실시간 AI 에이전트 로드밸런싱: QPS 대신 active session을 봐야 하는 이유
요약: 음성 에이전트와 실시간 상담형 AI는 일반 HTTP API처럼 QPS만 보고 분산하면 안 된다. 긴 WebSocket 또는 gRPC 스트림이 살아 있는 동안 백엔드는 이미 미래의 부하를 떠안고 있기 때문이다. active session 기반 지표가 필요하다.
기존 API와 실시간 AI의 부하가 다른 이유
일반 웹 API는 요청 단위로 생각하기 쉽다. 클라이언트가 요청을 보내고, 서버가 처리하고, 응답을 돌려주면 끝난다. 그래서 QPS, 평균 지연 시간, CPU 사용률, 에러율이 좋은 기본 지표가 된다.
실시간 AI 에이전트는 다르다. 특히 음성 대화형 에이전트는 오디오 chunk, partial transcript, 모델 출력, TTS 결과가 양방향으로 계속 흐른다. 사용자가 말을 끊으면 서버는 진행 중인 음성 생성을 멈추고, 컨텍스트를 갱신하고, 필요한 도구를 다시 호출해야 한다. 연결은 살아 있고 상태도 계속 변한다.
Google Developers Blog의 session-aware load balancing 글이 지적한 핵심도 여기에 있다. QPS는 들어온 요청 수를 보지만, 실시간 AI에서는 “이미 열려 있는 대화 수”가 더 중요한 부하 신호가 된다. 50ms짜리 요청 100개와 20분짜리 세션 5개는 QPS로 보면 전자가 바빠 보일 수 있다. 하지만 후자는 백엔드가 긴 시간 동안 메모리, 컨텍스트, 연결, 향후 CPU 스파이크를 책임져야 한다.
QPS와 CPU만 보면 생기는 장애
실시간 에이전트에서 CPU 사용률은 순간 부하만 보여준다. 예를 들어 한 서버에 20개의 음성 세션이 열려 있지만 모두 조용한 상태라고 하자. CPU는 낮다. 기존 로드밸런서는 이 서버가 한가하다고 판단하고 새 세션을 계속 배정할 수 있다.
문제는 사용자가 동시에 말하기 시작하는 순간이다. 오디오 처리, VAD, STT, LLM 호출, TTS가 동시에 올라오며 CPU와 네트워크가 급등한다. 방금까지 여유 있어 보이던 서버가 갑자기 병목이 된다. 사용자는 첫 응답 지연, 끊김, 중간 발화 유실, 세션 종료를 경험한다.
반대로 active session만 봐도 부족하다. 세션 80개가 열려 있어도 대부분 idle이면 CPU는 낮을 수 있다. 세션 10개라도 모두 긴 발화와 도구 호출을 반복하면 CPU는 높을 수 있다. 따라서 정답은 하나의 지표가 아니라 hybrid signal이다. active session은 이미 약속한 부하를 나타내고, CPU와 메모리는 현재 압력을 나타낸다.
active session 카운터는 애플리케이션 안에서 세야 한다
일반 로드밸런서는 연결 수를 볼 수 있지만, 그 연결이 실제 대화 세션인지, idle인지, 재시도인지, 이미 종료되어야 하는 ghost session인지 알기 어렵다. 가장 정확한 위치는 런타임 내부다. 스트리밍 세션이 시작되는 시점에 카운터를 올리고, 종료, 취소, timeout, disconnect가 확정되는 시점에 반드시 내린다.
구현은 단순해 보이지만 운영에서는 엣지가 많다. finally 블록에서 decrement를 보장해야 하고, timeout과 disconnect가 동시에 들어와 double decrement가 나지 않게 해야 한다. 반대로 decrement가 누락되면 서버가 계속 꽉 찬 것처럼 보인다. 이런 ghost session은 메모리 누수보다 더 위험할 수 있다. 라우팅 레이어가 잘못된 신호를 믿고 트래픽을 엉뚱하게 보내기 때문이다.
또한 로드밸런서가 지표를 polling한다면 snapshot 일관성도 필요하다. 세션은 실시간으로 늘고 줄지만, 라우팅 결정은 일정 주기의 지표를 보고 이뤄진다. 보고 간격이 길거나 값이 흔들리면 한 서버에 새 세션이 몰릴 수 있다.
hybrid capacity 모델의 기본 아이디어
가장 단순한 식은 max_sessions - active_sessions다. 서버가 100세션까지 안정적이고 현재 80개면 20개를 더 받을 수 있다는 식이다. 하지만 AI 세션은 모두 같은 비용을 쓰지 않는다. 조용한 상담, 긴 음성 입력, 도구 호출이 많은 상담, TTS가 긴 상담은 부하가 다르다.
그래서 active session을 CPU/메모리와 섞어야 한다. Google 글은 active session을 일종의 rate 신호로 변환해 기존 balancing 신호와 결합하는 방식을 설명한다. 예를 들어 10초 reporting window 동안 90개 세션이 유지되면 9 pretend QPS처럼 취급할 수 있다. 이렇게 하면 정적인 세션 수를 라우터가 이해하는 흐름 단위로 바꿀 수 있다.
실무에서는 다음처럼 접근하면 된다.
- target CPU를 정한다. 예: 80%
- 최근 window의 평균 CPU를 계산한다.
- active session 1개당 평균 CPU 비용을 추정한다.
- 안전 계수를 둔다. 예: 갑작스런 동시 발화를 대비해 2.0
- 추가로 받을 수 있는 세션 수를 보수적으로 계산한다.
이 모델은 완벽할 필요가 없다. 목적은 가장 덜 위험한 서버로 새 세션을 보내는 것이다. 특히 한 서버가 active session은 적어도 CPU가 90%라면 새 세션을 보내지 않아야 한다. 반대로 active session은 많지만 CPU가 낮은 서버에는 천천히만 추가 배정해야 한다.
벤치마크도 세션형으로 바꿔야 한다
짧은 요청을 많이 보내는 부하 테스트는 실시간 AI 장애를 잘 재현하지 못한다. 세션형 벤치마크가 필요하다. 최소한 다음 변수를 흔들어야 한다.
- 동시 세션 수
- 세션 지속 시간
- 사용자가 말하는 시간과 침묵하는 시간의 비율
- 갑작스런 동시 발화 패턴
- cancel, timeout, disconnect 비율
- backend 인스턴스 수
- backend별 최대 세션 수
지표도 바꿔야 한다. 평균 latency만 보면 부족하다. time-to-first-stream, p95/p99 startup latency, dropped session, backend별 active session 분포, overloaded assignment rate, forced disconnect 후 카운터 복구 여부를 봐야 한다.
특히 overloaded assignment rate는 유용하다. 라우터가 이미 위험한 backend에 새 세션을 보낸 비율이다. 이 값이 높으면 CPU가 터지기 전에 라우팅 정책부터 고쳐야 한다.
실행 체크리스트
- QPS, CPU 외에 active session 지표를 애플리케이션 런타임에서 수집한다.
- 세션 시작, 정상 종료, timeout, cancel, disconnect 경로를 모두 테스트한다.
- double decrement와 누락 decrement를 막는 idempotent cleanup을 구현한다.
- active session과 CPU/메모리를 결합한 hybrid routing 기준을 만든다.
- 세션 수가 많지만 CPU가 낮은 서버에는 새 세션을 천천히 배정한다.
- CPU가 높은 서버에는 active session이 적어도 새 세션을 제한한다.
- 부하 테스트에 긴 세션, idle 상태, 동시 발화, 강제 disconnect를 포함한다.
- p95 startup latency, dropped session, backend별 session 분포를 대시보드에 올린다.
실시간 AI 에이전트의 병목은 요청 수가 아니라 살아 있는 대화의 무게에서 온다. QPS만 보는 로드밸런싱은 이 무게를 놓친다. active session을 운영 지표로 올리는 순간부터 장애 원인이 훨씬 선명해진다.