왜 기존 로드밸런싱으로는 실시간 AI를 못 버티는가

일반적인 웹 API는 요청-응답이 끝나면 서버 자원이 해제됩니다. 그래서 QPS, CPU 사용률 같은 지표로 충분했죠. 그런데 실시간 AI 에이전트는 다릅니다. WebSocket이나 gRPC 양방향 스트림 위에서 오디오 청크, 전사 텍스트(transcript), 모델 출력, TTS 음성이 동시에 흐르는 장기 실행 세션이에요.

사용자가 말을 끊으면? 서버는 즉시 음성 생성을 중단하고, 컨텍스트를 갱신하고, 필요하면 새 툴을 호출하고, 다른 응답 초안을 잡아야 합니다. 연결을 끊지 않고서요. 이건 더 이상 '요청 최적화' 문제가 아니라 살아있는 대화를 관리하는 인프라 문제입니다.

근거자료: Scaling real-time AI agents with session-aware load balancing

QPS와 CPU가 속이는 두 가지 함정

  • Task A: 50ms 짜리 요청 100개 처리
  • Task B: 20분짜리 세션 5개 처리

QPS만 보면 Task A가 바빠 보입니다. 하지만 실제 커밋된 워크로드는 Task B가 압도적이에요. QPS는 '도착량'만 세지, '이미 진행 중인 대화 수'는 못 셉니다.

CPU도 마찬가지예요. 음성 런타임이 조용한 세션 20개를 물고 있으면 CPU는 놀고 있는 것처럼 보입니다. 그런데 그 20명이 동시에 말을 시작하면? CPU가 수직 상승합니다. CPU는 현재 압력, 활성 세션 수는 약속된 미래 부하입니다. 둘 다 봐야 해요.

Server rack with active session counters for real-time AI load balancing Programming Illustration

활성 세션 카운터, 제대로 구현하기

표준 로드밸런서는 연결이 '진짜 대화 중'인지, '유휴 리스너'인지, '헬스체크 재시도'인지 구분하지 못합니다. 그래서 애플리케이션 레벨에서 직접 세션 상태를 보고해야 해요.

가장 단순하면서 견고한 패턴은 스트리밍 세션 라이프사이클의 시작/종료 지점에서 카운터를 증감하는 겁니다.

suspend fun handleAudioSession(audioStream: Flow<AudioFrame>) {
    activeSessions.incrementAndGet()
    try {
        withTimeout(20.minutes) {
            audioStream.collect { frame ->
                processAndRespond(frame)
            }
        }
    } finally {
        // finally 블록이 핵심입니다.
        // 이게 있어야 라우팅 결정에 쓸 수 있을 만큼 카운트가 정확해져요.
        activeSessions.decrementAndGet()
    }
}

finally 블록은 단순한 정리가 아닙니다. 이게 빠지면 세션이 끝난 뒤에도 서버가 과부하로 보이고, 반대로 이중 감소가 일어나면 없는 용량을 있다고 거짓 보고해서 트래픽을 과도하게 끌어옵니다. 타임아웃, 취소, 연결 끊김이 동시에 발생하는 엣지 케이스도 반드시 처리해야 해요.

하이브리드 용량 모델: 세션 + CPU를 하나로

정적 슬롯 모델은 이렇게 생겼죠.

remaining_capacity = max_sessions - active_sessions

100개 슬롯에 80개 차 있으면 20개 남는다? 하지만 모든 세션이 같은 CPU를 쓴다는 가정은 생성형 AI에서 거의 틀립니다. 그래서 정규화된 하이브리드 모델이 필요해요.

로드밸런서는 rate 단위로 사고하기 때문에, 정적 세션 수를 rate로 변환합니다. 예를 들어 10초 리포팅 윈도우에 활성 세션 90개면 → **'가짜 QPS 9'**로 취급하는 식이죠.

Cost_Per_Session = (Current_Utilization - Target_Utilization) / Active_Sessions
Additional_Session_Rate = (Target_Utilization - Current_Utilization) / Cost_Per_Session * Safety_Scaler
Effective_Capacity = Active_Sessions + (Additional_Session_Rate * Reporting_Interval)

이 공식이 실무에서 어떻게 작동하냐면:

  • 세션 10개 + CPU 90% → Cost_Per_Session이 매우 높아져 Additional_Session_Rate가 0. 새 트래픽을 아예 안 받아요.
  • 세션 80개 + CPU 40% → 여유가 있어 보이지만 Safety_Scaler가 점진적으로만 새 세션을 흘려보냅니다. 급격한 스파이크 방지용이에요.

핵심은 현재 상태의 무게와 커밋된 세션의 양을 동시에 이해하는 것입니다.

Network diagram showing bidirectional streaming sessions between AI agents and load balancer Development Concept Image

벤치마크와 카운터 설계, 여기서 다들 삽질합니다

fire-and-forget 부하 테스트는 함정입니다

