09. Routing·load balancing·autoscaling — 세 제어 루프를 섞지 않기
이 장의 핵심 질문
- 요청을 "어디로 보낼지" 정하는 일은 사실 세 개의 다른 제어 루프다. 무엇이 다른가?
- 라우팅·로드밸런싱·오토스케일링의 입력·출력·시간축은 각각 무엇인가?
- 이 셋을 섞으면 왜 시스템 동작을 잘못 진단하게 되는가?
앞 장과의 연결 — 04장에서 추론 DP는 "복제와 라우팅의 축"이라 했고, 08장에서 KV가 여러 인스턴스를 오가며 상태 관리가 문제가 됐다. 이 장은 여러 복제본·풀로 흩어진 시스템에서 요청과 자원을 조율하는 세 제어 루프를 분리한다. 이 과정의 핵심 기율(층·축 섞지 않기)이 여기서 정점에 이른다.
먼저: "요청을 보낸다"는 하나의 일이 아니다
배포가 여러 복제본(04장 DP)과 여러 풀(07장 P/D)로 커지면, "요청을 처리한다"는 여러 결정으로 쪼개진다. 흔히 이것을 뭉뚱그려 "로드밸런싱"이라 부르지만, 실제로는 성격이 다른 세 제어 루프가 있다.
일상 비유로 콜센터를 생각하자.
- 라우팅(routing) — 걸려온 전화를 "어느 부서·어느 상담원"에게 연결할지 정한다. (요청 하나하나의 목적지 결정)
- 로드밸런싱(load balancing) — 상담원들 사이에 통화량이 고르게 퍼지도록 조절한다. (부하의 균등 분산)
- 오토스케일링(autoscaling) — 통화가 몰리는 시간대에 상담원을 더 부르고, 한산하면 줄인다. (자원 규모 자체의 조절)
세 일은 다른 질문에 답하고, 다른 속도로 움직인다. 섞으면 "왜 이 상담원만 바쁘지?"(로드밸런싱)와 "상담원이 아예 부족하네"(오토스케일링)를 구분 못 하게 된다.
세 제어 루프의 입력·출력·시간축
각 루프를 무엇을 보고(입력) / 무엇을 결정하며(출력) / 얼마나 자주 도는가(시간축) 로 분리한다.
1) 라우팅 (routing)
- 입력 — 개별 요청의 속성(어느 풀로 갈지 P/D, 프리픽스 캐시 적중 여부, 세션 소속, 필요한 모델 등)과 후보 인스턴스들의 현재 상태.
- 출력 — 이 요청 하나를 어느 인스턴스/풀로 보낼지.
- 시간축 — 요청 단위(요청마다 즉시 결정). 가장 빠른 루프.
- 예: "이 요청은 프리픽스 캐시가 있는 인스턴스로", "이 요청은 prefill 풀 P로, KV 생성 후 decode 풀 D로"(07장).
2) 로드밸런싱 (load balancing)
- 입력 — 인스턴스들의 부하 지표(큐 길이, 활성 요청 수, 메모리 점유, 처리 지연 등)의 분포.
- 출력 — 요청·부하가 인스턴스들 사이에 고르게 퍼지도록 하는 조정(라우팅 결정에 반영되거나, 재분배).
- 시간축 — 요청 단위~짧은 주기. 라우팅과 겹치지만 관심사가 다르다(개별 목적지 vs 전체 균등).
- 예: "한 인스턴스에 요청이 쏠리지 않게", "큐가 긴 인스턴스는 피하기".
3) 오토스케일링 (autoscaling)
- 입력 — 집계된 부하 추세(최근 수십 초~수 분의 요청률, 전체 큐·이용률, SLO 여유).
- 출력 — 복제본/풀의 개수 자체를 늘리거나 줄이기(scale out/in).
- 시간축 — 느림(수십 초~수 분). 인스턴스를 띄우고 내리는 데 시간이 걸리므로(모델 로딩 등) 빠르게 반응할 수 없다.
- 예: "피크가 오니 decode 풀을 2개 더", "새벽엔 복제본을 줄여 비용 절감".
flowchart TB
REQ["요청 스트림"] --> R{"라우팅<br/>요청→인스턴스<br/>⏱ 요청 단위"}
R --> I0["인스턴스 0"]
R --> I1["인스턴스 1"]
R --> I2["인스턴스 2"]
LB["로드밸런싱<br/>부하 균등화<br/>⏱ 요청~짧은 주기"] -.->|"부하 분포 관찰·조정"| R
AS["오토스케일링<br/>인스턴스 개수 조절<br/>⏱ 수십 초~분"] -.->|"추세 보고 규모 변경"| POOL
subgraph POOL["인스턴스 풀 (규모가 변함)"]
I0
I1
I2
end
classDef req fill:#fff3c4,stroke:#d4a017,color:#000
classDef route fill:#c4f1f4,stroke:#0891b2,color:#000
classDef comp fill:#cfe2ff,stroke:#2563eb,color:#000
classDef metric fill:#d1f0d1,stroke:#16a34a,color:#000
class REQ req
class R,LB route
class I0,I1,I2 comp
class AS metric
| 루프 | 입력 | 출력 | 시간축 | 답하는 질문 |
|---|---|---|---|---|
| 라우팅 | 요청 속성 + 인스턴스 상태 | 요청 하나의 목적지 | 요청 단위(가장 빠름) | "이 요청을 어디로?" |
| 로드밸런싱 | 인스턴스 부하 분포 | 부하 균등화 조정 | 요청~짧은 주기 | "부하가 고른가?" |
| 오토스케일링 | 집계 부하 추세 | 인스턴스 개수 변경 | 수십 초~분(느림) | "자원이 충분한가?" |
📌 핵심 — 세 루프는 시간축이 가장 명확히 다르다: 라우팅은 요청마다, 로드밸런싱은 짧은 주기로, 오토스케일링은 느리게 돈다. 어떤 문제를 만나면 "이건 어느 시간축의 문제인가"를 먼저 물어라. 순간적 쏠림은 로드밸런싱, 지속적 자원 부족은 오토스케일링이다.
왜 섞으면 안 되는가 — 오진의 예
세 루프를 섞으면 진단이 틀린다.
- 오진 1: 로드밸런싱 문제를 오토스케일링으로 — "한 인스턴스만 큐가 길다"(로드밸런싱 결함)를 보고 "자원이 부족하다"며 오토스케일링으로 인스턴스를 늘리면, 새 인스턴스에도 여전히 쏠려 낭비만 한다. 문제는 개수가 아니라 분배였다.
- 오진 2: 오토스케일링 문제를 라우팅으로 — 전체 부하가 지속적으로 용량을 넘는데(오토스케일링 필요), 라우팅 알고리즘만 계속 바꾸면, 어디로 보내든 다 밀린다. 요령으로 풀 수 없는 규모의 문제다.
- 오진 3: 시간축 혼동 — 오토스케일링은 느리다(인스턴스 로딩). 이 느림을 모르고 "부하가 튀는 순간마다 스케일링이 반응하길" 기대하면, 스케일링이 도착하기 전에 순간 부하는 이미 지나가 있다(뒤늦은 대응, 진동). 순간 대응은 로드밸런싱·큐·backpressure(08장)의 몫이다.
⚠️ 경계 혼동 주의 — 세 루프는 서로를 대체하지 않는다 — 라우팅을 잘한다고 오토스케일링이 필요 없어지지 않고, 인스턴스를 늘린다고 로드밸런싱 결함이 사라지지 않는다. 각 루프는 다른 시간축의 다른 문제를 담당한다. "로드밸런서를 좋게 하면 다 해결된다"는 흔한 오해다.
상태(state)가 만드는 복잡성 — 스테이트풀 라우팅
앞의 콜센터 비유는 "어느 상담원이든 같다"고 가정했다. 하지만 LLM 서빙은 상태(state) 가 있어 더 복잡하다.
- 프리픽스 캐시 지역성 — 어떤 인스턴스가 특정 프리픽스의 KV를 이미 갖고 있으면(08장), 같은 프리픽스 요청을 그 인스턴스로 보내는 게 유리하다. "아무 데나 균등"이 최선이 아니다.
- 세션 친화(session affinity) — 멀티턴 대화는 같은 상태를 이어가는 인스턴스로 보내면 재계산을 아낀다.
- P/D 경로 — PD 분리(07장)에서 요청은 P를 거쳐 D로 가는 2단계 경로를 탄다. 라우팅이 이 경로를 알아야 한다.
이 때문에 라우팅은 단순 균등 분배(stateless load balancing)와 충돌할 수 있다. 캐시 지역성을 살리려 한 인스턴스로 몰면 부하가 쏠리고, 균등하게 펴면 캐시 재사용을 잃는다.
⚖️ 절충 — 지역성(locality) vs 균등(balance) — 상태 지역성을 살리는 라우팅(캐시·세션 친화)은 재계산·전송을 아끼지만 부하 쏠림을 부른다. 균등 로드밸런싱은 부하를 고르게 하지만 캐시 재사용을 깬다. 둘은 근본적으로 긴장 관계이며, 최적은 워크로드(프리픽스 공유율·세션 길이)에 달렸다(조건 의존). "무조건 균등" 또는 "무조건 지역성"은 둘 다 틀리기 쉽다.
배포 D-라우팅 — 척추에 조율을 얹다
배포 확장 — 배포가 여러 복제본(DP)과 P/D 풀로 커지자, 팀은 세 루프를 명시적으로 분리한다: 라우팅은 각 요청을 (프리픽스·세션·P/D 경로를 보고) 적절한 인스턴스로, 로드밸런싱은 순간 쏠림을 완화하도록, 오토스케일링은 분 단위 추세로 P·D 풀 개수를 따로 조절한다(07장의 독립 스케일링). 이때 지역성 vs 균등 절충과 스케일링의 느린 시간축을 함께 관리해야 한다.
🔬 증거 읽기 — 라우팅/스케일링 주장 검증 — "우리 라우터가 처리량을 X% 높였다" 또는 "오토스케일링으로 비용 Y% 절감"이라는 주장을 만나면 확인하라:
- (a) 어느 루프의 개선인가 — 라우팅? 로드밸런싱? 오토스케일링? 시간축이 뭉개진 주장인지.
- (b) 워크로드의 상태 특성(프리픽스 공유율·세션 분포)은 무엇인가 — 지역성 이득은 여기 크게 의존.
- (c) 어떤 부하 패턴(꾸준함 vs 급변)인가 — 오토스케일링 이득은 부하 변동성에 의존.
- (d) SLO 위반을 포함해 측정했나 — 비용만 줄이고 SLO를 어겼다면 goodput(10장) 관점에서 손해다.
루프·워크로드·부하 패턴이 다르면 다른 배포와 비교 불가다(15장).
이 장에서 배운 것
- 요청·자원 조율은 세 개의 다른 제어 루프다: 라우팅(요청→인스턴스), 로드밸런싱(부하 균등), 오토스케일링(인스턴스 개수).
- 셋은 입력·출력·시간축이 다르다. 특히 시간축: 라우팅은 요청 단위, 로드밸런싱은 짧은 주기, 오토스케일링은 수십 초~분(느림).
- 섞으면 오진한다: 분배 문제를 규모로, 규모 문제를 분배로, 순간 대응을 느린 스케일링에 맡기는 실수.
- LLM 서빙은 상태(프리픽스 캐시·세션·P/D 경로) 가 있어, 지역성 vs 균등이 긴장 관계다. 무조건 균등도 무조건 지역성도 틀리기 쉽다.
- 각 루프는 서로를 대체하지 않는다. "로드밸런서만 좋게 하면 다 된다"는 오해다.
🔎 이 장의 '지지하지 않는 결론' + 'unknown으로 남겨야 할 것'
- 지지하지 않는 결론 — "좋은 로드밸런싱이면 오토스케일링이 필요 없다"거나 "지역성 라우팅이 항상 최선이다"는 지지되지 않는다. 각 루프는 다른 시간축의 다른 문제를 담당하고, 지역성/균등 최적은 워크로드 의존이다.
- unknown으로 남길 것 — 특정 배포의 라우팅 정책·스케일링 임계값·프리픽스 공유율·세션 분포는 공개되지 않으면
unknown. "이 라우터가 X% 개선"은 그 워크로드·부하 패턴 조건 없이 옮기지 않는다.
✍️ 확인 문제
- "한 인스턴스만 큐가 계속 길다"와 "전체 큐가 지속적으로 늘어난다"는 각각 세 루프 중 어느 것의 문제인가? 두 상황에 오토스케일링으로 인스턴스를 늘리는 대응이 각각 적절한지/부적절한지 시간축으로 설명하라.
- (경계 혼동 유형) 세 제어 루프를 "시간축"으로 구분하라. 오토스케일링이 느리다는 사실이, 왜 "순간 부하 급증에 대한 대응"을 오토스케일링이 아니라 로드밸런싱·큐·backpressure(08장)에 맡겨야 하는 이유가 되는가?
- (절충 유형) 프리픽스 캐시 지역성을 극대화하려 같은 프리픽스 요청을 한 인스턴스로 몰면 무엇을 얻고 무엇을 잃는가? 이 지역성 vs 균등 긴장의 최적이 왜 워크로드(프리픽스 공유율)에 의존하는지 말하라.
다음 장에서는 이 묶음을 닫으며, 지금까지 등장한 goodput·tail·큐·backpressure를 하나로 묶는 계약 — SLO와 capacity planning — 을 다룬다. "약속한 품질을 지키며 얼마나 처리할 수 있는가"를 계약으로 쓰는 법이 초점이다.
다음 → 10. SLO와 capacity planning — 약속을 숫자로 쓰기