콘텐츠로 이동

로드 밸런싱 가이드 (Load Balancing Guide)

Continuum Router는 요청을 라우팅할 때 먼저 전체 백엔드 풀에서 적격 후보 집합을 추려냅니다(활성화되어 있고, 헬스 체크가 정상이며, 호출자 API 키에 허용되고, 서킷 브레이커나 재시도 제외 상태가 아니며, 요청된 모델을 서빙할 수 있는 백엔드). 그 다음 등록된 스코어러(예: KV 캐시 오버랩 스코어러)가 설정되어 있으면 이를 먼저 평가하고, 해당하지 않으면 최상위 selection_strategy를 적용합니다.

기본 설정

selection_strategy: RoundRobin

backends:
  - name: gpu-a
    url: http://gpu-a:8000
    weight: 3
    models: [llama-3.3-70b]
  - name: gpu-b
    url: http://gpu-b:8000
    weight: 1
    models: [llama-3.3-70b]

백엔드 선택 동작은 최상위 selection_strategy, backends[].weight, backends[].models, smart_routing, prefix_routing, 그리고 routing.engine_load로 제어합니다. (참고: routing: 블록 아래의 레거시 routing.strategy 키는 런타임에서 사용되지 않으므로, 전략 지정은 항상 최상위 selection_strategy를 사용하십시오.)

CLI 및 환경 변수 오버라이드

기동 시점에 CLI 플래그나 환경 변수로 설정 파일의 선택 전략을 덮어쓸 수 있습니다.

# CLI 플래그로 지정
continuum-router --config config.yaml --selection-strategy LeastLatency

# 환경 변수로 지정 (대소문자 구분 없음, PascalCase / snake_case / kebab-case 모두 허용)
export CONTINUUM_SELECTION_STRATEGY=LeastLatency
continuum-router --config config.yaml

적용 우선순위 (높은 순서):

  1. --selection-strategy CLI 플래그
  2. CONTINUUM_SELECTION_STRATEGY 환경 변수
  3. 설정 파일(config.yaml)의 selection_strategy
  4. 내장 기본값 (RoundRobin)

CONTINUUM_SELECTION_STRATEGY에 잘못된 전략 이름을 지정하면 기본값으로 무시하고 넘어가지 않고 명확한 오류와 함께 기동이 중단됩니다.

선택 전략 (Strategies)

RoundRobin (기본값)

selection_strategy: RoundRobin

원자적(atomic) 카운터를 사용해 적격 백엔드 목록을 순서대로 순회합니다. 사양이 비슷한 무상태(stateless) 백엔드 환경에 적합하며, 백엔드 가중치나 응답 지연 시간은 고려하지 않습니다.

WeightedRoundRobin

selection_strategy: WeightedRoundRobin

backends:
  - name: large
    url: http://large:8000
    weight: 3
  - name: small
    url: http://small:8000
    weight: 1

적격 백엔드들의 양수 가중치(weight) 비율에 비례하여 요청을 분배합니다. 고정된 3, 3, 3, 1 순서를 기계적으로 반복하는 대신 요청마다 가중치 확률에 따라 대상을 샘플링합니다. 모든 적격 백엔드의 가중치가 0이면 일반 RoundRobin으로 동작합니다.

LeastLatency

selection_strategy: LeastLatency

관측된 평균 응답 시간이 가장 짧은 적격 백엔드를 선택합니다. 아직 응답 시간 측정값이 없는 후보는 첫 측정값이 쌓일 때까지 라운드 로빈 순서로 선택됩니다. 이 전략은 순수하게 관측된 지연 시간만 비교하므로 요청의 입력 길이나 복잡도를 예측하지 않으며, 특정 백엔드로 트래픽이 쏠릴 수 있습니다.

이 전략은 유도할 뿐 제거하지 않습니다. 일관되게 느린 백엔드도 적격인 한 자기 몫의 트래픽을 계속 받습니다. 이러한 백엔드를 라우팅에서 빼려면 circuit_breaker.latency_as_failure를 활성화하십시오. ttft_ms(스트리밍) 또는 total_ms(버퍼링)보다 느린 성공을 실패로 집계하므로 서킷이 열리고 이후 프로브를 통해 복귀합니다.

Random

selection_strategy: Random

요청마다 적격 백엔드 중 하나를 균등 확률로 무작위 선택합니다. 장기적으로는 고르게 분산되지만 짧은 구간에서는 편차가 발생할 수 있습니다.

