이 글에서 얻는 것

  • 트래픽이 늘 때 “느낌"이 아니라 리틀의 법칙(L = λW) 과 포화 지표로 증설 시점을 계산하는 방법을 익힙니다.
  • p95 지연, 동시 처리량, 에러율을 같이 보고 증설 vs 쿼리 튜닝 vs 백프레셔 중 무엇을 먼저 할지 우선순위를 정할 수 있습니다.
  • 실무에서 바로 쓰는 임계치 예시(큐 길이, CPU, 커넥션 풀, GC, 타임아웃)를 기준으로 운영 런북 초안을 만들 수 있습니다.

핵심 개념/이슈

용량 계획이 자주 실패하는 이유는 단순합니다. 평균 QPS만 보고 계획하기 때문입니다. 실제 장애는 평균이 아니라 피크 구간, 특히 p95/p99 지연이 튈 때 발생합니다. 그래서 용량 계획은 아래 3개를 함께 봐야 합니다.

  1. 유입률(λ, arrival rate): 초당 요청 수(QPS)
  2. 처리시간(W, response time): 평균이 아니라 p95 기준
  3. 동시 작업 수(L, in-flight): 시스템 안에 동시에 걸려 있는 요청 수

리틀의 법칙은 L = λ × W 입니다. 예를 들어 p95 기준으로 2,000 rps가 들어오고, p95 응답시간이 250ms(0.25s)라면 in-flight는 대략 500개입니다. 이 수치가 웹 서버 스레드, 비동기 워커, DB 커넥션 풀, 외부 API 동시 호출 한계를 침범하면 포화가 시작됩니다.

여기서 중요한 포인트는 “서버 전체"보다 병목 리소스별로 따로 계산해야 한다는 점입니다.

  • 애플리케이션 스레드풀
  • DB 커넥션 풀
  • Redis/외부 API 연결
  • 메시지 큐 소비자(concurrency)

한 곳이라도 임계치에 가까우면 전체 지연이 급증합니다. 이 현상은 이미 API 레이트 리밋과 백프레셔에서 다룬 것처럼, 대기열이 무한할 때 더 악화됩니다.

또한 용량 계획은 성능 최적화와 별개가 아닙니다. SQL 튜닝, 인덱스, 캐시 전략이 W를 줄여주면 같은 하드웨어로 더 높은 λ를 소화할 수 있습니다. 즉, 용량 계획의 핵심은 “몇 대 더"보다 W를 줄이거나 불필요한 λ를 줄이는 구조 변경입니다. 관련해서 MySQL 인덱스와 실행계획, Redis 캐시 스탬피드 완화를 함께 보는 게 좋습니다.

실무 적용

아래는 작은 팀(서비스 13개, MAU 10만100만) 기준으로 바로 적용 가능한 의사결정 프레임입니다.

1) SLO와 임계치 먼저 고정

  • 사용자 API p95: 300ms 이하
  • 에러율(5xx + 타임아웃): 0.5% 이하
  • 피크 10분 구간 기준 운영

SLO를 먼저 못 박아야 이후 계산이 의미가 있습니다. 목표가 없으면 “빠르다/느리다” 논쟁만 생깁니다.

2) 병목 후보별 현재 여유율 측정

최소 1주 데이터로 아래를 수집합니다.

  • 앱 CPU: 평균/피크 (권장 피크 65~70% 이내)
  • 힙/GC: full GC 빈도, STW 시간
  • DB 커넥션 풀 점유율: 피크 80% 초과 여부
  • DB p95 쿼리시간, lock wait
  • 큐 길이와 소비 지연(consumer lag)
  • 외부 API 타임아웃/재시도율

3) 포화지점 계산 예시

가정:

  • 피크 유입 2,400 rps
  • p95 응답시간 280ms(0.28s)
  • 현재 in-flight ≈ 2,400 × 0.28 = 672

앱 인스턴스 6대라면, 인스턴스당 in-flight 약 112입니다. 만약 인스턴스당 실효 동시 처리 한계가 130이라면 여유가 14%밖에 없습니다.

