Post

[대규모 시스템] Day 3: 로드밸런싱과 수평 확장 - 서버를 여러 대로 늘리기

[대규모 시스템] Day 3: 로드밸런싱과 수평 확장 - 서버를 여러 대로 늘리기

이 글은 AI(Claude)의 도움을 받아 작성하고, 작성자가 검토·편집했습니다.

서론: 한 대를 키우기 vs 여러 대로 늘리기

서버가 한계에 닿으면 두 갈래다. 더 큰 서버로 바꾸거나(수직 확장), 서버를 여러 대로 늘리거나(수평 확장). 수직 확장은 한계가 있고 단일 장애점이다. 대규모 시스템은 결국 수평 확장으로 간다. 그 전제 조건과 트래픽 분산 장치가 로드밸런서다.

1. 수직 확장 vs 수평 확장

1
2
3
4
5
6
7
8
9
수직 확장(Scale Up): 서버 사양을 키움 (CPU·RAM 증설)
  ✅ 간단, 코드 변경 없음
  ❌ 물리적 상한, 비용 급증, 단일 장애점, 무중단 불가

수평 확장(Scale Out): 서버 대수를 늘림
  ✅ 사실상 무한 확장, 장애 내성, 무중단 증설
  ❌ 무상태 설계 필요, 로드밸런서·분산 복잡도

대규모의 정석: 수평 확장. 단, 이를 위해 "무상태"가 전제다.

2. 무상태(Stateless)가 핵심 전제

수평 확장이 되려면 어느 서버가 요청을 받아도 같은 결과를 줘야 한다.

1
2
3
4
5
6
7
상태가 있는 서버 (확장 불가):
  로그인 세션을 서버 A의 메모리에 저장
  → 다음 요청이 서버 B로 가면 "로그인 안 됨" (세션 없음)

무상태 서버 (확장 가능):
  세션을 외부 공유 저장소(Redis - Day 1)나 토큰(JWT)에 둠
  → 어느 서버로 가도 동일하게 동작

원칙: 상태를 앱 서버 밖으로 밀어낸다. 세션은 Redis나 토큰, 파일은 객체 스토리지(S3), 데이터는 DB로. 앱 서버는 계산만 하는 교체 가능한 부품이 된다.

3. 로드밸런싱 알고리즘

1
2
3
4
5
Round Robin:      순서대로 분배. 가장 단순, 서버 성능이 균일할 때
Weighted RR:      서버 성능에 가중치 → 강한 서버에 더 많이
Least Connections: 현재 연결 수가 적은 서버로 → 요청 처리시간이 들쭉날쭉할 때
IP Hash:          클라이언트 IP 해시로 고정 → 세션 고정(sticky)이 필요할 때
Least Response Time: 응답이 빠른 서버 우선

대부분 Round Robin이나 Least Connections로 시작한다. gRPC 같은 장수명 연결은 L7 요청 단위 분산이 필요하다(gRPC Day 5 참고).

4. L4 vs L7 로드밸런서

1
2
3
4
5
6
7
8
9
10
11
L4 (전송 계층, TCP/UDP):
  IP·포트만 보고 분배. 패킷 내용 안 봄.
  ✅ 매우 빠름, 프로토콜 무관
  ❌ 콘텐츠 기반 라우팅 불가

L7 (애플리케이션 계층, HTTP):
  URL·헤더·쿠키를 보고 분배.
  ✅ 경로별 라우팅(/api → A, /img → B), TLS 종료, 콘텐츠 캐싱
  ❌ L4보다 느리고 비쌈

실무: L7(nginx, Envoy, ALB)이 유연성으로 가장 흔하다.

5. 헬스체크와 장애 제거

로드밸런서는 죽은 서버에 트래픽을 보내지 않아야 한다.

1
2
3
4
5
6
7
8
9
10
헬스체크:
  LB가 주기적으로 각 서버에 /health 요청
  → 실패하면 풀에서 자동 제외, 복구되면 자동 복귀

Active:  LB가 능동적으로 핑 (주기적 요청)
Passive: 실제 요청의 실패율을 보고 판단

우아한 종료(graceful shutdown):
  서버 종료 시 LB가 먼저 트래픽을 끊고(draining),
  진행 중 요청을 마친 뒤 종료 → 무중단 배포의 핵심
1
2
3
4
5
6
# 헬스 엔드포인트는 의존성까지 확인해야 의미가 있다
@app.route("/health")
def health():
    if not db.ping() or not redis.ping():
        return "unhealthy", 503   # LB가 이 서버를 제외
    return "ok", 200

6. 오토스케일링

1
2
3
4
5
6
7
8
트래픽에 따라 서버 대수를 자동 조절:
  지표(CPU·요청율·큐 lag - Day 2) 임계 초과 → 인스턴스 추가
  한가하면 → 인스턴스 축소 (비용 절감)

주의:
  - 스케일아웃은 즉시 안 됨(부팅 시간) → 선제적 스케일링·여유 용량
  - 플래핑(껐다 켰다 반복) 방지: 쿨다운·히스테리시스
  - DB 연결 풀: 서버가 늘면 DB 커넥션도 폭증 → 커넥션 풀러(PgBouncer)

오토스케일링은 앱이 무상태일 때만 안전하다. 상태가 있으면 새 인스턴스가 제 역할을 못 한다.

7. 다층 아키텍처로 종합

1
2
3
4
5
6
7
8
9
10
11
            [DNS / GeoDNS]  ← 지역별 분산
                 │
            [CDN]  ← 정적 자원 (Day 1)
                 │
         [L7 Load Balancer]  ← TLS 종료, 경로 라우팅
            /    │    \
      [App1]  [App2]  [App3]  ← 무상태, 오토스케일
            \    │    /
        [Redis 캐시]  [메시지 큐]  ← Day 1, Day 2
                 │
        [DB Primary] → [Read Replicas]  ← 읽기 분산 (분산 시스템 Day 2)

각 계층이 독립적으로 확장된다. 병목이 생긴 계층만 늘리면 된다.

8. Day 3 체크리스트

  1. 수평 확장이 대규모의 정석이며 무상태가 전제임을 이해했다.
  2. 상태(세션·파일)를 앱 서버 밖으로 밀어내는 설계를 파악했다.
  3. 로드밸런싱 알고리즘과 L4/L7의 차이를 구분했다.
  4. 헬스체크와 우아한 종료로 무중단 운영의 기반을 잡았다.
  5. 오토스케일링의 주의점(부팅 지연·플래핑·DB 커넥션)을 인지했다.

다음 편 예고

서버를 늘려도 한 클라이언트가 트래픽을 독점하거나 악용하면 시스템이 위험하다. Day 4에서는 레이트 리미팅으로 시스템을 보호하고 공정하게 자원을 분배하는 알고리즘을 다룬다.

This post is licensed under CC BY 4.0 by the author.