ConsistentHash

selection_strategy: ConsistentHash

해석된 모델 이름을 일관 해시 링(consistent-hash ring)에 매핑하여, 동일한 모델과 후보 집합에 대한 요청을 항상 같은 백엔드로 보냅니다. 이 방식은 사용자나 세션 단위가 아닌 **모델 단위 친화성(model affinity)**을 제공하므로, 소수의 인기 모델이 전체 트래픽의 대부분을 차지하는 환경에서는 특정 백엔드에 부하가 집중될 수 있습니다.

PrefixAwareHash

selection_strategy: PrefixAwareHash

prefix_routing:
  enabled: true
  max_prefix_length: 1024
  load_factor_epsilon: 0.25

부하 제한 일관 해싱(CHWBL, Consistent Hashing with Bounded Loads)을 사용해 동일한 프롬프트 접두사(prefix)를 공유하는 요청을 같은 백엔드로 라우팅합니다.

  • 접두사 키 추출: 채팅(/v1/chat/completions), /v1/responses, /anthropic/v1/messages 요청에서 모델 이름 + 시스템 프롬프트(시스템 프롬프트가 없으면 모델 이름 + 첫 번째 사용자 메시지)로 SHA-256 해시 키를 생성합니다.
  • 부하 상한(Bounded-load) 오버플로: 선호 백엔드의 현재 처리 중(in-flight) 요청 수가 아래 상한에 도달하면 해시 링의 다음 백엔드로 트래픽을 넘깁니다.

    ceil((total_in_flight + 1) * (1 + epsilon) / backend_count)
    

    load_factor_epsilon 값을 낮추면 부하 균등 분산을 우선하고, 값을 높이면 접두사 캐시 친화성을 더 강하게 유지합니다. - 필수 조건 (prefix_routing.enabled: true): 요청에서 접두사 키를 추출할 수 없거나 prefix_routing.enabled: false인 경우에는 모델 이름 기반 일관 해싱으로 폴백합니다. 단, 이 폴백 경로에는 부하 상한이 적용되지 않아 해당 모델의 모든 요청이 단일 백엔드로 몰리게 됩니다. 따라서 PrefixAwareHash를 쓸 때는 반드시 prefix_routing.enabled: true를 함께 켜야 합니다(꺼져 있으면 config validate와 기동 로그에서 경고를 출력합니다). - 해시 링 크기 및 Anthropic 캐시 제어: - prefix_routing.virtual_nodes (기본값: 백엔드당 150개 복제본)는 일관 해시 링의 크기를 결정하며, 핫 리로드 시 실행 중인 풀의 링을 즉시 재구성합니다. - prefix_routing.anthropic_cache_control_injection (기본값: false)은 플릿 전체 마스터 스위치로, 개별 백엔드에 anthropic_auto_cache_control: false가 설정되어 있더라도 적격 네이티브 Anthropic 요청에 자동 cache_control 주입을 강제합니다(클라이언트가 직접 지정한 cache_control 블록은 그대로 보존).

EngineLoad

selection_strategy: EngineLoad

engine_stats:
  enabled: true

routing:
  engine_load:
    balance_abs_threshold: 64
    balance_rel_threshold: 1.5
    base_strategy: RoundRobin

엔진 부하 인식 선택의 스코어러 항과 동일한 엔진 통계 메트릭(waiting_requests 및 kv_cache_usage)을 읽어, 현재 엔진 부하가 가장 낮은 후보 백엔드로 라우팅합니다.

KV 오버랩 스코어러의 엔진 부하 항은 KV 캐시 인덱스가 켜진 상태에서 접두사 캐시를 보유한 백엔드들만 비교하는 반면, selection_strategy: EngineLoad는 prefix_routing이나 KV 인덱스 없이도 전체 적격 후보 집합을 대상으로 엔진 부하 순위를 매깁니다.

