Load Balancer: 여러 서버로 요청을 분산해 가용성과 처리량 높이기
로드 밸런서(Load Balancer)는 클라이언트 요청을 여러 서버에 나눠 전달한다. 서버 상태와 분산 알고리즘을 바탕으로 장애 서버를 제외하고, 특정 서버에 트래픽이 몰리는 현상을 줄인다. 전송 계층에서 연결을 분산하거나 애플리케이션 계층에서 HTTP 요청 내용을 기준으로 목적지를 선택할 수 있다.
By 송민성
1. 개념
로드 밸런서(Load Balancer)는 클라이언트와 여러 백엔드 서버(Backend Server) 사이에서 요청을 적절한 서버로 전달하는 구성 요소다.
클라이언트
│
▼
로드 밸런서
├── 서버 A
├── 서버 B
└── 서버 C클라이언트는 개별 서버 주소를 알 필요 없이 로드 밸런서의 가상 인터넷 프로토콜 주소(Virtual IP Address)나 도메인으로 요청한다.
로드 밸런서는 동작 계층에 따라 주로 두 종류로 나뉜다.
- 계층 4 로드 밸런서(L4 Load Balancer): 인터넷 프로토콜 주소(IP Address), 포트, 전송 제어 프로토콜(TCP), 사용자 데이터그램 프로토콜(UDP) 정보를 기준으로 연결을 분산한다.
- 계층 7 로드 밸런서(L7 Load Balancer): 경로, 호스트, 헤더, 쿠키 같은 하이퍼텍스트 전송 프로토콜(HTTP) 정보를 기준으로 요청을 분산한다.
L4 예시:
203.0.113.10:443 → 10.0.0.11:8443
L7 예시:
/api/* → API 서버 그룹
/images/* → 이미지 서버 그룹
Host: admin.example.com → 관리자 서버 그룹2. 왜 사용하는가
처리량 확장
서버 한 대가 처리할 수 있는 요청 수에는 한계가 있다. 같은 애플리케이션을 여러 서버에 배포하고 요청을 분산하면 수평 확장(Horizontal Scaling)이 가능하다.
고가용성
서버 한 대에 장애가 발생해도 정상 서버로 요청을 전달할 수 있다. 단일 서버 장애가 전체 서비스 장애로 이어지는 위험을 줄인다.
트래픽 집중 방지
특정 서버에 요청이 몰리면 응답 시간이 길어지고 오류가 증가한다. 로드 밸런서는 서버별 연결 수나 가중치 등을 이용해 부하를 분산한다.
배포 제어
새 버전에 일부 트래픽만 보내는 카나리 배포(Canary Deployment), 두 서버 그룹을 전환하는 블루-그린 배포(Blue-Green Deployment)에 사용할 수 있다.
3. 동작 원리
3.1 요청 전달 과정
- 클라이언트가 로드 밸런서 주소로 연결한다.
- 로드 밸런서가 정상 상태인 서버 목록을 확인한다.
- 분산 알고리즘으로 대상 서버를 선택한다.
- 요청을 대상 서버에 전달한다.
- 서버 응답을 클라이언트에 반환한다.
L7 로드 밸런서는 보통 역방향 프록시(Reverse Proxy)처럼 동작한다. 클라이언트 연결을 로드 밸런서에서 종료한 뒤 백엔드 서버에 별도 연결을 만든다.
3.2 분산 알고리즘
라운드 로빈
라운드 로빈(Round Robin)은 서버를 순서대로 선택한다.
요청 1 → 서버 A
요청 2 → 서버 B
요청 3 → 서버 C
요청 4 → 서버 A서버 성능과 요청 처리 비용이 비슷할 때 적합하다.
가중 라운드 로빈
가중 라운드 로빈(Weighted Round Robin)은 성능이 높은 서버에 더 많은 요청을 보낸다.
서버 A 가중치 3
서버 B 가중치 1
선택 순서 예시: A → A → A → B최소 연결
최소 연결(Least Connections)은 현재 활성 연결 수가 가장 적은 서버를 선택한다. 요청 처리 시간이 크게 다르거나 장시간 연결이 많을 때 유용하다.
해시 기반 분산
해시 기반 분산(Hash-based Distribution)은 클라이언트 인터넷 프로토콜 주소, 세션 식별자 같은 값을 해시해 서버를 선택한다.
서버 인덱스 = 해시(세션 식별자) % 서버 수같은 값은 같은 서버로 연결되지만, 서버 수가 바뀌면 많은 매핑이 달라질 수 있다. 일관된 해싱(Consistent Hashing)은 서버 추가·제거 시 매핑 변경 범위를 줄인다.
3.3 상태 검사
상태 검사(Health Check)는 백엔드 서버가 요청을 처리할 수 있는지 주기적으로 확인하는 과정이다.
GET /health
200 OK → 정상 서버로 유지
연결 실패 또는 500 → 분산 대상에서 제외상태 검사는 두 수준으로 구성할 수 있다.
- 생존 검사(Liveness Check): 프로세스가 살아 있는지 확인한다.
- 준비 상태 검사(Readiness Check): 데이터베이스 연결이나 초기화가 끝나 실제 요청을 받을 수 있는지 확인한다.
4. 코드 예제
다음 예제는 Node.js 표준 모듈로 만든 라운드 로빈 방식의 L7 로드 밸런서다. 두 백엔드 서버를 실행하고 8080 포트로 들어오는 요청을 번갈아 전달한다.
import http, {
type IncomingMessage,
type ServerResponse,
} from "node:http";
type Backend = {
host: string;
port: number;
};
const backends: Backend[] = [
{ host: "127.0.0.1", port: 3001 },
{ host: "127.0.0.1", port: 3002 },
];
let nextBackendIndex = 0;
function selectBackend(): Backend {
const backend = backends[nextBackendIndex];
nextBackendIndex = (nextBackendIndex + 1) % backends.length;
return backend;
}
function startBackend(port: number, name: string): void {
http
.createServer((_request: IncomingMessage, response: ServerResponse) => {
response.writeHead(200, {
"content-type": "application/json; charset=utf-8",
});
response.end(
JSON.stringify({
server: name,
port,
}),
);
})
.listen(port, "127.0.0.1", () => {
console.log(`${name}: http://127.0.0.1:${port}`);
});
}
const loadBalancer = http.createServer(
(clientRequest: IncomingMessage, clientResponse: ServerResponse) => {
const backend = selectBackend();
const proxyRequest = http.request(
{
host: backend.host,
port: backend.port,
method: clientRequest.method,
path: clientRequest.url,
headers: {
...clientRequest.headers,
host: `${backend.host}:${backend.port}`,
"x-forwarded-for": clientRequest.socket.remoteAddress ?? "",
"x-forwarded-proto": "http",
},
},
(backendResponse: IncomingMessage) => {
clientResponse.writeHead(
backendResponse.statusCode ?? 502,
backendResponse.headers,
);
backendResponse.pipe(clientResponse);
},
);
proxyRequest.on("error", (error: Error) => {
console.error(
`${backend.host}:${backend.port} 연결 실패: ${error.message}`,
);
if (!clientResponse.headersSent) {
clientResponse.writeHead(502, {
"content-type": "application/json; charset=utf-8",
});
}
clientResponse.end(
JSON.stringify({
error: "백엔드 서버에 연결할 수 없습니다.",
}),
);
});
clientRequest.pipe(proxyRequest);
},
);
startBackend(3001, "서버 A");
startBackend(3002, "서버 B");
loadBalancer.listen(8080, "127.0.0.1", () => {
console.log("로드 밸런서: http://127.0.0.1:8080");
});실행 방법:
npx tsx load-balancer.ts다른 터미널에서 여러 번 요청한다.
curl http://127.0.0.1:8080
curl http://127.0.0.1:8080
curl http://127.0.0.1:8080응답의 server 값이 서버 A, 서버 B, 서버 A 순서로 바뀐다.
이 코드는 분산 원리를 보여주는 최소 예제다. 상태 검사, 재시도, 연결 시간 제한, 전송 계층 보안(TLS), 웹소켓(WebSocket), 백프레셔(Backpressure) 정책은 운영용 로드 밸런서에서 별도로 처리해야 한다.
5. 시간 복잡도 또는 성능 특성
서버 선택 비용
서버 수를 n이라고 할 때 일반적인 선택 비용은 다음과 같다.
| 알고리즘 | 선택 시간 | 추가 상태 |
|---|---|---|
| 라운드 로빈 | O(1) | 다음 서버 인덱스 |
| 가중 라운드 로빈 | 구현에 따라 O(1)~O(n) | 서버별 가중치 |
| 최소 연결 선형 탐색 | O(n) | 서버별 활성 연결 수 |
| 최소 연결 우선순위 큐 | O(log n) | 우선순위 큐와 연결 수 |
| 일반 해시 테이블 기반 선택 | 평균 O(1) | 해시 키와 서버 목록 |
로드 밸런서의 실제 성능은 선택 알고리즘보다 다음 요소의 영향을 크게 받을 수 있다.
- 전송 계층 보안 종료(TLS Termination)에 필요한 암호화 연산
- 새 연결 생성 비용과 연결 재사용 여부
- 요청·응답 본문 크기
- 백엔드 서버의 응답 시간
- 장시간 연결과 웹소켓 수
- 로드 밸런서 자체의 네트워크 대역폭과 파일 서술자(File Descriptor) 한도
라운드 로빈은 요청 개수만 균등하게 나눈다. 요청별 처리 시간이 다르면 실제 중앙 처리 장치(CPU) 사용량이나 활성 연결 수는 균등하지 않을 수 있다.
6. 실무 사용 사례
웹 애플리케이션 수평 확장
동일한 애플리케이션 인스턴스를 여러 대 실행하고 로드 밸런서가 요청을 분산한다.
사용자 → 로드 밸런서 → 웹 서버 1
웹 서버 2
웹 서버 3애플리케이션을 무상태(Stateless)로 만들면 어느 서버가 요청을 받아도 같은 결과를 낼 수 있다. 로그인 세션은 외부 세션 저장소나 서명된 토큰으로 관리한다.
경로 기반 라우팅
하나의 도메인에서 경로에 따라 서로 다른 서비스로 전달한다.
/api/users/* → 사용자 서비스
/api/orders/* → 주문 서비스
/static/* → 정적 파일 서버마이크로서비스(Microservices) 환경에서는 응용 프로그램 인터페이스 게이트웨이(API Gateway)나 인그레스 컨트롤러(Ingress Controller)가 이 역할을 맡기도 한다.
무중단 배포
기존 서버 그룹을 유지한 채 새 버전 서버를 추가한다. 상태 검사가 성공하면 새 서버에 트래픽을 보내고, 배포가 끝난 기존 서버는 연결 배출(Connection Draining) 후 제거한다.
지역 분산
전역 로드 밸런싱(Global Load Balancing)은 사용자 위치, 지역별 장애 상태, 지연 시간 정책에 따라 데이터 센터나 클라우드 리전을 선택한다.
7. 주의할 점
로드 밸런서 자체가 단일 장애 지점이 될 수 있다
로드 밸런서가 한 대뿐이면 해당 장비의 장애가 전체 서비스 장애로 이어진다. 운영 환경에서는 이중화, 관리형 로드 밸서 서비스, 장애 조치(Failover)를 사용한다.
세션 고정은 확장을 어렵게 만들 수 있다
세션 고정(Session Affinity)은 같은 사용자의 요청을 같은 서버로 보낸다. 서버 메모리에 세션을 저장한 기존 애플리케이션에 유용하지만, 서버 장애 시 세션이 사라지고 부하가 불균형해질 수 있다.
가능하면 세션을 외부 저장소에 두고 서버를 무상태로 구성한다.
재시도는 중복 처리를 만들 수 있다
로드 밸런서가 응답 지연을 장애로 판단해 요청을 재시도하면 결제나 주문이 중복 실행될 수 있다. 재시도 대상 메서드와 조건을 제한하고, 변경 요청에는 멱등성 키(Idempotency Key)를 사용한다.
상태 검사가 너무 단순하면 장애를 놓친다
프로세스가 살아 있어도 데이터베이스 연결 고갈이나 의존 서비스 장애 때문에 요청을 처리하지 못할 수 있다. 반대로 모든 외부 의존성을 검사하면 일시적인 의존 서비스 장애가 전체 서버 제외로 확대될 수 있다.
클라이언트 주소 전달을 검증해야 한다
L7 로드 밸런서를 거치면 백엔드는 로드 밸런서 주소를 클라이언트 주소로 볼 수 있다. X-Forwarded-For나 표준 Forwarded 헤더를 사용하되, 신뢰할 수 있는 프록시가 추가한 값만 인정해야 한다.
8. 핵심 정리
- 로드 밸런서는 여러 백엔드 서버로 요청이나 연결을 분산한다.
- L4는 주소·포트·전송 프로토콜을, L7은 HTTP 경로·헤더·쿠키 등을 기준으로 분산한다.
- 라운드 로빈, 최소 연결, 가중치, 해시 기반 방식은 서로 다른 트래픽 특성에 맞는다.
- 상태 검사와 연결 배출이 장애 대응과 무중단 배포를 가능하게 한다.
- 무상태 서버, 멱등성, 로드 밸런서 이중화까지 함께 설계해야 안정적인 확장이 가능하다.