콘텐츠로 이동

로드 밸런싱 가이드

Continuum Router는 먼저 요청 후보를 실행 중이고 정상이며 호출자에게 보이고 해석된 모델을 제공할 수 있는 백엔드로 좁힙니다. 그 다음 설정된 경우 scorer 기반 라우팅을 적용하고, 그렇지 않으면 최상위 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: 섹션은 런타임 소비자가 없으므로 프로덕션 동작에 사용하면 안 됩니다.

CLI에서 시작 시 전략을 덮어쓸 수 있습니다.

continuum-router --config config.yaml --selection-strategy LeastLatency

CONTINUUM_SELECTION_STRATEGY--selection-strategy와 같은 규칙으로 파싱되는 시작 시 직접 환경 변수 오버라이드입니다(대소문자 구분 없이 일곱 가지 전략 이름 모두, snake_casekebab-case 표기도 허용).

export CONTINUUM_SELECTION_STRATEGY=LeastLatency
continuum-router --config config.yaml

우선순위는 높은 순서대로 --selection-strategy > CONTINUUM_SELECTION_STRATEGY > 설정 파일의 selection_strategy 값 > 기본값 RoundRobin입니다. 환경 변수에 인식할 수 없는 값을 설정하면 파일 값이나 기본값으로 조용히 대체되지 않고 시작이 명확한 오류와 함께 실패합니다.

전략

RoundRobin(기본값)

selection_strategy: RoundRobin

원자적 카운터로 적격 백엔드 목록을 순회합니다. 성능이 비슷한 무상태 백엔드의 좋은 기본값입니다. 백엔드 가중치나 관측 지연은 사용하지 않습니다.

WeightedRoundRobin

selection_strategy: WeightedRoundRobin

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

시간이 지남에 따라 양수 가중치에 비례해 적격 백엔드를 선택합니다. 현재 구현은 결정론적인 3,3,3,1 순서를 강제하지 않고 요청마다 가중 대상 하나를 샘플링합니다. 모든 적격 가중치가 0이면 라운드 로빈 동작으로 폴백합니다.

LeastLatency

selection_strategy: LeastLatency

기록된 평균 응답 시간이 가장 낮은 적격 백엔드를 선택합니다. 후보 관측값이 없을 때는 라운드 로빈 카운터를 사용합니다. 이 전략은 관측 지연을 최적화하지만 요청 복잡도를 예측하지 않으며 한 후보에 트래픽이 집중될 수 있습니다.

Random

selection_strategy: Random

요청마다 적격 백엔드 하나를 균등하게 선택합니다. 장기 분포가 균형을 이루더라도 짧은 구간은 불균등할 수 있습니다.

ConsistentHash

selection_strategy: ConsistentHash

해석된 모델 이름을 백엔드 해시 링에 매핑합니다. 동일한 모델과 후보 집합의 요청은 보통 같은 백엔드를 선택합니다. 이는 사용자/세션 affinity가 아니라 모델 affinity이며, 소수의 인기 모델 키가 트래픽을 지배하면 불균형할 수 있습니다.

PrefixAwareHash

selection_strategy: PrefixAwareHash

prefix_routing:
  enabled: true
  max_prefix_length: 1024
  load_factor_epsilon: 0.25

접두사 추출이 활성화되면 모델과 시스템 프롬프트, 또는 시스템 프롬프트가 없을 때 모델과 첫 사용자 메시지에서 SHA-256 키를 만듭니다. 같은 접두사를 같은 백엔드로 보내고 CHWBL(부하 제한 일관 해싱)로 바쁜 선호 백엔드에서 다음 백엔드로 넘깁니다. 접두사 키가 없으면 모델 기반 일관 해싱으로 폴백하는데, 이 폴백 경로에는 부하 상한이 없습니다. 넘기는 동작 자체가 없으므로 한 모델의 모든 요청이 한 백엔드로 몰립니다. 그래서 PrefixAwareHash에는 prefix_routing.enabled: true가 필요합니다. 접두사 라우팅을 끄면 모든 요청이 상한 없는 이 폴백을 타고, config validate와 라우터 로그가 이 조합을 경고로 알려 줍니다.

부하 상한 계산식:

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

