본문으로 건너뛰기
목록으로

트래픽이 늘면 어디부터 문제가 생기나 — 스케일이 만드는 문제들

Johny Cho
Software Engineer @ Kurly

트래픽이 2배가 됐다고 문제가 2배로 늘지는 않습니다. 평소엔 멀쩡하다가 어느 지점을 넘는 순간 갑자기 성능이 급락하는 게 스케일 문제의 특징입니다. "어디가 먼저, 왜 문제가 생기는지"를 스택별로 훑고, 대응 원칙을 정리합니다. 각 항목의 자세한 내용은 관련 글로 링크했습니다.

왜 "갑자기" 문제가 발생하나 — 포화의 비선형성​

자원(스레드·커넥션·CPU)이 여유로울 땐 부하가 늘어도 지연이 완만하게 오릅니다. 그런데 포화(100%)에 가까워지면 대기가 급증해 지연이 가파르게 오릅니다. 이 "임계점(knee)"을 넘으면 작은 트래픽 증가가 급격한 성능 저하로 이어집니다.

그래서 "지금 여유 있어 보임"이 안전을 뜻하지 않습니다 — 임계점 바로 앞일 수 있습니다. 아래에서 다룰 문제들은 대부분, 이 자원 포화(고갈)가 스택의 어느 부분에서 가장 먼저 나타나는지에 관한 것입니다.

이 임계점은 감이 아니라 대기행렬 이론으로 설명되는 현상입니다. 가장 단순한 M/M/1 모델에서 평균 응답시간은 대략 R = S / (1 − ρ) 꼴이라(S=서비스 시간, ρ=이용률), 이용률 ρ가 100%에 가까워지면 응답시간이 쌍곡선으로 발산합니다. 도착·처리 시간의 변동성까지 반영한 Kingman 공식은 대기시간이 ρ / (1 − ρ)에 비례한다고 말하고, 병렬 시스템에서는 Universal Scalability Law(USL) 가 경합(contention)·코히런시(coherency) 비용 때문에 어느 지점 뒤로는 처리량이 오히려 감소(retrograde) 한다고 설명합니다.

스택별로 어디부터 문제가 생기나​

① 커넥션·스레드 풀 고갈
가장 먼저 한계에 닿는 지점. 처리시간이 조금만 늘어도 동시 요청이 불어나 풀이 바닥납니다(리틀의 법칙: 동시 요청 = 유입 × 처리시간). 풀이 마르면 요청이 큐에서 대기하다 타임아웃. 자세히는 서버 지표 읽기의 리틀의 법칙 부분.

② 캐시 — 히트율 저하·스탬피드·핫키
트래픽이 늘면 캐시 미스가 왜 함께 늘까요? 이유는 크게 두 가지입니다.

  • 미스 개수: 히트율이 그대로여도 요청 수가 늘면 미스의 절대 개수가 비례해 늘어, 캐시가 막아 주던 원본(DB) 부하도 그만큼 커집니다(히트율 90%여도 요청이 10배면 미스도 10배).
  • 히트율 자체: 트래픽이 늘수록 더 다양한 키가 유입돼 캐시 용량을 넘기면 에빅션(오래된 키 제거)이 잦아지고, 재사용되기 전에 밀려난 키는 다음에 다시 미스가 됩니다. 즉 트래픽 증가가 히트율까지 끌어내려 미스가 이중으로 늘어납니다.

여기에 두 패턴이 더해집니다. 인기 키가 만료되는 짧은 순간 다수 요청이 동시에 미스 나 원본으로 몰리는 캐시 스탬피드, 특정 키에 요청이 집중돼 그 키를 보관한 노드가 과부하되는 핫키입니다. 결국 캐시가 흡수하던 부하가 DB로 전가되고, 캐시가 완충 역할을 못 하는 순간 DB가 앞서 말한 임계점을 넘습니다. → 커머스 캐시 트래픽 대응.

③ DB 병목
대개 최종 병목은 DB입니다. 슬로 쿼리·인덱스 부재가 트래픽과 겹쳐 커지고, 락 경합이 심해지며, 읽기 복제본은 복제 지연이 벌어집니다. 커넥션 상한도 먼저 찹니다.