짧은 요청을 마구 던지는 부하 테스트는 처리량만 측정할 뿐, 장기 세션의 실패 모드를 재현하지 못해요. 벤치마크는 반드시 다양화해야 합니다.

  • 짧은 요청 vs 장기 세션 혼합
  • 세션 수명 분포 (p50, p95, p99)
  • 강제 연결 끊김 시나리오
  • 동시 인터럽트 폭주

측정해야 할 스트리밍 지표

평균 레이턴시와 QPS를 넘어서:

  • 백엔드별 활성 세션 분포
  • 과부하 할당률(overloaded assignment rate)
  • p95/p99 시작 레이턴시
  • time-to-first-stream (첫 스트림까지 걸린 시간)
  • 드롭된 세션 수
  • 강제 연결 끊김 후 카운터 동작

카운터 자체가 병목이 될 수 있습니다

모든 스트림 시작/종료가 카운터를 때립니다. 대규모 동시성에서는 이 트래커가 서비스 병목이 되지 않도록 설계해야 해요.

JVM이라면 AtomicInteger가 대부분의 워크로드에서 충분하지만, **고동시성에서는 캐시 라인 컨텐션(cache-line bouncing)**으로 처리량이 급락할 수 있습니다. 여러 스레드가 같은 메모리 주소를 계속 업데이트하니까요. 이럴 땐 sharded counterLongAdder 스타일 집계가 답입니다.

마이크로벤치마크를 할 때는 JMH 같은 프레임워크로 JIT 최적화, JVM 워밍업, dead-code elimination을 반드시 통제하세요. 단일 스레드 레이턴시보다 컨텐션 시나리오에 집중하는 게 실무적으로 훨씬 유용합니다.

이 기술의 한계와 주의사항

  • 네트워크 파티션: 로드밸런서가 백엔드 메트릭을 못 받으면? 세션 카운트가 stale해지고 잘못된 라우팅이 발생합니다. 폴백 전략(예: 마지막 스냅샷 + TTL)을 반드시 설계하세요.
  • 프록시별 편차: Envoy, NGINX, HAProxy의 메트릭 폴링 주기와 집계 방식이 다릅니다. 특정 프록시에 종속된 튜닝은 이식성이 떨어져요.
  • 오버 엔지니어링 경계: 세션 수가 수백 개 수준이면 굳이 하이브리드 모델까지 갈 필요 없습니다. QPS + CPU로 충분해요. 이 전략은 수천 동시 세션부터 진가를 발휘합니다.

한국 개발 생태계에서의 적용 맥락

국내에서 실시간 AI 음성/영상 에이전트를 만드는 팀은 대부분 스타트업이거나 SI 프로젝트의 PoC 단계입니다. 문제는 국내 클라우드 인프라에서 gRPC 양방향 스트림 유지가 생각보다 까다롭다는 점이에요. 특히 L7 로드밸런서를 쓰는 환경에서는 HTTP/2 스트림이 idle timeout으로 끊기는 경우가 많아서, 세션 카운터 이전에 연결 유지 자체가 먼저 깨지는 상황을 자주 봅니다.

또 하나, 국내 SI 환경에서는 '세션 수'를 운영팀이 이해할 수 있는 지표로 번역해야 합니다. '활성 세션 3,000개'보다 '동시 통화 3,000건'이 훨씬 설득력 있어요. 대시보드 설계 단계부터 이 번역을 염두에 두세요.

다음 단계 학습 방향

  1. Envoy의 least_request vs 커스텀 세션 기반 정책 비교 실험
  2. OpenTelemetry로 세션 라이프사이클 트레이싱 붙이기
  3. LongAdder vs sharded counter 실제 부하 벤치마크
  4. Kubernetes HPA에 커스텀 메트릭(active_sessions) 연동해서 오토스케일링 트리거 만들기

실시간 AI 인프라는 '요청 밸런싱'에서 '살아있는 대화 밸런싱'으로 넘어가고 있습니다. QPS와 CPU는 이제 시작일 뿐이에요.

AI chat interface representing real-time voice agent conversation with session-aware routing Developer Related Image

정리: 세 가지 신호를 함께 보세요

  • QPS: 도착량
  • CPU: 현재 압력
  • 활성 세션 수: 커밋된 진짜 동시성

이 세 가지를 하나의 모델로 합치는 순간, 로드밸런서는 훨씬 똑똑해집니다. 특정 인스턴스가 병목이 되는 걸 막고, 유휴 서버에 트래픽을 자연스럽게 분산시킬 수 있어요.

실시간 AI 에이전트가 프로덕션으로 들어오는 지금, 인프라도 '이산적 요청'에서 '연속적 대화'로 사고를 전환해야 합니다.

함께 보면 좋은 글

본 콘텐츠는 신뢰할 수 있는 출처를 바탕으로 AI 도구를 활용하여 초안이 작성되었으며, 편집자의 검토를 거쳐 발행되었습니다. 전문가의 조언을 대체하지 않습니다.