낮은 epsilon은 균형을, 높은 epsilon은 affinity를 더 보존합니다. 접두사 추출은 코드가 적격 텍스트 접두사를 얻을 수 있는 채팅, Responses, Anthropic 호환 typed 경로를 지원합니다.

prefix_routing.virtual_nodes는 consistent hash 링 크기를 정하며(백엔드당 기본 150개 복제본), 핫 리로드로 실행 중인 풀에 적용되어 값을 바꾸면 링을 다시 만듭니다. anthropic_cache_control_injection(기본값 false)은 fleet 전체 마스터 활성화 스위치로, 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 인덱스도 접두사 키도 필요하지 않습니다. 접두사 라우팅을 쓰지 않는 fleet에서도 엔진 통계를 실제 라우팅에 쓸 수 있게 하는 것이 이 전략의 목적입니다.

한 번의 선택은 반드시 다음 세 결과 중 하나에 도달합니다.

  • 순위 적용(reason="strategy_engine_load"): 적격 후보 전부가 waiting_requests를 보고하는 신선한 스냅샷을 가지고 있고, 격차가 두 balance 임계값을 모두 넘습니다. 부하가 가장 낮은 후보가 선택됩니다.
  • 오래된 데이터 폴백(reason="strategy_stale_fallback"): 적격 후보 중 하나라도 신선한 보고가 없으면 비교 자체가 정의되지 않으므로 routing.engine_load.base_strategy가 결정합니다. 후보 전부의 보고를 요구하는 것은 의도된 설계입니다. 일부만 순위 매기면 불균형 구간의 트래픽이 통계를 공개하는 백엔드로만 몰리고 그렇지 않은 백엔드는 굶게 되는데, 자체 호스팅과 호스팅 API가 섞인 fleet에서는 이쪽이 더 나쁜 결과입니다.
  • 히스테리시스 유지(reason="strategy_hysteresis_hold"): 후보 전부가 신선한 큐 길이를 보고하지만 격차가 데드밴드 안에 있어 base 전략이 결정합니다. 균형 잡힌 fleet의 정상 상태 응답입니다.

즉 이 전략은 base_strategy(기본값 RoundRobin, 이기종 fleet에서는 균형 상태에서도 백엔드별 가중치를 존중하도록 WeightedRoundRobin 권장) 위에 실제 불균형 구간에만 개입하는 교정 층입니다. 이 구조가 herding도 제한합니다. 스냅샷은 engine_stats.interval마다 한 번만 갱신되므로 순위가 적용된 패스는 한 인터벌 동안의 트래픽을 한 백엔드로 보내는데, 데드밴드는 그 비용을 감수할 만큼 불균형이 커지기 전에는 순위 적용 자체를 막습니다.

selection_strategy: EngineLoadrouting.engine_load.enabled를 읽지 않습니다. 그 스위치는 스코어러 항만 제어하며, 전략 이름을 지정하는 것 자체가 옵트인입니다. 다만 데이터를 위해 engine_stats.enabled: true는 필요하고, 이것이 빠지면 config validateselection_strategy 경로에 경고를 냅니다. routing.engine_load.base_strategy에는 EngineLoad를 넣을 수 없으며 로드 시 거부됩니다. 신선도 한계(engine_stats.interval * routing.engine_load.max_staleness_intervals)와 포화 admission 힌트는 스코어러 항과 완전히 동일하게 적용됩니다. 힌트는 모든 전략보다 앞선 공통 선택 지점에서 실행되므로 여기에 따로 설정할 것이 없습니다.

Scorer 우선순위

BackendPool은 설정된 폴백 전략 전에 등록된 scorer를 평가합니다. 예를 들어 KV 캐시 인덱스가 활성화되고 겹침 점수가 충분히 높으면 관련 캐시 토큰을 가진 백엔드를 선택하며, 그렇지 않으면 selection_strategy가 실행됩니다. 따라서 관측 분포가 순수 라운드 로빈이나 가중 비율과 의도적으로 다를 수 있습니다.

scorer 설정과 임계값은 KV 캐시 아키텍처를 참고하십시오.

엔진 부하 인식 선택