각 선택 패스는 다음 세 가지 결과 중 하나로 결정되며 routing_engine_load_decisions_total{backend,reason} 메트릭에 기록됩니다.

  • 엔진 부하 순위 적용 (reason="strategy_engine_load"): 모든 적격 후보가 최신(fresh) 스냅샷을 보유하고 있고, 후보 간 waiting_requests 격차가 balance_abs_threshold와 balance_rel_threshold를 모두 초과한 경우입니다. 부하가 가장 낮은 백엔드가 선택됩니다.
  • 오래된 통계 폴백 (reason="strategy_stale_fallback"): 적격 후보 중 하나라도 최신 스냅샷이 없는 경우로, routing.engine_load.base_strategy가 대신 백엔드를 선택합니다. 자체 호스팅 엔진과 외부 호스팅 API가 혼합된 환경에서 일부 백엔드만 통계를 보고할 때 트래픽이 한쪽으로 쏠리거나 특정 백엔드가 기아(starvation) 상태에 빠지는 것을 막기 위해, 모든 후보의 최신 스냅샷이 있을 때만 순위를 매깁니다.
  • 히스테리시스 유지 (reason="strategy_hysteresis_hold"): 모든 후보의 스냅샷이 최신이지만 큐 길이 격차가 데드밴드(balance_abs_threshold / balance_rel_threshold) 이내인 경우로, base_strategy가 선택을 담당합니다. 엔진 스냅샷은 engine_stats.interval마다 한 번씩만 갱신되므로, 부하 차이가 작을 때는 base_strategy(기본값 RoundRobin, 이기종 서버 환경에서는 가중치를 반영하는 WeightedRoundRobin 권장)로 분산하여 한 폴링 주기 동안 단일 백엔드로 요청이 쏠리는 현상(herding)을 방지합니다.

selection_strategy: EngineLoad 설정 시 참고 사항:

  • selection_strategy: EngineLoad를 지정하는 것만으로 활성화되며, 스코어러 전용 스위치인 routing.engine_load.enabled는 읽지 않습니다.
  • 엔진 통계 수집을 위해 engine_stats.enabled: true가 필수입니다(비활성 상태면 config validate가 경고를 출력).
  • routing.engine_load.base_strategy에는 EngineLoad를 다시 지정할 수 없습니다.
  • 스냅샷 유효 기간(engine_stats.interval * routing.engine_load.max_staleness_intervals)과 포화 상태 유입 차단(routing.engine_load.admission) 규칙은 아래 스코어러 항과 동일하게 적용됩니다.

LeastBusy

selection_strategy: LeastBusy

풀의 백엔드별 처리 중(in-flight) 요청 카운터를 읽어, 라우터가 자체적으로 처리 중인 요청 수가 가장 적은 적격 백엔드를 선택합니다. 이 카운터는 모든 디스패치 경로에서 갱신되므로 엔진 통계 없이 어떤 백엔드 타입에도 동작하며, 외부 호스팅 API 백엔드도 자체 호스팅 엔진과 동일하게 순위가 매겨집니다.

동률인 경우(모든 백엔드가 유휴 상태인 경우 포함)에는 풀 카운터로 순회하여, 부하가 같은 후보들이 트래픽을 고르게 나눕니다. 한 번도 라우팅된 적이 없는 후보는 유휴 상태로 취급됩니다.

긴 스트리밍 요청은 스트림이 끝날 때까지 해당 백엔드를 점유하므로, 이 전략은 느린 스트림이 몰리는 구간에서 풀 전체로 분산시킵니다. LeastLatency는 과거에 응답이 빨랐던 백엔드로 몰리는 경향이 있습니다.

LeastCost

selection_strategy: LeastCost

backends:
  - name: provider-endpoint
    url: https://api.provider.example/v1
    models: [llama-3.3-70b]
    model_configs:
      - id: llama-3.3-70b
        metadata:
          pricing:
            input_tokens: 2.50
            output_tokens: 10.00
  - name: self-hosted
    url: http://gpu-a:8000
    models: [llama-3.3-70b]
    model_configs:
      - id: llama-3.3-70b
        metadata:
          pricing:
            input_tokens: 0.20
            output_tokens: 0.60

요청의 예상 비용이 가장 낮은 적격 백엔드를 선택합니다. 비용은 입력 추정 토큰 x 입력 단가 + max_tokens x 출력 단가로 계산하며, 모델별 PricingInfo(1M 토큰당 USD)를 사용합니다.

가격 조회 순서. 백엔드의 model_configs[].metadata.pricing을 먼저 보고, 없으면 전역 모델 메타데이터 카탈로그를 봅니다. 제공자가 선언한 가격이 카탈로그 값보다 우선하므로, 같은 모델의 복제본은 각자 가격을 선언할 때만 가격이 달라집니다. 메타데이터 블록은 있지만 가격이 없는 백엔드 항목은 카탈로그를 의도적으로 가리므로, 공유 단가를 상속하지 않고 미가격 후보로 취급합니다. 메타데이터 블록 자체가 없는 항목은 카탈로그 가격으로 넘어갑니다.

