사이오닉AI · 2024.09 — now

부하 증가에 따른 정산 지연 개선

  • Rate Limit + batch 주기, API Key cache TTL 조절로 마이너스 잔액 발생 구간 최소화
  • 최소의 개발로 안정성 확보 후 아키텍처 재설계로 전환

이슈 상황

기 구현된 비용 제어 로직은 의도적으로 단순하게 설계되었습니다.

  • Data Plane은 로컬 cache된 API Key로 AI provider를 호출하고 결과를 반환합니다
  • Control Plane은 호출 결과로 과금 처리를 하고, balance가 0 이하가 되면 API Key를 SUSPENDED 합니다
  • 이때 Data Plane에 cache된 API Key는 상태가 갱신되지 않아 TTL 동안 시차가 발생해 과금 누수가 발생할 수 있었습니다

이런 문제가 있는 와중에 사내 트래픽이 집중되고, 자체 모델 서빙으로 유입이 늘면서 처리량을 초과하는 트래픽이 유입되 비용 제어의 한계가 드러났습니다.

---
config:
  theme: base
  darkMode: false
  themeVariables:
    background: "#ffffff"
    primaryColor: "#ffffff"
    primaryTextColor: "#111827"
    primaryBorderColor: "#475569"
    lineColor: "#334155"
    edgeLabelBackground: "#ffffff"
---
flowchart LR
  Client["Client"] -->|"LLM 호출"| B["API Key 인증<br/>ACTIVE cache"]
  B --> C["AI provider 호출"]
  C -->|"호출 기록 전송"| G["과금 처리"]

  subgraph DP["Data Plane"]
    B
    C
  end

  subgraph CP["Control Plane"]
    direction TB
    G["과금 처리"]
    G --> H["balance ≤ 0<br/>key SUSPENDED"]
  end

분석

고려한 방식효과비용
요청 전 budget reservation마이너스 잔액 원천 차단매 요청마다 CP DB 조회 → 처리량 급감
key SUSPENDED 시 즉시 evictcache 시차 제거CP→다중 DP fan-out, 복잡도 증가
✅ Rate Limit 1요청 단위 과금 제한구현 단순, 정밀 제어 어려움
Rate Limit 2토큰 사용량 기반 과금 제한구현 복잡도 높음, 내부용·솔로 파운더 대상이라 우선순위 낮음
✅ 마이너스 잔액 허용처리량 유지, 구조 단순과금 누수 발생하지만, 고처리량 게이트웨이에서 흔히 채택되는 절충안이며 허용 손실로 정의
✅ batch 처리량 확대batch 주기 축소batch 부하 증가
✅ cache TTL 단축cache 시차 축소API Key 조회량 증가하지만, 분당 고유 active key 수에 비례하므로 측정 후 수용 가능

초기에는 대상이 제한된 서비스였기에 비용 제어의 수준이 낮았지만, 플랫폼 안정성이 검증되고 자체 모델 서빙 등의 니즈가 들어오면서 목표가 150만 RPM으로 크게 상향되었습니다.
따라서 이번 이슈는 최소한의 개발과 모니터링 확충으로 정리하고, 아키텍처 재설계에 빠르게 들어가기로 했습니다.

개선

최소의 추가 개발과 설정 수정만으로도 15,000 RPM까지 문제 없음을 실측으로 확인하고, 빠르게 다음 단계로 넘어갈 수 있었습니다.

Phase 1: batch 처리량 확대

항목BeforeAfter
chunk size1,0005,000
batch count13
처리량1,000건15,000건

하지만 적용 후 테스트 과정에서 청크당 처리 시간이 너무 오래 걸리는 것을 발견했습니다. (5,000건, 11초)

Phase 2: JFR 분석 기반 과금 배치 쿼리 개선

분석 결과 status-update-execute가 5,000건당 평균 2,845ms로 핵심 병목이었으며, 5,000개 복합 키를 거대한 OR 조건으로 만든 UPDATE가 원인이었습니다.

JFR — Before: status-update-execute 2,845ms

JFR — After: status-update-execute 99ms

항목BeforeAfter
쿼리 형태row 단위 다중 UPDATEUPDATE ... FROM (VALUES ...) 단일 쿼리
status-update-execute2,845ms99ms
5,000건 처리 시간11초1.65초

빌링 실행시간 — Before: 5,000건 처리 시 11초

빌링 실행시간 — After: 5,000건 처리 시 1.65초

Phase 3: 페이지네이션 청크 병렬 처리 — 현재 수준에서 불필요하여 의도적 미진행

Phase 2에서 처리하는 청크를 병렬로 전환하면 처리 시간을 더 낮출 수 있습니다.
하지만 5,000건 처리 1.65초, batch 1분 주기 + 병렬 3배치로 15,000 RPM을 이미 수용 가능했고, 빠르게 다음 아키텍처로 넘어가기 위해 보류하였습니다.