실무 기준으로 여유율 20% 미만이면 “다음 이벤트/캠페인 전” 조치가 필요합니다.

  • 우선순위 1: W 감소 (느린 쿼리, 불필요한 직렬화, 외부 API 동기 호출 제거)
  • 우선순위 2: λ 감소 (캐시 적중률 개선, 중복 요청 억제, 배치화)
  • 우선순위 3: 수평 증설 (인스턴스/컨슈머 증설)

많은 팀이 3번을 먼저 하지만, 1·2를 선행하면 비용 증가를 크게 줄일 수 있습니다.

4) 증설/튜닝/차단 의사결정 기준(예시)

  • DB 커넥션 풀 점유율이 피크 85% 이상이면서 대기시간이 증가: 쿼리/인덱스 튜닝 우선, 즉시 풀 증가 금지
  • 앱 CPU 75% 이상 + run queue 증가 + GC 안정: 앱 증설
  • 외부 API 타임아웃 급증 + 재시도율 2배 이상: 재시도 제한 + 서킷 브레이커 + 부분 기능 강등
  • 큐 소비 지연이 10분 이상 누적: 컨슈머 증설 또는 작업 분리, 동시에 생산 속도 제한

핵심은 “한 번에 하나의 가설"을 검증하는 것입니다. DB가 병목인데 앱만 늘리면 비용만 늘고 지연은 그대로입니다.

5) 주간 용량 리뷰 운영 방식

주 1회 30분 고정 회의로 아래만 봅니다.

  • 지난주 피크 10분의 L, λ, W
  • SLO 위반 구간 3개
  • 가장 큰 원인 1개(쿼리/외부 API/락/GC/큐)
  • 다음 주 조치 1~2개와 예상 개선 수치

이 루틴을 돌리면 “장애 나면 증설” 패턴에서 벗어나, 사전 예방형 운영으로 전환됩니다. 관측 항목은 SLO/SLI/Error Budget와 같이 설계해야 합니다.

트레이드오프/주의점

  1. 평균 응답시간 중심 보고의 함정 평균 120ms여도 p99가 2초면 사용자 체감은 나쁩니다. 용량 계획은 평균이 아니라 tail latency 기준으로 해야 합니다.

  2. 무작정 커넥션 풀 확장의 부작용 앱 풀을 늘리면 DB에는 더 큰 동시 부하가 걸립니다. DB CPU/락 경합이 늘어 전체가 더 느려질 수 있습니다.

  3. 재시도 정책이 용량을 망가뜨릴 수 있음 타임아웃 상황에서 재시도는 트래픽 증폭기입니다. retry budget, jitter, 최대 재시도 횟수를 반드시 제한하세요. 이 주제는 Timeout/Retry/Backoff와 연결됩니다.

  4. 증설만으로는 구조적 병목이 해결되지 않음 느린 단일 쿼리, N+1, 불필요한 직렬 처리, 거대한 트랜잭션은 인스턴스를 늘려도 계속 남습니다.

체크리스트 또는 연습

실무 체크리스트(운영 반영용)

  • 서비스별 SLO(p95, 에러율)를 숫자로 합의했다.
  • 피크 10분 기준 L = λW를 주간으로 계산한다.
  • 병목 리소스별 임계치(앱 CPU, DB 풀, 큐 지연)를 문서화했다.
  • 임계치 초과 시 액션(튜닝/증설/차단) 우선순위를 런북으로 정리했다.
  • 재시도 정책에 retry budget, 지수 백오프, jitter를 적용했다.

연습 과제

현재 서비스 1개를 골라 아래를 계산해보세요.

  1. 피크 10분의 λ, p95 W를 구하고 L 계산
  2. 인스턴스당 in-flight와 실효 한계 비교
  3. 여유율이 20% 미만이면 조치안 2개 작성
    • 조치 A: W를 20% 줄이는 방법
    • 조치 B: λ를 15% 줄이는 방법
  4. 두 조치의 비용/리스크/예상 효과를 표 대신 bullet로 비교

숫자로 계산된 용량 계획은 “불안해서 증설"이 아니라 “근거 있는 증설"을 가능하게 만듭니다.

관련 글