로드 밸런서
L4와 L7 로드 밸런서의 라우팅 기준을 비교하고 분산 알고리즘·헬스 체크·세션 유지 방식을 설계합니다.
서비스의 사용자가 늘어나면, 하나의 서버로는 모든 요청을 처리할 수 없습니다.
서버를 여러 대로 확장(Scale Out)하면, 들어오는 요청을 어떤 기준으로 어떤 서버에 보낼지 결정해야 합니다.
이 역할을 하는 것이 로드 밸런서(Load Balancer)입니다.
L4 vs L7 로드 밸런서
로드 밸런서는 동작하는 계층에 따라 두 종류로 나뉩니다.
L4 로드 밸런서는 전송 계층(TCP/UDP)에서 동작합니다.
IP 주소, 포트, 프로토콜 같은 연결/흐름 정보를 기준으로 트래픽을 분배하고, HTTP 헤더나 URL 경로는 해석하지 않습니다.
HTTP 내용 분석을 생략할 수 있지만 실제 지연·처리량은 구현과 설정·부하에 따라 달라집니다.
AWS의 NLB(Network Load Balancer)가 대표적입니다.
L7 로드 밸런서는 애플리케이션 계층(HTTP)에서 동작합니다.
HTTP 헤더, URL 경로, 쿠키 등을 분석하여 라우팅 결정을 내립니다.
/api/* 요청은 API 서버 그룹으로, /static/* 요청은 정적 파일 서버 그룹으로 보내는 것이 가능합니다.
HTTPS 트래픽에서 URL이나 헤더를 기준으로 라우팅하려면 보통 로드 밸런서에서 TLS를 종료하고, 필요하면 백엔드로 다시 TLS를 연결합니다.
AWS의 ALB(Application Load Balancer)가 대표적입니다.
| 구분 | 기준과 적용 범위 |
|---|---|
| L4 | IP·포트·TCP/UDP 흐름으로 분산합니다. TLS 패스스루 또는 종단을 지원할 수 있고, WebSocket·gRPC도 전송 연결로 다룰 수 있습니다. AWS NLB가 한 예입니다. |
| L7 | HTTP의 Host·경로·헤더·쿠키를 해석합니다. 내용 기반 HTTPS 라우팅에는 TLS 종단이 필요합니다. HTTP/2·gRPC·WebSocket 지원 범위는 제품별로 확인하며 AWS ALB가 한 예입니다. |
로드 밸런싱 알고리즘
| 알고리즘 | 선택 기준과 주의점 |
|---|---|
| 라운드 로빈 | 순서대로 분배합니다. 동일한 요청 수가 동일한 처리 부하를 뜻하지는 않습니다. |
| 가중 순환 | 가중치 비율을 반영합니다. 서버 성능 변화에 맞춰 값을 관리해야 합니다. |
| 최소 연결 | 현재 연결 수가 적은 대상을 선택합니다. 연결별 비용 차이는 별도 문제입니다. |
| 가중 최소 연결 | 연결 수와 가중치를 함께 반영합니다. 항상 가장 정확한 부하 추정은 아닙니다. |
| IP 해시 | 같은 IP의 대상 일관성에 유리하지만 NAT 사용자 집중과 대상 변경을 고려합니다. |
| 랜덤 | 무작위로 선택합니다. 적은 표본에서는 분산 편차가 클 수 있습니다. |
| P2C | 무작위 후보 두 개를 부하 지표로 비교합니다. 지표의 신선도·비용에 영향을 받습니다. |
IP 해시는 “같은 IP는 같은 서버로 가게 할 가능성”을 높이지만, NAT나 모바일망처럼 여러 사용자가 같은 공인 IP를 공유하면 특정 서버에 부하가 몰릴 수 있습니다.
쿠키 기반 sticky session도 비슷하게 장애와 불균형을 같이 고려해야 합니다.
헬스 체크
헬스 체크는 보통 HTTP 요청, TCP 연결, gRPC 헬스 체크 같은 방식으로 대상 서버가 트래픽을 받을 수 있는지 확인합니다.
단, 모든 헬스 체크가 같은 의미는 아닙니다.
liveness는 프로세스가 죽었는지 확인하는 얕은 체크에 가깝고, readiness는 지금 트래픽을 받아도 되는지 확인하는 체크입니다.
DB 연결 같은 의존성 검사는 readiness에는 유용하지만, liveness에 과하게 넣으면 일시적인 DB 장애가 전체 서버 재시작으로 번질 수 있습니다.
# Flask 헬스 체크 엔드포인트 예: live와 ready를 분리
from flask import Flask, jsonify
import psycopg2
app = Flask(__name__)
@app.route("/live")
def live():
return jsonify({"status": "alive"}), 200
@app.route("/ready")
def ready():
checks = {"status": "ready"}
try:
conn = psycopg2.connect("dbname=mydb user=app")
conn.close()
checks["database"] = "ok"
except Exception:
checks["database"] = "fail"
checks["status"] = "unhealthy"
return jsonify(checks), 503
return jsonify(checks), 200이 코드는 Flask 엔드포인트를 정의하며 서버를 실행하는 명령은 포함하지 않습니다. /ready의 성공은 DB 연결·종료가 예외 없이 끝났다는 범위이고 쿼리 정확성이나 모든 요청의 성공을 보장하지 않습니다. 명시적인 DB 연결 시간 제한도 없습니다.
로드 밸런서는 설정된 시간 제한·주기·성공/실패 임계값으로 대상을 판단합니다.
탐지 지연과 진행 중인 요청 때문에 무중단 전환이 보장되지는 않습니다. 모든 대상이 비정상일 때의 동작도 제품별로 다르며, AWS ALB는 이 경우 비정상 대상에도 전달하는 fail-open 동작을 정의합니다.
배포 시 대상 등록 해제와 drain은 헬스 체크 실패 판정과 별도입니다. 새 요청 중단과 기존 연결의 종료·완료 대기를 구분해 조정합니다.
Sticky Session과 세션 공유
세션 기반 인증에서 사용자의 세션이 서버 A의 메모리에 저장되어 있다면, 다음 요청이 서버 B로 가면 로그인 상태가 유지되지 않습니다.
| 상태 관리 | 장애·확장 시 고려할 점 |
|---|---|
| Sticky session | 같은 대상에 붙이는 정책입니다. 서버 메모리 상태는 장애 때 소실될 수 있고 대상별 부하가 불균등해질 수 있습니다. |
| Redis 공유 | 여러 서버가 같은 세션을 읽습니다. Redis의 가용성·복제·일관성 운영이 필요합니다. |
| JWT | 서버 교체의 영향을 줄일 수 있지만 만료·폐기·키 회전과 별도 애플리케이션 상태 설계가 필요합니다. |
서버 메모리에 사용자 상태를 묶지 않으려면 외부 세션 저장소나 토큰 기반 인증을 검토합니다. Sticky session도 요구에 맞는 선택일 수 있으므로 장애 시 상태와 부하 분포를 기준으로 결정합니다.
로드 밸런서는 단순히 “요청을 나누는 장비”가 아니라, TLS 종료 지점, 장애 감지 기준, 세션 상태, 배포 중 drain 전략까지 함께 결정하는 운영 경계입니다.
다음 절에서는 로드 밸런서와 함께 자주 등장하는 프록시와 VPN을 다루겠습니다.