로드 밸런싱과 고가용성 (1) - 로드 밸런싱의 원리 - soo:bak
작성일 :
부하를 어떻게 분산하는가
웹 서비스가 성장하면 사용자가 늘어나고 트래픽이 증가합니다.
어느 순간 단일 서버로는 감당할 수 없는 지점에 도달하게 되는데, 이 문제를 어떻게 해결할 수 있을까요?
확장성의 두 가지 방향
서버 용량을 늘리는 방법에는 두 가지가 있습니다.
Scale-up (수직 확장)
더 강력한 서버를 사용하는 방식입니다.
1
2
3
CPU 업그레이드: 4코어 → 64코어
RAM 증설: 16GB → 512GB
SSD 추가: 더 빠른 스토리지
Scale-out (수평 확장)
반면, Scale-out은 서버 수를 늘리는 방식입니다.
1
2
3
4
5
서버 1대 (1000 요청/초)
↓
서버 10대 (10000 요청/초)
↓
서버 100대 (100000 요청/초)
직접 비교
두 방식의 특징을 비교하면 다음과 같습니다.
1
2
3
4
5
6
7
8
9
10
11
12
13
┌────────────────┬─────────────────────────┬─────────────────────────┐
│ 항목 │ Scale-up │ Scale-out │
├────────────────┼─────────────────────────┼─────────────────────────┤
│ 구현 난이도 │ 단순 (서버 한 대 유지) │ 복잡 (분산 시스템 필요) │
├────────────────┼─────────────────────────┼─────────────────────────┤
│ 확장 한계 │ 물리적 한계 존재 │ 이론상 무한 확장 │
├────────────────┼─────────────────────────┼─────────────────────────┤
│ 비용 효율 │ 고성능 장비는 급격히 비쌈│ 범용 서버로 효율적 │
├────────────────┼─────────────────────────┼─────────────────────────┤
│ 장애 대응 │ 단일 장애점(SPOF) │ 일부 장애에도 서비스 유지│
├────────────────┼─────────────────────────┼─────────────────────────┤
│ 상태 관리 │ 단순 (로컬 상태) │ 세션/캐시 공유 필요 │
└────────────────┴─────────────────────────┴─────────────────────────┘
언제 어떤 방식을 선택하는가
Scale-up이 적합한 경우:
- 빠른 대응이 필요하고 애플리케이션 수정이 어려울 때
- 데이터베이스처럼 상태 공유가 복잡한 시스템
- 트래픽 규모가 단일 서버로 충분한 경우
Scale-out이 적합한 경우:
- 트래픽이 지속적으로 증가하는 서비스
- 고가용성이 필수인 시스템
- 비용 효율이 중요한 대규모 서비스
현대의 웹 서비스는 대부분 Scale-out을 기본 전략으로 채택합니다. 이 방식에서 핵심적인 역할을 담당하는 것이 바로 로드 밸런서(Load Balancer)입니다.
로드 밸런서의 역할
1
2
3
4
5
6
7
8
9
10
클라이언트들 로드 밸런서 서버들
│
사용자1 ─────────────────► │ ─────► 서버 A
│
사용자2 ─────────────────► │ ─────► 서버 B
│
사용자3 ─────────────────► │ ─────► 서버 C
│
사용자4 ─────────────────► │ ─────► 서버 A
│
로드 밸런서는 다음과 같은 순서로 동작합니다.
- 클라이언트 요청을 수신
- 백엔드 서버 중 하나를 선택
- 요청을 해당 서버로 전달
- 응답을 클라이언트에게 반환
클라이언트는 로드 밸런서의 주소만 알면 됩니다. 백엔드 서버가 몇 대인지, 어떻게 구성되어 있는지는 알 필요가 없습니다.
L4 vs L7 로드 밸런싱
로드 밸런싱은 동작 계층에 따라 두 가지로 나뉩니다.
L4 로드 밸런싱 (Transport Layer)
전송 계층(TCP/UDP)에서 동작하며, IP 주소와 포트 번호만 확인합니다.
1
2
3
4
5
6
7
8
9
10
클라이언트 요청:
┌─────────────────────────────────────────┐
│ IP 헤더 │ TCP 헤더 │ 데이터 │
│ Dst:LB IP │ Dst:80 │ HTTP 요청...│
└─────────────────────────────────────────┘
L4 로드 밸런서:
- IP, 포트만 확인
- 데이터(HTTP 요청) 내용은 모름
- 빠른 결정, 빠른 전달
특징:
- 빠른 처리 (패킷 헤더만 검사)
- 프로토콜에 무관 (HTTP, HTTPS, 기타 모두)
- 하드웨어 가속 가능
- 내용 기반 라우팅 불가
L7 로드 밸런싱 (Application Layer)
애플리케이션 계층에서 동작하며, HTTP 헤더, URL, 쿠키 등 요청 내용을 확인할 수 있습니다.
1
2
3
4
5
6
7
8
9
10
HTTP 요청:
GET /api/users HTTP/1.1
Host: example.com
Cookie: session=abc123
X-User-Id: 42
L7 로드 밸런서:
- URL 경로 확인 (/api/users)
- 헤더 확인 (Host, Cookie)
- 내용 기반 라우팅 가능
특징:
- 내용 기반 라우팅 (URL, 헤더, 쿠키)
- SSL/TLS 종료 가능
- 요청 수정/재작성 가능
- 처리 비용이 높음
비교
1
2
3
4
5
6
7
8
9
10
11
12
13
┌────────────────┬─────────────────────┬─────────────────────┐
│ 항목 │ L4 │ L7 │
├────────────────┼─────────────────────┼─────────────────────┤
│ 결정 기준 │ IP, 포트 │ URL, 헤더, 쿠키 등 │
├────────────────┼─────────────────────┼─────────────────────┤
│ 성능 │ 매우 빠름 │ 상대적으로 느림 │
├────────────────┼─────────────────────┼─────────────────────┤
│ SSL 처리 │ 패스스루 │ 종료 가능 │
├────────────────┼─────────────────────┼─────────────────────┤
│ 유연성 │ 낮음 │ 높음 │
├────────────────┼─────────────────────┼─────────────────────┤
│ 사용 사례 │ 대용량 TCP 트래픽 │ 웹 애플리케이션 │
└────────────────┴─────────────────────┴─────────────────────┘
로드 밸런싱 알고리즘
로드 밸런서는 어떤 서버에 요청을 보낼지 결정해야 합니다. 이를 위해 다양한 분산 알고리즘을 사용합니다.
Round Robin
가장 단순한 방법으로, 순서대로 돌아가며 서버를 선택합니다.
1
2
3
4
5
6
요청 1 → 서버 A
요청 2 → 서버 B
요청 3 → 서버 C
요청 4 → 서버 A
요청 5 → 서버 B
...
Weighted Round Robin
서버마다 가중치를 부여하여, 성능이 좋은 서버에 더 많은 요청을 보냅니다.
1
2
3
4
5
서버 A (가중치 3)
서버 B (가중치 2)
서버 C (가중치 1)
요청 순서: A, A, A, B, B, C, A, A, A, B, B, C, ...
Least Connections
현재 연결 수가 가장 적은 서버를 선택합니다. 실시간 부하를 반영하는 동적 방식입니다.
1
2
3
서버 A: 50개 연결
서버 B: 30개 연결 ← 선택
서버 C: 45개 연결
Weighted Least Connections
Least Connections에 가중치를 결합한 방식입니다. 연결수 / 가중치가 가장 낮은 서버를 선택합니다.
IP Hash
클라이언트 IP 주소를 해시하여 서버를 선택합니다. 동일한 클라이언트는 항상 같은 서버로 연결됩니다.
1
2
3
hash(192.168.1.10) % 3 = 0 → 서버 A
hash(192.168.1.11) % 3 = 2 → 서버 C
hash(192.168.1.12) % 3 = 1 → 서버 B
Consistent Hashing
IP Hash의 문제를 해결하는 알고리즘입니다. 서버 변경 시에도 최소한의 요청만 재분배되어, 해당 서버가 담당하던 범위만 영향을 받고 대부분의 요청은 같은 서버를 유지합니다.
1
2
3
4
5
6
7
8
9
10
11
12
13
해시 링 (0 ~ 2^32-1):
0
│
서버C │ 서버A
│ /
●──────●
/ \
/ \
● ●
서버B 요청 해시값
요청의 해시값에서 시계방향으로 가장 가까운 서버 선택
알고리즘 비교와 선택 기준
각 알고리즘은 서로 다른 특성을 가지며, 상황에 따라 적합한 선택이 달라집니다.
1
2
3
4
5
6
7
8
9
10
11
12
13
┌────────────────────┬──────────────────┬──────────────────┬─────────────────────┐
│ 알고리즘 │ 장점 │ 단점 │ 적합한 상황 │
├────────────────────┼──────────────────┼──────────────────┼─────────────────────┤
│ Round Robin │ 단순, 오버헤드 적음│ 서버 성능/부하 무시│ 동일 사양 서버 │
├────────────────────┼──────────────────┼──────────────────┼─────────────────────┤
│ Weighted RR │ 서버 성능 반영 │ 실시간 부하 무시 │ 서버 사양이 다를 때 │
├────────────────────┼──────────────────┼──────────────────┼─────────────────────┤
│ Least Connections │ 실제 부하 반영 │ 연결 추적 비용 │ WebSocket, 긴 연결 │
├────────────────────┼──────────────────┼──────────────────┼─────────────────────┤
│ IP Hash │ 세션 지속성 제공 │ NAT 환경 불균등 │ 세션 기반 서비스 │
├────────────────────┼──────────────────┼──────────────────┼─────────────────────┤
│ Consistent Hashing │ 서버 변경 영향 최소│ 구현 복잡 │ 캐시 서버, 분산 DB │
└────────────────────┴──────────────────┴──────────────────┴─────────────────────┘
실무 가이드: 단순한 웹 서비스라면 Round Robin으로 시작하고, 서버 사양이 다르면 Weighted를 추가하며, 세션이 중요하면 IP Hash나 Cookie 기반 세션 지속성을 고려하는 것이 일반적입니다.
세션 지속성 (Session Persistence)
Sticky Sessions이라고도 부르며, 같은 클라이언트의 요청을 같은 서버로 보내는 기능입니다.
왜 필요한가
세션 지속성은 상태 저장 애플리케이션에서 중요한 역할을 합니다.
예: 장바구니
1
2
요청 1 (서버 A): 상품 추가 → 서버 A 세션에 저장
요청 2 (서버 B): 장바구니 조회 → 서버 B에는 세션 없음!
이처럼 상태가 특정 서버에 저장되어 있으면, 다른 서버로 요청이 가면 해당 상태에 접근할 수 없습니다.
Source IP 기반
IP Hash와 유사한 방식으로 클라이언트 IP 주소를 기준으로 서버를 결정합니다.
하지만 다음과 같은 문제가 있습니다.
- NAT 뒤의 사용자들이 같은 서버로 몰림
- 공용 IP를 공유하면 다른 사용자도 같이 묶임
Cookie 기반
로드 밸런서가 쿠키를 설정합니다.
1
2
3
4
5
6
첫 번째 응답:
Set-Cookie: SERVERID=server-a
이후 요청:
Cookie: SERVERID=server-a
→ 서버 A로 라우팅
장점:
- 정확한 사용자 식별
- NAT 문제 없음
단점:
- L7 로드 밸런서 필요
- 쿠키를 지원하지 않는 클라이언트에서 동작 안 함
더 좋은 해결책: Stateless 설계
그러나 근본적인 해결책은 가능하면 상태를 서버 외부로 이동하는 것입니다.
1
2
3
4
5
6
세션을 Redis/Memcached에 저장
│
┌───────┴───────┐
▼ ▼
서버 A 서버 B
(세션 조회) (세션 조회)
이렇게 하면 어느 서버로 가든 동일한 세션에 접근할 수 있어서, 별도의 세션 지속성 설정이 필요하지 않게 됩니다.
헬스 체크
로드 밸런서는 백엔드 서버의 상태를 지속적으로 모니터링해야 합니다. 장애가 발생한 서버로 트래픽을 보내면 사용자 경험이 크게 저하되기 때문입니다.
Active Health Check
로드 밸런서가 능동적으로 서버 상태를 확인합니다.
1
2
3
4
5
6
7
8
9
10
로드 밸런서 서버
│ │
│ ──── GET /health ────────────► │
│ ◄──── 200 OK ──────────────── │
│ │
│ (10초 후) │
│ │
│ ──── GET /health ────────────► │
│ ◄──── 200 OK ──────────────── │
│ │
헬스 체크 종류
TCP 체크:
- 포트에 TCP 연결 시도
- 연결되면 정상
- 가장 단순
HTTP 체크:
- HTTP 요청을 보내고 응답 확인
- 상태 코드 확인 (예: 200이면 정상)
- 응답 내용 확인 가능
1
2
3
4
5
6
7
8
9
10
# Nginx 예시
upstream backend {
server 10.0.0.1:8080;
server 10.0.0.2:8080;
health_check interval=5s
fails=3
passes=2
uri=/health;
}
실패 임계값
한 번 실패했다고 바로 서버를 제외하지 않습니다. 일시적인 네트워크 지연이나 타임아웃을 고려하여 임계값을 설정합니다.
설정 예시를 살펴보면,
fails=3: 3번 연속 실패하면 unhealthypasses=2: 2번 연속 성공하면 다시 healthyinterval=5s: 5초마다 체크
Passive Health Check
실제 요청의 결과로 상태를 판단합니다.
1
2
3
4
요청 → 서버 A → 5xx 에러 (실패 카운트 +1)
요청 → 서버 A → timeout (실패 카운트 +1)
요청 → 서버 A → 5xx 에러 (실패 카운트 +1, 임계값 도달)
→ 서버 A를 unhealthy로 표시
추가적인 헬스 체크 트래픽이 필요 없다는 장점이 있지만, 실제 사용자가 오류를 경험한 후에야 장애를 감지한다는 단점도 있습니다.
따라서 대부분의 환경에서는 Active + Passive를 함께 사용하여, 사전 예방과 실시간 감지를 병행합니다.
정리: 분산이 확장의 핵심
로드 밸런싱 핵심 요약:
- Scale-out: 서버를 추가하여 확장
- L4 vs L7: 성능 vs 유연성 트레이드오프
- 알고리즘 선택: Round Robin, Least Connections 등
- 세션 지속성: 필요하면 사용, 가능하면 Stateless 설계
- 헬스 체크: 장애 서버 자동 제외
Part 2에서는 DNS 기반 로드 밸런싱을 살펴봅니다.
Part 3에서는 고가용성 아키텍처를 다룹니다.
관련 글