미가격 후보. 모델에 가격 메타데이터가 없는 후보는 무료가 아니라 항상 마지막 순위로 밀립니다. 선택 패스의 어떤 후보에도 가격이 없으면 전략은 RoundRobin과 똑같이 동작하므로, 미가격 백엔드로 트래픽이 조용히 고정되는 일은 없습니다.

동률 처리. 예상 비용이 같으면 출력 단가로 순서를 정하고, 그다음 풀 카운터로 순회합니다. 따라서 가격이 같은 복제본도 첫 번째 후보에 고정되지 않고 트래픽을 나눕니다.

요청 크기 산정. 요청 본문을 이미 파싱한 디스패치 경로는 토크나이저 없이 계산한 입력 추정치와 요청의 출력 상한으로 비용을 계산합니다. 출력 상한은 채팅의 max_tokens 또는 max_completion_tokens, Responses의 max_output_tokens, Anthropic Messages의 max_tokens입니다.

본문 없이 선택에 도달하는 경로는 입력 100만 토큰, 출력 0이라는 단가 기준을 사용하므로 입력 단가로 순위를 정하고 출력 단가로 동률을 깨뜨립니다. 이 기준은 일반적인 장문 맥락 임계값을 넘으므로, 해당 경로에서는 모델이 장문 맥락 가격 구간을 선언했다면 그 구간의 입력 단가가 적용됩니다. 오디오, 토크나이즈, 실시간(realtime), ACP 선택은 이 방식으로 공유 선택 지점에 도달하므로, 요청의 실제 프롬프트 크기가 아니라 이 기준으로 순위를 매깁니다.

검증. config validate는 둘 이상의 활성 백엔드가 서빙하는 모델에 가격이 책정된 복제본이 둘 미만이면 selection_strategy 경로에 경고를 출력합니다. 이 경우 복제본을 가격으로 구분할 수 없기 때문입니다. models 또는 model_configs에 선언된 모델만 검사하며, 디스커버리만 사용하는 배포는 설정이 확인할 수 없는 조건으로 경고하지 않습니다.

다른 기능과의 조합. 스마트 라우팅 티어는 비용 순위보다 먼저 후보 집합을 좁힙니다. 허브 지정 가격 차익 라우팅(src/control_plane/arbitrage.rs)은 로컬 선택 전에 요청을 허브 동등 클래스에서 가장 저렴한 정상 멤버로 바꾸므로, 둘이 함께 적용되면 차익 라우팅이 우선합니다.

스코어러 우선순위 (Scorer Precedence)

BackendPool은 selection_strategy를 실행하기 전에 등록된 스코어러를 먼저 평가합니다. 예를 들어 KV 캐시 인덱스가 활성화되어 있고 특정 백엔드의 접두사 캐시 겹침(overlap) 점수가 임계값 이상이면 해당 백엔드를 우선 선택하고, 그렇지 않을 때만 selection_strategy로 넘어갑니다. 따라서 스코어러가 활성화된 환경에서는 실제 요청 분포가 단순 라운드 로빈이나 가중치 비율과 달라질 수 있습니다.

스코어러 설정 및 임계값에 대한 자세한 내용은 KV 캐시 아키텍처를 참고하십시오.

엔진 부하 인식 선택

routing.engine_load.enabled: true를 설정하면 엔진 통계 폴러가 수집한 데이터를 바탕으로 KV 오버랩 스코어러에 엔진 부하 가산 항이 추가됩니다. 요청 접두사 캐시를 보유한 백엔드들 중에서 엔진이 보고한 대기 요청 수(waiting_requests)가 적고 kv_cache_usage가 낮은 후보에 가점을 부여합니다. 라우터 내부의 in-flight 카운터만으로는 알 수 없는 추론 엔진 내부 큐 적체나 KV 캐시 선점(preemption) 직전 상태를 반영할 수 있습니다.

비교: EngineLoad 선택 전략 vs. KV 스코어러 엔진 부하 항