routing.engine_load.enabled: true로 설정하면 KV 오버랩 스코어러에 엔진 통계 폴러가 공급하는 엔진 부하 항이 추가됩니다. 요청 접두사의 캐시를 보유한 백엔드 가운데 엔진이 보고한 waiting_requests가 적고(신선한 후보 전부가 total_slots를 보고하면 그 값으로 정규화하고, 그렇지 않으면 해당 요청 후보 집합의 최대 waiting_requests로 정규화) kv_cache_usage가 낮은 쪽을 우선합니다. 라우터 자체의 in-flight 카운터는 엔진 내부 큐에 쌓인 요청이나 선점 직전의 KV 캐시를 볼 수 없는데, 이 항이 그 공백을 채웁니다.

세 가지 가드가 이 항을 제한합니다.

  • 신선도: engine_stats.interval * routing.engine_load.max_staleness_intervals(기본 3 주기)보다 오래된 스냅샷은 없는 것으로 취급합니다. 신선한 스냅샷이 없는 후보는 기본 점수를 유지하고, 모든 후보가 오래되면 해당 패스는 기본 스코어링과 완전히 같게 동작합니다.
  • 히스테리시스: 신선한 waiting_requests의 편차가 balance_abs_threshold(기본 64)와 balance_rel_threshold(기본 1.5)를 모두 넘을 때만 순위를 매깁니다. SGLang Model Gateway의 balance 게이트를 그대로 따른 것으로, 이 데드 밴드 안에서는 항이 아무 기여도 하지 않아 큐가 서로 엇갈리며 진동해도 선택이 요동하지 않습니다. 이 데드 밴드는 오직 waiting_requests 편차만으로 판정됩니다. kv_cache_usage만으로는 게이트를 열 수 없으므로, 큐는 균형 잡혔거나 비어 있지만 KV 압박은 서로 크게 다른 플릿은 kv_cache_usage가 부하 점수의 일부임에도 불구하고 엔진 부하 순위를 전혀 받지 못합니다.
  • 범위: 부하·헬스 항과 마찬가지로 접두사 보유 백엔드 사이에서만 순위를 매기며, 스코어러 자체가 존재해야 하고(prefix_routing.enabled와 활성화된 kv_cache_index) 데이터를 위해 engine_stats.enabled: true가 필요합니다. 둘 중 하나라도 빠지면 config validate가 경고합니다. 게이트 판정, 정규화, 캐시된 결과, routing_engine_load_decisions_total의 backend 레이블은 모두 해당 선택 패스의 정확한 라이브 후보 집합에 묶입니다. 따라서 모델 가시성, 상태, 서킷/재시도 제외, 키별 권한으로 제외된 풀 멤버가 게이트를 열거나 캐시된 선호를 요청에 흘릴 수 없습니다.

신뢰 모델. waiting_requestskv_cache_usage는 각 엔진이 자기 자신에 대해 보고하는 값이므로, 침해되었거나 결함이 있는 백엔드가 부하를 낮게 신고해 트래픽을 끌어올 수 있습니다. 이때 얻을 수 있는 이득은 세 가지 성질로 제한됩니다. 이 항은 min_overlap_threshold를 이미 통과한 접두사 보유 백엔드에만 적용되므로, 해당 요청의 캐시를 전혀 보유하지 않은 백엔드는 애초에 끌려 들어올 수 없습니다. 항의 상한은 engine_load_weight(기본 0.3)이고 기본 가중치들의 합은 1.0이므로, 자가 보고 값 하나만으로 결정적인 오버랩·부하·헬스 차이를 뒤집을 수 없습니다. 그리고 큐 편차가 작은 동안에는 balance 데드 밴드가 이 항을 완전히 무력화합니다. engine_load_weight를 1.0 쪽으로 올리면 그만큼 상한도 올라갑니다. 반대편의 어드미션 힌트는 모든 정상 후보가 신선한 포화 상태를 보고해야 작동하므로, 엔진 하나가 거부를 유발할 수 있는 범위는 자신이 유일한 후보인 요청뿐입니다. kv_cache_usage는 수집 시점에 [0, 1]로 클램프되고 유한하지 않은 값은 폐기되므로, 터무니없는 값이 그 범위를 벗어날 수 없습니다.