④ 큐 적체·백프레셔(backpressure)
유입이 처리 속도를 앞서면 큐가 계속 쌓입니다. 방치하면 메모리까지 밀어붙이죠. → 대용량 Redis 대응, 서버 지표 읽기.

⑤ CPU·메모리·GC 포화
CPU가 차면 런 큐가 길어지고, 힙 압박으로 GC가 자주·오래 돌아 지연을 키우다 OOM으로 갑니다. → JVM 힙 덤프 분석.

⑥ 팬아웃·P99 레이턴시 증폭
한 요청이 내부적으로 여러 번 호출하면(fan-out), 그중 하나만 느려도 전체가 느려집니다. 트래픽이 늘수록 내부 호출이 늘어, 하나라도 느린 호출에 걸릴 확률이 높아지고 그만큼 P99 레이턴시가 빠르게 나빠집니다. → 서버 지표 읽기.

⑦ 부하 불균형(핫 파티션)
샤딩·파티셔닝을 해도 특정 키·샤드에 트래픽이 쏠리면 그 노드만 포화됩니다. 평균은 여유인데 한 곳이 멈추는 형태.

부하가 부하를 낳는다 — 연쇄 장애​

트래픽 증가의 가장 무서운 형태는 스스로 커지는 실패입니다.

  • 재시도 폭풍(retry storm) — 느려진 서비스에 클라이언트가 재시도하면, 그 재시도가 부하를 더 키워 더 느려지는 악순환.
  • 연쇄 장애(cascading failure) — 한 컴포넌트가 느려지면 그걸 기다리는 상류가 함께 밀리고, 스레드가 묶여 옆 기능까지 정지.
  • 동시 쇄도(thundering herd) — 캐시 만료·재시작 직후 수많은 요청이 한꺼번에 몰림.

이걸 막으려면 "실패를 빨리, 국소적으로" 만들어야 합니다 — 타임아웃·서킷 브레이커·백오프·로드 셰딩. 관련 개념은 내결함성과 고가용성.

대응 원칙​

증상별 대처가 아니라 방향만 요약하면:

  • 여유(headroom)를 두고 임계점 전에 확장 — 자원 사용률을 100%가 아니라 여유 구간에서 운영, 오토스케일은 웜업 시간을 감안.
  • 캐시·풀 튜닝 — 히트율·풀 크기·타임아웃을 부하에 맞게.
  • 백프레셔·로드 셰딩 — 못 받을 요청은 빨리 거절(429 Too Many Requests)해 전체를 지킴.
  • 서킷 브레이커·타임아웃·백오프(지터) — 재시도 폭풍·연쇄 장애 차단.
  • 수평 확장·샤딩 + 핫키 분산 — 단일 노드 포화 회피.
  • 비동기·큐로 평탄화 — 스파이크를 큐로 흡수(대신 적체 관리).
  • 관측과 부하 테스트 — 지표로 임계점을 미리 보고, 부하 테스트로 한계를 먼저 찾는다.

정리​

  • 스케일 문제는 선형이 아니라 임계점을 넘으면 급격히 나빠진다.
  • 보통 커넥션/풀 → 캐시 → DB 순으로 먼저 문제가 생기고, CPU·GC·큐·핫키가 뒤따른다.
  • 최악은 재시도 폭풍·연쇄 장애 — 실패를 빨리·국소적으로 만드는 장치가 필수.
  • 대비의 핵심은 "여유를 두고, 실패를 빨리 끊고, 병목을 분산하고, 지표로 미리 본다"입니다.

메시지 큐로 스파이크를 완충하는 선택은 메시지 큐 비교도 함께 보세요.

용어 한 줄 정리​

용어쉬운 뜻
임계점(knee)포화 직전, 지연이 급격히 오르기 시작하는 지점
백프레셔(backpressure)처리보다 유입이 빨라 큐가 쌓이는 상태
로드 셰딩(load shedding)초과 요청을 빨리 거절해 전체를 살리기
재시도 폭풍재시도가 부하를 더 키워 악화되는 악순환
연쇄 장애한 곳의 지연이 상류로 번져 함께 장애로 이어짐
P99 레이턴시상위 1%가 겪는 응답시간 — 팬아웃이 많을수록 크게 증폭