구분 selection_strategy: EngineLoad routing.engine_load.enabled: true (KV 스코어러 항)
평가 대상 범위 요청을 처리할 수 있는 모든 적격 백엔드 min_overlap_threshold를 통과한 접두사 캐시 보유 백엔드
필수 선행 설정 engine_stats.enabled: true engine_stats.enabled: true, prefix_routing.enabled: true, kv_cache_index 활성화
스냅샷 누락 시 동작 후보 중 하나라도 최신 스냅샷이 없으면 패스 전체가 base_strategy로 폴백 스냅샷이 오래된 후보만 기본 점수로 돌아가며, 모든 후보가 오래되었을 때만 기본 스코어링과 동일하게 동작
reason 메트릭 레이블 strategy_engine_load, strategy_stale_fallback, strategy_hysteresis_hold engine_load, stale_fallback, hysteresis_hold

가산 항의 동작 조건 및 안전 장치

  • 큐 길이 정규화: 최신 스냅샷을 가진 모든 후보가 total_slots를 보고하면 total_slots로 나누어 정규화하고, 하나라도 total_slots가 없으면 현재 후보 집합의 최대 waiting_requests를 기준으로 정규화합니다(vLLM과 llama.cpp가 섞인 풀에서도 순위가 왜곡되지 않도록 보장).
  • 스냅샷 유효 기간 (max_staleness_intervals): engine_stats.interval * max_staleness_intervals(기본값 3 주기)보다 오래된 스냅샷은 무시하며, 해당 후보는 기본 오버랩 점수를 유지합니다.
  • 히스테리시스 데드밴드 (balance_abs_threshold, balance_rel_threshold): 후보 간 최신 waiting_requests 편차가 절대 임계값(balance_abs_threshold, 기본값 64)과 상대 임계값(balance_rel_threshold, 기본값 1.5)을 모두 넘을 때만 엔진 부하 항이 반영됩니다. 데드밴드 개방 여부는 오직 waiting_requests 편차로만 판정하므로, 대기 큐는 비어 있거나 비슷한데 kv_cache_usage만 차이나는 경우에는 엔진 부하 순위가 개입하지 않습니다(일단 게이트가 열리면 kv_cache_usage도 점수 계산에 반영됨).
  • 자가 보고 수치에 대한 신뢰 한도 (Trust model): waiting_requests와 kv_cache_usage(수집 시 [0.0, 1.0] 범위로 클램프)는 각 백엔드 엔진이 스스로 보고하는 값이므로, 오작동하거나 손상된 백엔드가 부하를 낮게 보고하더라도 다음 세 가지 제약으로 영향력이 제한됩니다.
    1. 이미 min_overlap_threshold 이상의 접두사 캐시를 보유한 후보에만 적용되므로, 관련 캐시가 없는 백엔드는 선택 대상에 끼어들 수 없습니다.
    2. 가산 가중치 상한이 engine_load_weight(기본값 0.3, 기본 가중치 합 1.0에 추가)로 제한되어 있어, 자가 보고 수치만으로는 큰 폭의 캐시 오버랩·부하·헬스 점수 차이를 뒤집을 수 없습니다.
    3. 큐 길이 차이가 데드밴드 안에 머무는 동안에는 가산 항이 0이 됩니다.

포화 상태 유입 차단 (routing.engine_load.admission)

routing.engine_load.admission.enabled: true를 설정하면 모든 선택 전략에 앞서 공통 선택 지점에서 KV 캐시 포화 여부를 검사합니다.

  • 차단 조건: 요청의 모든 정상 후보 백엔드가 최신 스냅샷에서 admission.kv_usage_threshold(기본값 0.98)를 초과하는 kv_cache_usage를 보고하면, 곧 선점(preemption)이 발생할 엔진에 요청을 쌓는 대신 선택을 거부합니다(reason="admission_reject").
  • 폴백 체인 연동: 채팅 경로(/v1/chat/completions)에서 이 거부는 재시도 가능한 HTTP 503을 반환하며, BackendUnhealthy 트리거를 통해 fallback.fallback_chains의 다음 모델로 자동 폴백됩니다.
  • 단일 백엔드 오작동 방어: 최신 스냅샷이 없는 후보는 포화되지 않은 것으로 간주하여 통과시키므로, 특정 엔진 하나가 잘못된 포화 수치를 보고하더라도 그 엔진이 유일한 후보인 요청 외에는 전체 트래픽을 차단할 수 없습니다.

사용량 가중 선택 (routing.usage_weight)

routing.usage_weight는 제공자가 선언한 토큰·요청 쿼터 중 라우터가 아직 쓰지 않은 몫으로 후보 순위를 매깁니다. selection_strategy 값이 아니라 가산 스코어링 항이므로, 모든 전략과 KV 오버랩·엔진 부하 항에 함께 적용됩니다.

