포스트

큐잉 이론 한 조각 - 도착률과 서비스율로 파라미터를 정하기

스레드 풀 크기, 커넥션 풀 크기, 벌크헤드 permit 수를 정할 때 대개 “적당한 둥근 수”를 쓴다. Tomcat 스레드 고갈 실험에서도 벌크헤드 permit을 20으로 두고 “근거 있는 값이 아니다”라고 한계에 적었다. 큐잉 이론의 아주 작은 부분만 알아도 그 값을 추정하고 검증할 수 있다.

Little’s Law

유일하게 외울 가치가 있는 식이다.

1
2
3
4
5
L = λ × W

L = 시스템 안에 있는 평균 요청 수 (동시성)
λ = 도착률 (초당 요청 수)
W = 요청 하나가 시스템에 머무는 평균 시간

가정이 거의 없다. 분포가 무엇이든, 스케줄링이 어떻든 정상 상태면 성립한다. 그래서 세 값 중 둘을 알면 나머지가 나온다.

스레드 풀 크기 추정에 바로 쓴다.

1
2
3
4
5
초당 100건, 요청당 평균 200ms
→ L = 100 × 0.2 = 20

즉 평균 20개가 동시에 처리 중이다. 스레드 20개가 최소선이고,
변동과 꼬리를 감당하려면 그보다 커야 한다.

거꾸로도 쓴다. 스레드가 200개인데 평균 동시 처리가 20이라면 180개는 대부분 놀고 있다. 그 자체는 문제가 아니지만, 풀 크기가 실제로 무엇을 막고 있는지를 알 수 있다.

스레드 고갈 실험의 산술도 이 식이다. 30초 걸리는 호출이 초당 20건 도착하면 L = 600이다. 스레드가 200개뿐이니 10초 만에 고갈된다. 실제로 t+15s 시점의 dump에서 202개가 전부 묶여 있었다.

사용률이 오르면 대기가 폭발한다

두 번째로 알아야 할 것은 사용률과 대기 시간의 관계가 선형이 아니라는 점이다.

단순한 대기 모델(M/M/1)에서 서버 하나의 사용률을 ρ라 하면

1
평균 대기 시간 ∝ ρ / (1 - ρ)
사용률대기 배수
50%1×
80%4×
90%9×
95%19×
99%99×

50%에서 80%로 올리면 대기가 4배가 된다. 사용률이 1에 가까워질수록 무한대로 발산한다.

이 곡선이 실무의 여러 관행을 설명한다.

  • 용량 계획에서 목표 사용률을 70~80%로 두는 이유. 그 위는 변동에 취약하다.
  • 컨테이너 CPU 상한에서 “리퀘스트를 평시 최대의 2배로 잡으면 평시 사용률이 50% 이하”가 낭비처럼 보여도 정당한 이유.
  • 오토스케일링 임계를 90%가 아니라 60~70%에 두는 이유. 90%에서 스케일을 시작하면 이미 지연이 9배다.

서버가 여러 대면(M/M/c) 곡선이 완만해진다. 같은 사용률이라도 서버가 많을수록 대기가 적다. 큰 풀 하나가 작은 풀 여럿보다 효율적인 이유이고, 반대로 트래픽을 잘게 쪼개면 같은 총 용량으로도 대기가 늘어난다.

변동성이 대기를 만든다

같은 평균이라도 분산이 크면 대기가 길어진다. 모든 요청이 정확히 200ms 걸리는 서버와, 절반은 10ms 절반은 390ms 걸리는 서버는 평균이 같지만 후자의 대기가 훨씬 길다.

실무적 함의 셋.

  • 빠른 요청과 느린 요청을 섞지 않는다. 큐를 나누거나 별도 풀을 준다. 그렇지 않으면 느린 것 하나가 뒤의 빠른 것들을 막는다(head-of-line blocking).
  • 타임아웃이 분산을 자른다. 꼬리를 잘라 내면 평균 대기가 크게 준다. 타임아웃이 지연 관리 수단이기도 한 이유다.
  • 재시도는 도착률을 올린다. λ가 커지면 사용률이 오르고, 위 곡선에서 대기가 비선형으로 늘어난다. 이미 포화된 시스템에 재시도를 더하면 무너진다(ParityPay 9편에서 재시도 3회가 기관 요청을 정확히 3.00배로 만들었다).