스코어링 패스마다 routing_engine_load_decisions_total{backend,reason}이 기록됩니다. reasonengine_load(항이 실제로 순위를 매김, backend는 엔진이 선호한 후보), stale_fallback(신선한 엔진 데이터 없음), hysteresis_hold(데이터는 있으나 데드 밴드 안) 중 하나입니다.

이와 별개로 routing.engine_load.admission.enabled: true는 공용 선택 지점에 포화 어드미션 힌트를 켭니다. 모든 정상 후보가 admission.kv_usage_threshold(기본 0.98)를 넘는 신선한 kv_cache_usage 스냅샷을 보고하면 선점 직전의 엔진에 큐잉하는 대신 선택을 거부합니다(reason="admission_reject"). 채팅 경로에서 이 거부는 재시도 가능한 503으로 나타나고 BackendUnhealthy 트리거를 통해 fallback.fallback_chains에 참여합니다. 신선한 스냅샷이 없는 후보는 항상 통과시키므로 게이트는 엔진이 실제 보고한 값으로만 작동합니다. 전체 필드 목록과 리로드 분류는 config.yaml.examplerouting 섹션을 참고하십시오.

상태와 적격성

선택기는 미리 해석된 후보 집합 밖의 백엔드를 의도적으로 고르지 않습니다. 후보 필터에는 다음이 포함됩니다.

  • 모델 가용성과 별칭 해석
  • 백엔드 상태
  • 서킷/재시도 제외
  • 키별 allowed_backends 및 모델 가시성
  • 해당하는 경우 내부 백엔드 필터
  • 기능 활성 시 AppProxy ROUTER 공개 모델 제한

상태 확인은 전역으로 설정합니다.

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

현재 스키마에는 dynamic_weight_adjustment, health_score_threshold, Custom, Geographic 선택 설정이 없습니다.

전략 변경

selection_strategy 수정은 라이브 핫 리로드입니다. 설정 감시자가 새 전략을 실행 중인 백엔드 풀에 원자적으로 교체하므로, 재시작 없이 다음 선택부터 새 전략이 적용됩니다. 교체 과정에서 백엔드 멤버십, 백엔드별 통계, 처리 중(in-flight) 요청 집계는 그대로 유지되며 선택 알고리즘만 바뀝니다.

풀의 prefix_routing.load_factor_epsilon(CHWBL 부하 계수)도 같은 방식으로 리로드됩니다. 새 값이 재시작 없이 실행 중인 풀에 교체됩니다.

변경을 적용하기 전에 파일을 검증할 수 있습니다.

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

모니터링

설정된 Admin 인증을 사용합니다.

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

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

/admin/backends는 설정된 백엔드 상태 정보를 보고합니다. /admin/prefix-routing/stats는 접두사 결정 카운터와 실시간 진행 중 분포를 보고합니다.

메트릭이 컴파일되고 활성화되면 접두사 라우팅은 다음을 내보냅니다.

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

기능 제한 메트릭은 해당 기능을 등록한 빌드에만 있으므로 시리즈에 의존하기 전 실행 중인 /metrics 출력을 확인하십시오.

전략 선택

전략 적합한 경우 주요 절충점
RoundRobin 적격 백엔드 성능이 비슷함 용량과 지연 차이를 무시
WeightedRoundRobin 상대 용량을 알고 있음 가중치 튜닝 필요, 비율은 통계적
LeastLatency 관측 응답 시간으로 라우팅 트래픽 집중 가능, 이전 관측 필요
Random 단순 무상태 분산이면 충분함 단기 분포 예측이 어려움
ConsistentHash 모델 affinity가 중요함 인기 모델 키가 부하를 불균형하게 만들 수 있음
PrefixAwareHash 공유 프롬프트와 분산 KV 캐시가 중요함 고유 프롬프트 이점이 적고 접두사 추출/튜닝 필요
EngineLoad 자체 호스팅 fleet이 엔진 통계를 공개하고 큐 길이로 라우팅하고 싶음 engine_stats.enabled: true와 후보 전부의 신선한 보고가 필요하고, 아니면 base_strategy로 폴백

실제 적격 모델 집합으로 테스트하십시오. 모델이 한 백엔드에만 존재하면 어떤 선택 전략도 그 모델을 다른 곳으로 분산할 수 없습니다.

같이 보기