백엔드마다 제공자 한도를 선언한 뒤 항을 켭니다.

backends:
  - name: provider-a
    url: https://api.provider-a.example/v1
    provider_tpm: 200000
    provider_rpm: 500
  - name: provider-b
    url: https://api.provider-b.example/v1
    provider_tpm: 40000

routing:
  usage_weight:
    enabled: true
    weight: 0.2
    staleness_interval: 60s

선언은 제공자가 허용하는 양을 뜻할 뿐 아무것도 강제하지 않습니다. provider_tpm을 넘긴 백엔드도 제공자가 거부할 때까지 계속 디스패치됩니다. 강제가 필요하면 rate_limiting이나 키별 토큰 예산을 사용합니다.

순위 결정 규칙

  • 선언한 후보: weight x 남은 비율을 받습니다. 남은 비율은 (설정값 - 소비량) / 설정값을 0.0..=1.0으로 자른 값입니다. 두 축을 모두 선언한 후보는 소진에 더 가까운 축이 기준이 됩니다.
  • 선언하지 않은 후보: 정확히 0.0을 받아 기존 점수를 그대로 유지합니다. 알 수 없는 쿼터를 무제한으로도, 잔량 0으로도 취급하지 않습니다.
  • 소진한 후보: 남은 예산이 없는 선언 후보도 0.0이며, 선언하지 않은 후보와 같습니다. 중립 아래로 밀리지 않습니다.
  • 엄격한 우위: 항의 최대 기여가 0보다 크고, 그 후보의 합산 점수가 다른 모든 후보보다 엄격히 클 때만 항이 백엔드를 정합니다. 동점은 selection_strategy로 되돌리므로, 예산이 같은 복제본 두 개는 flapping 없이 계속 번갈아 선택됩니다.

weight: 0.2인 항은 단독으로 스코어러 문턱(0.3)을 넘을 수 없습니다. 그래도 KV나 엔진 부하 항이 문턱 아래의 약한 우위만 보고할 때는 이 항이 근접 동점을 남은 예산이 많은 후보 쪽으로 기울입니다.

데이터 소스와 윈도

  • 소비량은 라우터 자체 토큰 계량에서 백엔드별로 집계하며, staleness_interval(1초에서 1시간, 기본값 60s) 길이의 tumbling window를 씁니다. 윈도는 라우터 재시작 뒤에도 0에서 다시 시작하므로, 이미 소진된 백엔드가 재시작 직후 첫 윈도 동안 선호될 수 있습니다.
  • RPM 축은 사용량을 보고한 정상 종료 attempt 수를 셉니다. 사용량을 보고하기 전에 실패한 attempt는 세지 않으므로, 제공자의 토큰 버킷이 아니라 요청량의 근사치입니다.
  • 제공자 응답 헤더와 엔진 통계는 같은 설계의 이후 단계이며, 아직 읽지 않습니다.

운영 참고

  • 메트릭: 항이 결정하거나 판정하지 못한 패스마다 routing_usage_weight_decisions_total{backend,reason}을 기록하며, reason은 applied, no_declaration, no_differentiation 중 하나입니다(메트릭 참고). KV 오버랩이나 엔진 부하 항만으로 선택이 이미 정해진 패스는 KV 메트릭에 남고 이 카운터에는 포함되지 않습니다.
  • 검증: weight는 유한한 0.0..=1.0, staleness_interval은 1초 이상 1시간 이하의 duration, provider_tpm/provider_rpm은 1 이상이어야 합니다. 항이 켜져 있는데 한도를 선언한 활성 백엔드가 없으면, 그리고 항이 꺼져 있는데 한도를 선언한 백엔드가 있으면 config validate가 경고합니다.
  • 리로드: routing.usage_weight.*와 쿼터 선언은 다음 리로드 게시 시점에 적용되며, enabled가 바뀌면 항을 등록하거나 제거합니다.
  • 기본값: 블록이 없거나 꺼져 있으면 prefix key 없는 패스에서 어떤 스코어러도 실행되지 않고, 선택 동작은 이 섹션이 없는 빌드와 동일합니다.

후보 적격성 필터링 및 헬스 체크

