창구가 200개인 은행입니다 — 이 장에서 창구는 요청을 받는 자리(스레드)입니다. 위 그림에는 그중 여섯 개만 그렸고, 옆의 ×200 배지가 나머지를 대신합니다. 그런데 서류에 찍을 도장은 두 개뿐입니다.
도장을 쥐지 못한 창구는 손님을 부를 수가 없습니다. 그래서 그림의 여섯 창구 중 네 곳은 도장이 돌아오기를 기다리며 계속 비어 있고, 줄만 길어집니다 — 밖에서 보면 "직원은 많은데 줄은 안 줄어드는" 상태입니다. 그림은 기다리는 손님을 줄에 세워 두지만, 실제 서버에서는 그 요청이 이미 스레드를 하나 붙잡은 채 연결을 기다리며 앉아 있습니다.
위 그림에서 도장을 2에서 6으로 올려 보세요. 창구는 하나도 늘리지 않았는데 줄이 거의 없어집니다. 늘린 것은 창구가 아니라 "동시에 일을 시작할 수 있는 수"입니다.
이번엔 전화를 켜 보세요. 은행원이 수화기를 들고 있는 동안 그 창구는 다음 손님을 받지 못합니다 — 도장이 여섯 개나 있는데도 줄이 다시 자랍니다.
숫자로 뒤집기
실측 · 지금 계산됨
이제 같은 은행을 실제 서버 규모로 재봤습니다 — 그림보다 손님도 도장도 많습니다. 창구 200개, 한 건 처리에 200밀리초, 도장 10개에 손님이 초당 300명씩 옵니다. 실제로 볼일을 마치고 나간 손님은 초당 50.0명뿐이고, 나머지 초당 250명은 줄에 쌓입니다.
그 숫자는 우연이 아닙니다. 도장 하나가 1초에 5명을 보내니 도장 10개면 상한은 초당 50명이고, 위에서 잰 처리량이 바로 그 상한입니다 — 창구가 200개든 2,000개든 이 상한은 그대로입니다. 도장을 50개로 늘리면 상한이 초당 250명, 실측도 250명으로 정확히 5배가 됩니다.
도장을 창구 수만큼(200개) 늘리면 상한은 초당 1,000명이 되고, 이제 손님이 줄에 쌓이지 않습니다 — 초당 300명이 와서 초당 299명이 나갑니다. 이때 창구는 29.9%만 바쁩니다 — 이제 도장은 병목이 아닙니다.
그 상태에서 은행원이 전화 한 통을 560밀리초씩 붙잡으면, 손님 한 명이 창구를 쓰는 시간이 200에서 760밀리초로 늘어납니다. 상한은 초당 263명 — 도장 수는 200개 그대로인데 3.8분의 1이 됐습니다. 도장이 노는 것도 아닙니다: 통화 중인 손님이 도장을 쥔 채 기다리고 있어서 도장 이용률은 99.8%입니다 — 이제 처리량을 정하는 것은 도장이 몇 개냐가 아니라, 도장 하나를 얼마나 오래 붙잡고 있느냐입니다. 자기 일을 0초에 끝낸다 쳐도 창구 200개 ÷ 통화 560밀리초 = 초당 357명이 천장입니다.
이 숫자는 문구에 박아둔 값이 아니라, 이 화면을 그릴 때 시뮬을 돌려 나온 값입니다.
시뮬레이터에서 해보기
1.'HikariCP 풀 고갈' 프리셋을 기본값으로 열어 보세요 — 요청을 받는 자리(Tomcat threads.max)는 200개인데 DB 연결(HikariCP pool)은 5개입니다. KPI 패널의 Success는 2.4%뿐입니다.
2.인스펙터에서 HikariCP pool을 50으로 올려도 Success는 그대로 2.4%입니다 — 이번엔 더 뒤(디스크)가 좁은 쪽이기 때문입니다. 좁은 자리를 하나 넓히면 다음 좁은 자리가 보입니다.
프리셋 목록에서 "외부 의존 부분 장애"도 열어 보세요 — 500 RPS × 홀드 0.56초라 동시에 280개가 필요한데 Tomcat 스레드는 200개뿐이고, 성공률이 50.3%로 내려갑니다. 이 장의 전화가 그대로 나타납니다.
"Circuit Breaker — cascading 차단"은 그 전화를 빨리 끊습니다. CB를 켜면 내부 API 트래픽 5,077건이 전부 통과하고 p50이 243ms에서 60ms가 되지만, 총 성공률은 60.9%에서 51.7%로 내려갑니다 — 좁은 자리를 지키려고 외부 호출을 포기한 대가입니다.