파라미터를 정하는 절차

  1. λ를 잰다. 피크 도착률. 평균이 아니라 피크다.
  2. W를 잰다. 의존 자원의 응답 시간 분포. 평균과 p99를 함께.
  3. L = λ × W로 최소선을 구한다.
  4. 여유를 곱한다. 목표 사용률 70~80%를 역산하면 L / 0.75 정도.
  5. 상한을 확인한다. 그 크기가 의존 자원(DB 커넥션, 외부 기관의 허용 동시성)을 넘지 않는지. 넘으면 그쪽이 진짜 상한이다.
  6. 부하로 검증한다. 계산은 출발점이지 답이 아니다.

5번이 중요하다. 스레드를 200개로 늘려도 커넥션 풀이 10이면 동시성은 10이다. 가장 작은 상한이 시스템의 상한이고, 나머지를 키우는 것은 대기 위치만 옮긴다.

이 설명이 깨지는 곳

  • M/M/1은 단순화된 모델이다. 도착이 포아송이고 서비스 시간이 지수 분포라는 가정은 실제와 다르다. 곡선의 모양을 이해하는 데 쓰고 구체적 수치를 믿지 않는다.
  • Little’s Law는 정상 상태 식이다. 부하가 급변하는 구간에는 적용되지 않는다.
  • 평균만으로 설계하면 꼬리를 놓친다. L은 평균 동시성이고, 순간 최대는 훨씬 크다.
  • 계산은 검증을 대신하지 못한다. 실제 부하에서 재는 것이 항상 최종 근거다.

무엇을 재면 확인되는가

  1. 부하 중 실제 동시 처리 수(tomcat_threads_busy, hikaricp_connections_active)를 재고 λ × W와 비교한다. 맞으면 모델이 쓸 만한 것이고, 안 맞으면 어딘가에 큐가 더 있다.
  2. 도착률을 단계적으로 올리며 사용률과 p99를 함께 기록한다. 무릎(knee)이 어디인지 보인다.
  3. 요청을 빠른 것과 느린 것으로 나눠 같은 풀에 넣었을 때와 분리했을 때의 p99를 비교한다.

2번이 용량 계획의 실질이다. 계산으로 얻은 값을 이 곡선으로 확인한다. spring-ops-lab의 하니스가 이 측정에 쓸 수 있는 형태이고, 벌크헤드 permit 20의 근거를 다시 세우는 것이 남은 과제다.

실무와의 접점

대량 배치의 청크 병렬 처리에서 병렬도를 정할 때 기준은 “돌려 보고 빠른 값”이었다. 지금 다시 하면 DB의 동시 처리 능력을 W로 잡고 L을 계산한 뒤 그 근처에서 탐색할 것이다. 시행착오가 틀린 것은 아니지만, 출발점이 있으면 탐색 범위가 줄고 왜 그 값인지 설명할 수 있다.

정리

  • L = λ × W. 세 값 중 둘을 알면 나머지가 나온다. 풀 크기의 최소선을 이것으로 구한다.
  • 사용률과 대기는 선형이 아니다. 80%에서 4배, 95%에서 19배다.
  • 목표 사용률 70~80%, 오토스케일 임계 60~70%가 이 곡선에서 나온다.
  • 서버가 많을수록 같은 사용률에서 대기가 적다. 큰 풀 하나가 작은 풀 여럿보다 낫다.
  • 분산이 크면 대기가 길어진다. 빠른 요청과 느린 요청을 섞지 않는다.
  • 재시도는 λ를 올려 대기를 비선형으로 키운다.
  • 가장 작은 상한이 시스템의 상한이다. 나머지를 키우면 대기 위치만 옮긴다.

참고

장애 대응과 관측
이 기사는 저작권자의 CC BY 4.0 라이센스를 따릅니다.

댓글

아직 댓글이 없습니다