모든 선택 전략은 언제나 필터링을 통과한 적격 후보 집합 안에서만 백엔드를 선택합니다. 후보 필터링 단계에서는 다음 항목들을 검사합니다.

  • 모델 가용성 및 별칭(alias) 해석
  • 백엔드 헬스 체크 상태
  • 서킷 브레이커 개방(open) 및 재시도 제외 대상 여부
  • API 키별 allowed_backends 및 모델 가시성 권한
  • 내부 전용 백엔드(internal: true) 격리 규칙
  • AppProxy ROUTER 모드 활성화 시 공개 모델 제한

백그라운드 헬스 체크는 전역 health_checks 섹션에서 설정합니다.

health_checks:
  enabled: true
  interval: 30s
  timeout: 5s
  unhealthy_threshold: 3
  healthy_threshold: 2

전략 핫 리로드 및 설정 검증

selection_strategy와 prefix_routing.load_factor_epsilon(CHWBL 부하 계수)은 모두 무중단 핫 리로드를 지원합니다. 설정 파일을 수정하면 백엔드 멤버십, 누적 통계, 처리 중(in-flight) 요청 카운터를 그대로 유지한 채 다음 요청부터 즉시 새 선택 알고리즘과 부하 계수가 적용됩니다.

변경 사항을 운영에 반영하기 전에 다음 명령으로 설정 파일을 사전 검증할 수 있습니다.

continuum-router config validate /etc/continuum-router/config.yaml

모니터링

Admin API를 통해 백엔드 상태와 접두사 라우팅 분포를 조회할 수 있습니다.

curl -H "Authorization: Bearer $ADMIN_TOKEN" \
  http://localhost:8080/admin/backends

curl -H "Authorization: Bearer $ADMIN_TOKEN" \
  http://localhost:8080/admin/prefix-routing/stats

Prometheus 메트릭이 활성화된 빌드에서는 접두사 라우팅과 관련해 다음 시리즈가 노출됩니다.

  • continuum_prefix_routing_requests_total{strategy=...}
  • continuum_prefix_routing_backend_distribution{backend=...}
  • continuum_prefix_routing_prefix_cardinality

전략 선택 요약

전략 적합한 환경 주요 고려 사항 (Trade-off)
RoundRobin 사양이 동일한 무상태 백엔드 풀 백엔드별 처리 용량 및 응답 지연 차이를 반영하지 않음
WeightedRoundRobin 백엔드별 상대 처리 용량 비율을 알고 있는 경우 가중치 수동 튜닝 필요, 확률적 분배
LeastLatency 실측 응답 속도가 가장 빠른 백엔드를 우선할 때 특정 백엔드로 트래픽이 쏠릴 수 있으며 사전 관측치 필요
Random 단순 무상태 무작위 분산 짧은 구간에서는 분배가 고르지 않을 수 있음
ConsistentHash 모델 단위 캐시 친화성이 중요할 때 소수 인기 모델에 트래픽이 몰리면 부하 불균형 발생
PrefixAwareHash 공통 시스템 프롬프트 및 분산 KV 캐시 재사용 극대화 prefix_routing.enabled: true 필수, load_factor_epsilon 튜닝 필요
EngineLoad 자체 호스팅 추론 엔진이 실시간 대기 큐·KV 통계를 노출할 때 engine_stats.enabled: true 및 모든 후보의 최신 리포트 필요 (미충족 시 base_strategy로 폴백)
LeastBusy 외부 호스팅 API가 섞인 이기종 백엔드 풀 라우터가 관측한 처리 중 요청 수만 사용하며, 긴 스트림은 끝날 때까지 해당 백엔드를 점유
LeastCost 유료 제공자 API와 같은 모델의 저렴한 복제본을 함께 운영할 때 각 복제본에 모델별 가격 메타데이터가 필요하며, 미가격 후보는 마지막 순위, 카탈로그 가격이 같은 복제본은 동률로 순회

routing.usage_weight는 이 표에 넣지 않습니다. 선택한 전략 위에 더해지는 가산 항이지 전략 값이 아니기 때문입니다. 사용량 가중 선택을 참고하십시오.

업스트림 관측 쿼터

업스트림 제공자는 응답마다 자체 쿼터 잔량을 알려 줍니다. 라우터 로컬 계량(routing.usage_weight, 위 절)은 선언한 provider_tpm/provider_rpm에서 라우터가 직접 계량한 소비량을 뺀 값으로 순위를 매기고, routing.quota_aware(#2318)가 그 위에 관측 절반을 더합니다. 429 직전인 업스트림은 클라이언트가 오류를 보기 전에 후순위로 밀립니다.

  1. 헤더 캡처: 제공자가 문서화한 축별 rate-limit 헤더를 파싱해 백엔드별 최신 스냅샷을 저장합니다. 파싱 대상은 Anthropic anthropic-ratelimit-requests-{limit,remaining}, anthropic-ratelimit-tokens-{limit,remaining}, anthropic-ratelimit-input-tokens-{limit,remaining}, anthropic-ratelimit-output-tokens-{limit,remaining}, Anthropic 인그레스가 전달하는 anthropic-ratelimit-unified-{requests,tokens}-* 별칭(is_forwardable_upstream_response_header, #1687), 그리고 OpenAI x-ratelimit-limit-requests / x-ratelimit-remaining-requests 와 x-ratelimit-limit-tokens / x-ratelimit-remaining-tokens 입니다.
  2. 정규화: 잔량 값과 대응하는 -limit 헤더가 모두 있고 한도가 0보다 클 때만 그 축을 순위에 씁니다. 비율은 remaining / limit을 0.0..=1.0으로 자른 값입니다. 분모가 없는 잔량 값은 Admin API에 노출하지만 순위에는 쓰지 않으므로, 지어낸 비율이 라우터 로컬 계량을 앞지르지 않습니다. 요청 축은 제공자의 요청 비율을, 토큰 축은 tokens, input-tokens, output-tokens 가운데 쓸 수 있는 가장 작은 비율을 씁니다.
  3. 선택: 신선한 관측은 축마다 라우터 로컬 추정을 대체하므로, 관측 데이터가 있는 백엔드는 provider_tpm / provider_rpm 선언 없이도 참여합니다. 스냅샷이 없거나 만료되면 선언한 추정으로 되돌아가고, 블록이 꺼져 있으면 선택은 이 섹션이 없는 빌드와 같습니다. 선택한 selection_strategy 위에 더해지는 가산 항으로 남으며 새로운 전략 값은 추가하지 않습니다.
  4. 가시성: GET /admin/stats/backends가 백엔드마다 engine_stats 옆에 quota 객체를 붙이고, GET /admin/backends/{name}/engine-stats도 함께 돌려줍니다. 관측 시각, 현재 TTL 대비 경과, 캡처한 축과 비율, 인식한 원본 헤더가 들어갑니다. 선택 입력은 routing_quota_aware_decisions_total{backend,reason}에 집계되며, reason은 observed_used, stale, absent, unusable 중 하나입니다.

캡처는 요청을 서비스하는 모든 업스트림 전송에서 일어납니다. 공용 HTTP 전송(OAuth 401 재시도 포함), Unix 소켓 전송, 스트리밍 핸들러, 네이티브와 변환 Anthropic 전송(Messages, 웹 검색, count_tokens, 토크나이저의 엔진 호출), Responses 전송(네이티브 스트리밍, 패스스루, 라이프사이클), responses-only 브리지와 섀도 시도, 오디오, 이미지 생성과 편집, Gemini 임베딩이 여기에 해당합니다. 응답이 실제로 도착했을 때만 관측을 기록하며, 전송 오류, 연결 실패, 타임아웃은 아무것도 기록하지 않습니다. Bedrock-runtime과 자체 호스팅 엔진은 비교 가능한 헤더를 반환하지 않아 관측이 생기지 않으며, 파인튜닝이나 모델 탐색 같은 관리 호출은 라우팅 트래픽이 아니므로 캡처하지 않습니다.

이 헤더는 라우터 백엔드가 아니라 제공자 키를 설명합니다. 백엔드마다 스냅샷 하나를 두고 응답마다 교체하므로 서로 다른 자격 증명의 관측이 섞이지 않으며, 단일 자격 증명 백엔드에서 의미가 있습니다. URL이 바뀐 백엔드나 설정에서 제거된 백엔드는 변경이 적용될 때 스냅샷을 버리므로, 같은 이름의 교체 백엔드가 다른 엔드포인트의 잔량을 물려받지 않습니다.

엔진 통계 스코어러와 달리 쿼터 잔량은 폴 루프가 아닌 응답 경로에서 오므로, 스냅샷은 해당 백엔드로의 마지막 요청만큼 신선하고 별도의 fetch가 필요 없습니다. 설정은 설정 가이드의 routing.quota_aware에 있으며, routing.usage_weight.enabled가 참이 아니면 아무 효과가 없습니다.

관련 문서