헬스, 캐싱 & 로깅¶
헬스 체크 섹션¶
백엔드 헬스 모니터링을 설정합니다:
health_checks:
enabled: true # 모든 백엔드 프로브를 건너뛰려면 false로 설정
interval: "30s" # 확인 주기
timeout: "10s" # 요청 타임아웃
unhealthy_threshold: 3 # 비정상 표시까지의 실패 횟수
healthy_threshold: 2 # 정상 표시까지의 성공 횟수
endpoint: "/health" # 확인할 엔드포인트 (타입별 기본값은 아래 표 참고)
warmup_check_interval: "1s" # 워밍업 중 가속화된 확인 주기
max_warmup_duration: "300s" # 최대 워밍업 감지 시간
block_startup: true # 프리워밍과 첫 헬스 체크가 끝날 때까지 리스너 바인딩을 대기
prewarm_timeout: "10s" # 기동 시 프리워밍 프로브 요청 하나의 제한 시간
enabled의 기본값은 true입니다. false이면 라우터는 연결 프리워밍, 첫 헬스 체크 라운드, 백그라운드 모니터링을 모두 건너뛰며 애플리케이션 상태에 헬스 체커를 두지 않습니다. 실행 중인 라우터에서 이 값을 바꾸려면 재시작해야 하며, --disable-health-checks도 시작 전에 같은 설정을 적용합니다. 최상위 health_checks.interval과 health_checks.timeout 값은 시작 시점과 리로드 후 모두 권위 있는 설정입니다. 레거시 timeouts.health_check 블록은 마이그레이션 호환성을 위해 계속 파싱되지만 폐기 예정이며 무시됩니다. 유효 값이 충돌하면 경고가 출력됩니다.
헬스 체크 프로세스:
- 라우터가 각 백엔드의 헬스 엔드포인트를 쿼리
- 성공적인 응답은 성공 카운터 증가
- 실패한 응답은 실패 카운터 증가
- 실패 임계값에 도달하면 백엔드를 비정상으로 표시
- 성공 임계값에 도달하면 백엔드를 정상으로 표시
- 정상 백엔드만 트래픽 수신
가속화된 워밍업 헬스 체크¶
라우터는 백엔드 워밍업 중 가속화된 헬스 체크를 지원합니다. 이는 모델 로딩 중 HTTP 503을 반환하는 llama.cpp와 같은 백엔드에 특히 유용합니다.
백엔드 상태:
| 상태 | HTTP 응답 | 동작 |
|---|---|---|
ready | 200 OK | 일반 간격으로 체크 |
warming_up | 503 Service Unavailable | 가속화된 간격으로 체크 |
down | 연결 실패 | 일반 간격으로 체크 |
unknown | 초기 상태 | 첫 번째 체크로 상태 결정 |
워밍업 설정:
| 옵션 | 기본값 | 설명 |
|---|---|---|
warmup_check_interval | 1s | 워밍업 중 가속화된 체크 간격 |
max_warmup_duration | 300s | 가속화 모드 유지 최대 시간 |
동작 방식:
- 백엔드가 HTTP 503을 반환하면
warming_up상태로 진입 - 헬스 체크가 가속화된 간격(기본값: 1초)으로 전환
- 백엔드가 HTTP 200을 반환하면
ready상태가 되어 일반 간격으로 복귀 - 워밍업이
max_warmup_duration을 초과하면 백엔드를 비정상으로 표시
이 기능으로 모델 가용성 감지 지연 시간이 최대 30초(최악의 경우)에서 약 1초로 단축됩니다.
시작 시 동작¶
health_checks.block_startup(기본값: true)은 첫 헬스 체크 라운드가 끝나기 전에 리스너를 바인딩할지 여부를 결정합니다.
| 값 | 동작 |
|---|---|
true (기본값) | 설정된 모든 백엔드의 연결 프리워밍과 첫 헬스 체크 라운드가 끝날 때까지 리스너 바인딩을 기다립니다. 이 라운드가 끝나기 전까지는 /health와 /v1/models에 접근할 수 없습니다. |
false | 리스너를 먼저 바인딩합니다. 프리워밍과 첫 헬스 체크는 백그라운드에서 실행되어 /health가 즉시 응답하고 백엔드별 상태는 admin과 모델 엔드포인트를 통해 비동기로 수렴합니다. |
/health는 어느 쪽이든 라우터 자체의 생존 여부만 보고하며 백엔드 상태는 반영하지 않으므로 의미는 그대로 유지됩니다. 외부 준비성 프로브가 짧은 데드라인을 강제한다면 block_startup: false로 설정하세요. 그렇지 않으면 응답 없는 백엔드 하나가 네트워크 저하 상황에서 프리바인드 구간을 그 데드라인 너머로 늘릴 수 있습니다. block_startup 값과 무관하게 헬스 체크 요청은 모두 timeout으로 상한이 걸리고, 프리워밍 요청은 자체 상한인 health_checks.prewarm_timeout(기본값 10s)을 가지므로 응답 없는 백엔드가 라우터 공유 클라이언트의 타임아웃(기본 스트리밍 총합 600초)만큼 시작을 붙잡는 일은 이제 없습니다. Anthropic 분기와 일반 분기 모두 이 값 하나를 읽으며, 프리워밍은 기동 때만 실행되므로 값을 바꾸면 다음 재시작부터 적용됩니다.
짧은 데드라인을 가진 준비성 프로브
일부 임베딩 호스트는 /health를 고정된 준비성 캡으로 폴링합니다(예: Backend.AI GO의 30초 폴링). 네트워크가 저하된 상황에서 백엔드 프리워밍이나 첫 헬스 체크 라운드가 이 캡보다 오래 걸릴 수 있다면 block_startup: false로 설정해 프로브 데드라인과 경쟁하는 대신 라우터가 즉시 응답하도록 하세요.
백엔드별 헬스 체크 설정¶
각 백엔드 타입에는 합리적인 기본 헬스 체크 엔드포인트가 있습니다. 백엔드별로 커스텀 health_check 설정으로 이러한 기본값을 재정의할 수 있습니다.
백엔드 타입별 기본 헬스 체크 엔드포인트:
| 백엔드 타입 | 기본 엔드포인트 | 폴백 엔드포인트 | 메서드 | 참고 |
|---|---|---|---|---|
openai | /v1/models | - | GET | 표준 OpenAI 엔드포인트 |
vllm | /health | /v1/models | GET | /health는 모델 로드 후 사용 가능 |
sglang | /health | /v1/models | GET | sglang.srt 서버, 시작 중이거나 종료 중일 때 503 |
ollama | /api/tags | / | GET | Ollama 전용 엔드포인트 |
llamacpp | /health | /v1/models | GET | llama-server 엔드포인트 |
mlxcel | /health | /v1/models | GET | MLxcel 서버 (llama-server 호환) |
lmstudio | /v1/models | /api/v1/models | GET | OpenAI 호환 + 네이티브 API |
continuum-router | /health | /v1/models | GET | 원격 CR / Backend.AI GO |
anthropic | /v1/messages | - | POST | 200, 400, 401, 429를 정상으로 허용 |
gemini | /models | /v1beta/models | GET | 네이티브 Gemini 엔드포인트 |
azure | /health | /v1/models | GET | Azure OpenAI 엔드포인트 |
generic | /health | /v1/models | GET | 범용 폴백 |
폴백 동작:
기본 헬스 체크 엔드포인트가 HTTP 404를 반환하면 라우터는 자동으로 폴백 엔드포인트를 순서대로 시도합니다. 이렇게 하면 모든 표준 엔드포인트를 구현하지 않는 백엔드와도 호환됩니다.
커스텀 헬스 체크 설정:
backends:
- name: vllm-custom
type: vllm
url: http://localhost:8000
models:
- my-model
health_check:
endpoint: /custom-health # 기본 엔드포인트
fallback_endpoints: # 기본 엔드포인트가 404 반환 시 시도
- /health
- /v1/models
method: GET # HTTP 메서드: GET, POST 또는 HEAD
timeout: 10s # 전역 헬스 체크 타임아웃 재정의
accept_status: # 정상을 나타내는 상태 코드
- 200
- 204
warmup_status: # 모델 로딩 중을 나타내는 상태 코드
- 503
헬스 체크 설정 옵션:
| 옵션 | 타입 | 기본값 | 설명 |
|---|---|---|---|
endpoint | string | 백엔드 타입별 상이 | 기본 헬스 체크 엔드포인트 경로 |
fallback_endpoints | array | 백엔드 타입별 상이 | 기본 엔드포인트가 404 반환 시 시도할 엔드포인트 |
method | string | GET | HTTP 메서드: GET, POST 또는 HEAD |
body | object | null | POST 요청용 JSON 본문 |
accept_status | array | [200] | 백엔드가 정상임을 나타내는 상태 코드 |
warmup_status | array | [503] | 백엔드가 워밍업 중임을 나타내는 상태 코드 |
timeout | string | 전역 타임아웃 | 전역 헬스 체크 타임아웃 재정의 |
예시: Anthropic 스타일 헬스 체크:
POST 요청을 사용하거나 오류 코드를 정상 지표로 허용하는 백엔드의 경우:
backends:
- name: custom-api
type: generic
url: http://localhost:9000
models:
- custom-model
health_check:
endpoint: /api/v1/health
method: POST
body:
check: true
accept_status:
- 200
- 400 # Bad request는 서버가 동작 중임을 의미
- 401 # Unauthorized는 서버가 동작 중임을 의미
- 429 # Rate limited는 서버가 동작 중임을 의미
타임아웃 섹션¶
요청 처리 타임아웃은 timeouts: 아래에 두고, 비스트리밍, 스트리밍, 이미지 생성 요청별 예산과 모델별 오버라이드를 나눠 설정합니다:
timeouts:
connection: "10s" # TCP 연결 수립 타임아웃
request:
standard:
first_byte: "30s" # 폐기 예정이며 동작하지 않음. 실제 예산은 `total`
total: "180s" # 전체 요청 타임아웃 (기본 상한 540s)
streaming:
first_byte: "120s" # 첫 SSE 청크에 적용되는 실제 기한
chunk_interval: "30s" # 두 번째 청크부터의 최대 간격
total: "600s" # 전체 스트리밍 타임아웃 (기본 상한 1200s)
model_overrides:
gpt-5-latest:
streaming:
total: "1200s" # 모델별 오버라이드
limits: # 선택 사항. 기동 시 고정되므로 바꾸려면 재시작
max_standard_timeout: "900s" # 기본값 540s, 절대 상한 1080s
어느 필드가 어디에 적용되는가¶
first_byte는 두 경로에서 같은 뜻이 아니며, 둘 중 하나만 실제로 동작합니다.
streaming.first_byte는 강제됩니다. 스트리밍 응답은 스트림이 끝나기 한참 전에 첫 SSE 청크를 내보내므로, 첫 청크 기한은 전체 예산과 구별되는 실제 신호입니다. 기본값은120s입니다. 추론 모델이 첫 토큰까지 60초를 넘기는 일이 흔하기 때문입니다. 첫 청크가 도착한 뒤부터는chunk_interval이 이어받습니다.standard.first_byte와image_generation.first_byte는 폐기 대상이고 동작하지 않으며, 다음 메이저 릴리스에서 제거됩니다. 비스트리밍 응답은 버퍼링된 본문이라 완성된 뒤에야 헤더가 도착하므로, 첫 바이트까지의 시간이 곧 전체 생성 시간입니다. 이 경로에서 예산은total하나뿐입니다. 두 필드는 여전히 파싱되고first_byte <= total검사도 유지되며, 기본값과 다른 값을 쓰면 로드 시점에 폐기 경고가 나옵니다.
따라서 계속 타임아웃되는 추론 모델에는 스트리밍 경로에서 더 큰 streaming.first_byte를, 비스트리밍 경로에서 더 큰 standard.total을 주어야 합니다. standard.first_byte를 올려도 아무것도 바뀌지 않습니다.
검증 상한¶
이 섹션의 모든 상한은 timeouts.limits로 조정할 수 있습니다. 이 값은 부팅 설정에서 한 번 해석한 뒤 프로세스 수명 동안 고정됩니다. 핫 리로드, Admin API, 컨트롤 플레인 설정 동기화는 이를 바꾸는 후보를 모두 거부하므로 런타임 경로가 라우터 자신의 DoS 방어선을 넓힐 수 없습니다. 상한을 바꾸려면 파일을 고치고 재시작해야 합니다. 각 필드에는 컴파일 시점에 박힌 절대 상한도 있으며, 그 값을 넘기면 조용히 잘리는 대신 로드 오류가 납니다.
| 필드 | 기본값 | 절대 상한 | 제한 대상 |
|---|---|---|---|
max_standard_timeout | 540s | 1080s | request.standard.total |
max_retry_timeout | 30s | 120s | retry.timeout |
max_streaming_timeout | 1200s | 3600s | request.streaming.total |
max_connection_timeout | 15s | 60s | connection |
min_chunk_interval | 1s | 10s | chunk_interval의 하한 |
max_chunk_interval | 60s | 300s | chunk_interval |
max_image_generation_timeout | 600s | 1800s | request.image_generation.total |
max_first_byte_timeout | 480s | 1200s | request.streaming.first_byte |
large_model_bonus | 600s | 1200s | extended_models에 주는 추가 스트리밍 예산 |
extended_models | 기본 제공 프런티어 모델 목록 | - | large_model_bonus를 받는 모델 id |
타임아웃 고려 사항:
- 긴 total 값은 느린 모델 추론 수용
- 스트리밍 요청은 비스트리밍보다 오래 걸릴 수 있음
- 사용자 경험과 리소스 사용 간 균형
max_standard_timeout을 올리는 것만으로는 비용이 없습니다. 운영자가standard.total자체를 올리는 순간부터 멈춘 업스트림이 그만큼 오래 요청 슬롯을 차지하므로,connection과 헬스 체크 및 서킷 브레이커,server.max_concurrent_requests를 함께 두십시오
재시도 섹션¶
복원력을 위한 전역 재시도 설정:
retry:
max_attempts: 3 # 최대 재시도 횟수
initial_delay: "100ms" # 재시도 간 초기 지연
max_delay: "30s" # 재시도 지연 상한
backoff_multiplier: 2.0 # 지수 백오프 배수
jitter: true # 무작위 지터 추가
retryable_status_codes: [429, 502, 503, 504]
재시도 전략:
- 지수 백오프: 지연이 지수적으로 증가 (100ms, 200ms, 400ms...)
- 지터: 썬더링 허드 방지를 위한 무작위성 추가
- 최대 지연: 극도로 긴 대기 방지
모델 집계 섹션¶
model_aggregation 섹션은 집계된 모델 목록 캐시, 백엔드별 모델 페처, 중복 제거, 강제 갱신 엔드포인트, 백그라운드 갱신 작업을 설정합니다. 모든 필드에 기본값이 있어 섹션은 선택 사항이며, 시작 시 해석된 스냅샷이 적용됩니다. 변경하려면 재시작이 필요합니다.
model_aggregation:
cache_ttl: "60s" # 집계된 모델 목록의 하드 TTL
soft_ttl_ratio: 0.8 # 사용 가능한 stale 구간 비율, (0.0, 1.0]
request_timeout: "5s" # 백엔드별 모델 목록 페치 타임아웃
max_retries: 2 # 백엔드별 페치 재시도 횟수
retry_delay: "500ms" # 페치 재시도 간 지연
max_models_per_backend: 1000 # 단일 백엔드당 모델 상한 (> 0)
max_total_models: 5000 # 전체 백엔드 모델 상한 (> 0)
max_response_size: 10485760 # 백엔드 응답 최대 크기(바이트) (> 0)
empty_response_base_ttl: "5s" # 빈 응답 백오프 기준값
empty_response_max_ttl: "60s" # 빈 응답 백오프 상한 (>= base)
max_cache_entries: 100 # 캐시 항목 최대 수, 초과 시 LRU 축출 (> 0)
deduplication_strategy: merge_backends # first_wins | last_wins | merge_backends
allow_force_refresh: true # false이면 POST /v1/models/refresh가 403 반환
background_refresh:
enabled: true # 만료 전 선제적 캐시 갱신
check_interval: "10s" # 백그라운드 갱신 폴링 주기
응답을 멈춘 백엔드의 페치 예산¶
모든 백엔드로의 팬아웃은 동시에 진행되므로 한 번의 갱신 비용은 가장 느린 백엔드의 비용과 같습니다. 응답하지 않는 백엔드는 갱신마다 (max_retries + 1) × request_timeout + max_retries × retry_delay, 기본값으로 16초를 소비합니다. 이 꼬리를 제한하기 위해, 직전 갱신에서 일시적 오류(타임아웃, 연결 실패, 5xx)로 실패한 백엔드는 다음 갱신에서 재시도 없이 request_timeout 한 번만 시도합니다. 기본값 기준으로 죽은 백엔드가 갱신에 더하는 비용은 16초에서 5초로 줄어듭니다. 시도당 타임아웃은 의도적으로 줄이지 않습니다. 다시 응답하는 백엔드는 첫 성공에서 곧바로 전체 예산을 되찾고, 느리지만 동작하는 백엔드가 절대 맞출 수 없는 타임아웃에 갇히는 일도 없습니다.
실패 이력이 없는 백엔드는 항상 전체 예산을 받으므로, 프로세스의 첫 갱신과 새로 추가된 백엔드의 첫 갱신은 잘리지 않습니다. 같은 갱신에서 모든 백엔드가 실패하면 한 번만 시도한 백엔드들이 결과 발행 전에 남은 예산을 다 쓰므로, 일시적인 전체 장애가 빈 목록으로 저장되지 않습니다. 이 경우 갱신 비용은 이전과 같은 16초입니다. 짧게 잘린 백엔드는 일반적인 일시적 실패로 처리됩니다. model_transient_errors_total에 집계되고, 마지막으로 알려진 모델은 발행 목록에 남으며, 비정상으로 표시되지 않습니다. 이 동작에는 설정 항목이 없으며 기준은 실패한 갱신 한 번입니다.
캐시 스탬피드 방지¶
라우터는 캐시 스탬피드(썬더링 허드 문제)를 방지하기 위해 세 가지 전략을 구현합니다.
- 싱글플라이트 패턴: 동일 집계 요청을 한 번에 하나만 실행
- Stale-While-Revalidate: 갱신 중에도 오래된 데이터를 사용 가능하게 유지(
cache_ttl의soft_ttl_ratio지점 이후) - 백그라운드 갱신:
background_refresh.enabled이면check_interval루프가 소프트 TTL 이후 항목을 갱신
오래된 데이터 제공은 소프트 구간뿐 아니라 하드 만료 이후에도 적용됩니다. cache_ttl이 지나도 이전에 집계한 목록을 즉시 반환하면서 백그라운드 재집계를 한 번만 실행하므로, TTL 경계에 걸린 요청이 모든 백엔드에 대한 팬아웃 비용을 치르지 않습니다. 다만 무한정 허용하지는 않습니다. 항목 나이가 cache_ttl의 3배(기본 60초 TTL 기준 180초) 이내일 때만 제공하며, 그 한도를 넘으면 요청이 대기하며 새 데이터를 기다립니다. 따라서 갱신 작업이 멈춰도 임의로 오래된 모델 목록이 계속 제공되지는 않습니다.
무효화에도 같은 보존 방식이 적용됩니다. 핫 리로드로 백엔드 구성이 바뀌면 캐시된 목록을 삭제하지 않고 만료 상태로 표시하며, 나이 계산은 무효화 시점부터 다시 시작합니다. 백엔드를 등록한 직후 모델 목록을 조회하는 클라이언트는 보존된 목록으로 응답받고, 새 토폴로지는 백그라운드에서 집계됩니다. POST /v1/models/refresh는 영향을 받지 않습니다. 이 엔드포인트는 캐시를 완전히 비우고 실제로 새 데이터가 준비될 때까지 대기합니다.
빈 결과는 empty_response_base_ttl과 empty_response_max_ttl 사이의 제한된 지수 백오프를 사용합니다. 즉시 갱신이 필요하면 속도 제한된 POST /v1/models/refresh를 사용하십시오. allow_force_refresh: false이면 이 엔드포인트와 Admin 갱신 엔드포인트는 문서화된 비활성(403) 응답을 반환합니다.
응답 캐시 섹션¶
결정적(temperature == 0) 요청에 대해 완전한 LLM 응답을 캐싱하며, 비스트리밍과 스트리밍 모두 지원합니다. 반복되는 동일한 요청은 백엔드를 호출하지 않고 메모리에서 제공됩니다.
response_cache:
enabled: true # 응답 캐싱 활성화 (기본값: false)
backend: memory # 캐시 백엔드: "memory" (기본값), "redis", 또는 "tiered"
capacity: 1000 # 최대 캐시 응답 수 (LRU 퇴거)
ttl: "5m" # 캐시 항목 유효 기간 (예: "5m", "1h")
max_response_size: 1048576 # 최대 응답 본문 크기 (바이트, 기본값: 1 MiB)
max_stream_buffer_size: 10485760 # 최대 스트리밍 버퍼 크기 (바이트, 기본값: 10 MiB)
캐시 적격성¶
다음 조건이 모두 충족될 때만 응답이 캐싱됩니다:
response_cache.enabled가true- 요청의
temperature필드가0이거나 없음 (결정적 출력) - 응답 본문이
max_response_size를 초과하지 않음 - 비스트리밍 요청의 경우: 응답에
finish_reason: "error"가 포함되지 않음 - 스트리밍 요청의 경우: 스트림이 성공적으로 완료됨 (최종
[DONE]이벤트 수신); 중단되거나 오류가 발생한 스트림은 폐기되며 캐싱되지 않음
적격하지 않은 요청은 정상적으로 백엔드에 전달됩니다.
스트리밍 캐시 동작¶
캐싱이 활성화되고 temperature가 0일 때, 스트리밍(stream: true) chat completions도 캐싱 대상이 됩니다:
- SSE 이벤트가 클라이언트로의 정상 스트림 전달과 함께 메모리에 버퍼링되며, 관측 가능한 지연 증가는 없습니다.
- 스트림이 성공적으로 완료되면(최종
[DONE]이벤트), 라우터가 버퍼링된 청크에서 완전한chat.completionJSON 객체를 재구성하여 캐시에 저장합니다. - 스트림이 중단되면(클라이언트 연결 해제, 타임아웃, 백엔드 오류), 버퍼가 폐기되고 아무것도 저장되지 않습니다.
- 캐시 히트 시, 캐시된 응답이
X-Cache: HIT헤더와 함께 합성 SSE 스트림(text/event-stream)으로 클라이언트에 재생됩니다. max_stream_buffer_size설정은 스트리밍 버퍼를 제한합니다. 총 버퍼 내용이 이 임계값을 초과하는 스트림은 캐싱되지 않습니다.
X-Cache 응답 헤더¶
모든 chat completion 응답에 X-Cache 헤더가 포함됩니다:
| 값 | 의미 |
|---|---|
HIT | 캐시에서 응답이 제공됨; 백엔드 호출 없음 |
MISS | 캐시를 확인했지만 항목이 없음; 백엔드에서 응답을 받아 저장함 |
BYPASS | 요청이 캐싱 불가 (예: temperature > 0); 백엔드가 호출되었으나 저장하지 않음 |
캐시 설정 옵션¶
| 옵션 | 기본값 | 설명 |
|---|---|---|
enabled | false | 응답 캐싱 활성화 여부 |
backend | "memory" | 캐시 백엔드 타입: "memory", "redis", 또는 "tiered". 변경 시 재시작 필요. |
capacity | 1000 | 최대 항목 수; 제한에 도달하면 가장 오래된 항목이 퇴거됨 (LRU). Redis 또는 tiered 백엔드 사용 시 무시됨. |
ttl | "5m" | 캐시 항목의 유효 기간. 기간 문자열 지원: "30s", "5m", "1h" |
max_response_size | 1048576 | 이 값(바이트)보다 큰 비스트리밍 응답은 캐싱되지 않음 |
max_stream_buffer_size | 10485760 | 누적 버퍼가 이 값(바이트)을 초과하는 스트리밍 응답은 캐싱되지 않음 (기본값: 10 MiB) |
redis.url | -- | Redis/Valkey 연결 URL (redis-cache 빌드 기능 및 backend: redis 필요). |
redis.pool_size | 8 | Redis 연결 풀의 연결 수 |
redis.key_prefix | "cr:resp:" | 모든 캐시 키의 네임스페이스 접두사. 동일한 Redis 인스턴스의 다른 애플리케이션과의 충돌 방지. |
redis.connect_timeout_ms | 3000 | 새 Redis 연결 설정 타임아웃 (밀리초) |
redis.command_timeout_ms | 1000 | 개별 Redis 명령 타임아웃 (밀리초) |
redis.tls | false | Redis 연결에 TLS 사용 여부 (또는 URL에 rediss:// 스킴 사용) |
redis.fallback_to_memory | true | Redis에 연결할 수 없을 때 인메모리 캐시로 폴백 |
l1.type | "memory" | tiered 백엔드의 L1 티어 타입: "memory" 또는 "redis" (redis-cache 기능 필요) |
l1.max_value_size | 1048576 | L1 저장 대상 최대 값 크기 (바이트). 이를 초과하는 값은 L2에만 저장됨. |
l2.type | "s3" | L2 티어 타입 (현재 "s3"만 지원; s3-cache 기능 필요) |
l2.endpoint | -- | S3 호환 엔드포인트 URL (예: https://vast-cluster:8080) |
l2.bucket | -- | 캐시 객체 저장을 위한 S3 버킷 이름 |
l2.key_prefix | "response-cache/" | 버킷 내 객체 키 접두사 |
l2.region | "us-east-1" | AWS 호환 리전 문자열 |
l2.access_key | -- | S3 액세스 키. ${ENV_VAR} 확장 지원. |
l2.secret_key | -- | S3 시크릿 키. ${ENV_VAR} 확장 지원. |
l2.ttl_override | -- | L2 항목의 선택적 TTL 오버라이드 (예: "24h"). L2 저장 시에만 전역 ttl을 대체함. |
tiered.promote_on_hit | true | L2 히트 시 L1으로 프로모션할지 여부 |
tiered.l1_promotion_ttl | "5m" | L2에서 L1으로 프로모션된 값에 적용되는 TTL |
Redis/Valkey 백엔드¶
기본적으로 응답 캐시는 프로세스 메모리에 항목을 저장합니다. 여러 라우터 인스턴스 간에 캐시를 공유하거나 재시작 후에도 유지하려면 Redis 또는 Valkey 백엔드를 설정하세요.
빌드 요구 사항: 공식 산출물에는 없습니다. 릴리스 바이너리와 게시된 컨테이너 이미지는 redis-cache Cargo 기능을 컴파일하므로 이 섹션은 설정만 다루면 됩니다. 소스 빌드는 기본 full 세트에 이 기능이 없으므로 직접 추가해야 얻을 수 있습니다:
지원 토폴로지: 관리되는 엔드포인트 하나입니다. 단독 서버, 고정된 DNS 이름이나 가상 IP 뒤에서 장애 조치하는 제공자 관리형 HA 엔드포인트(자동 장애 조치를 지원하는 ElastiCache, Azure Cache for Redis, Memorystore, 관리형 Valkey), 클러스터를 주소 하나로 노출하는 프록시가 여기 해당합니다. Redis Sentinel과 Redis Cluster를 직접 지정하는 것은 지원 범위 밖입니다. 라우터가 구동하는 클라이언트는 엔드포인트 하나만 다루며 Sentinel master 탐색도, 클러스터 slot 라우팅도 하지 않습니다. redis+sentinel://, redis+cluster://, 쉼표로 구분한 다중 호스트 URL은 몇 시간 뒤 연결 시점이 아니라 설정을 로드할 때 거부됩니다. 배포 쪽 계약은 배포 가이드를 참조하세요.
설정:
response_cache:
enabled: true
backend: redis # Redis 백엔드 선택
ttl: "5m"
redis:
url: "redis://localhost:6379" # 일반 TCP
# url: "rediss://redis.example.com:6380" # TLS
# url: "redis://:password@localhost:6379" # 인증 포함
pool_size: 8 # 연결 풀 크기 (기본값: 8)
key_prefix: "cr:resp:" # 키 네임스페이스 접두사 (기본값: "cr:resp:")
connect_timeout_ms: 3000 # 연결 타임아웃 (기본값: 3000)
command_timeout_ms: 1000 # 명령별 타임아웃 (기본값: 1000)
tls: false # TLS 사용 (기본값: false)
fallback_to_memory: true # 실패 시 인메모리로 폴백 (기본값: true)
자격 증명: URL userinfo에 넣고 URL 전체를 환경 변수 참조로 전달하세요. 예를 들어 url: "${REDIS_URL}"로 두고 REDIS_URL=rediss://:$PASSWORD@cache.example.com:6380을 설정합니다. 배포 차트는 자신이 참조하되 직접 만들지는 않는 Secret에서 이 변수를 주입합니다. continuum-router config validate는 자격 증명이 설정 파일에 들어 있으면 경고하고, 평문 redis:// 엔드포인트로 오갈 때 AUTH 교환이 네트워크를 그대로 지나가므로 다시 경고합니다. 라우터 로그는 항상 비밀번호를 마스킹합니다.
장애 시 동작: fallback_to_memory: true(기본값)에서 Redis에 연결할 수 없으면 라우터는 경고를 로깅하고 replica별 인메모리 캐시에서 응답합니다. 백그라운드 헬스 모니터가 주기적으로 PING을 보내고 연결이 돌아오면 다시 전환합니다. 이것이 fail-open입니다. 라우터는 계속 서비스하고, Redis가 복구될 때까지 replica마다 캐시 내용이 갈라집니다. fail-closed를 원하면 fallback_to_memory: false로 설정하세요. 이 경우 캐시는 replica 로컬 계층으로 응답하는 대신 장애를 보고하고, 응답 캐시는 이를 로깅된 미스로 처리합니다. 요청은 여전히 백엔드까지 도달하지만 Redis가 돌아올 때까지 아무것도 캐시되지 않습니다.
분산 속도 제한(rate_limiting.storage: redis)은 fail-open이며 끌 수 없습니다. 장애가 나면 각 replica가 자체 인메모리 bucket으로 폴백하므로, 실효 플릿 속도는 설정한 제한값에 replica 수를 곱한 값이 됩니다. 속도 제한은 그 저하된 상한을 기준으로 잡으세요. 이 비대칭은 의도한 것입니다. 캐시 미스는 백엔드 호출 한 번으로 끝나지만, 거부된 요청은 사용자가 요청 자체를 잃는 것입니다.
키 안전성: clear()는 FLUSHDB 대신 설정된 접두사 패턴으로 SCAN + DEL을 사용합니다. 접두사와 일치하는 키만 제거되므로 다른 애플리케이션과 Redis 인스턴스를 안전하게 공유할 수 있습니다.
S3 호환 계층형 백엔드¶
계층형 백엔드는 빠른 L1 캐시와 S3 호환 API로 지원되는 거의 무제한의 L2 캐시를 결합합니다. 대형 응답은 L1의 퇴거 압력을 방지하기 위해 L2에만 저장됩니다. L2 히트 시 값은 선택적으로 L1으로 프로모션되어 이후 접근 속도를 높입니다.
빌드 요구 사항: 바이너리가 s3-cache Cargo 기능으로 컴파일되어야 합니다:
계층형 캐시 쓰기 경로:
- 모든
set()은 L2 (S3)에 무조건 씁니다. - 값이
l1.max_value_size보다 작으면 L1에도 씁니다. - 임계값을 초과하는 값은 L2 전용으로 강등되며, 강등 카운터가 증가합니다.
계층형 캐시 읽기 경로:
- L1을 먼저 확인합니다. 히트 시 즉시 값을 반환합니다.
- L1 미스 시 L2 (S3)를 조회합니다.
- L2 히트 시,
tiered.promote_on_hit이true이고 값이 L1에 맞으면tiered.l1_promotion_ttl을 TTL로 L1에 프로모션합니다.
TTL 적용: 각 S3 객체는 expires-at 메타데이터 필드(Unix 타임스탬프)를 가집니다. GET 시 타임스탬프가 지나면 객체가 지연 삭제되고 캐시 미스가 반환됩니다. l2.ttl_override를 사용하여 L2 항목을 전역 ttl보다 오래 유지할 수 있습니다.
설정:
response_cache:
enabled: true
backend: tiered # 계층형 L1/L2 백엔드 선택
ttl: "5m" # 두 티어 모두의 기본 TTL
l1: # L1 (핫) 티어 — 빠르고 제한됨
type: memory # "memory" 또는 "redis" (redis는 redis-cache 기능 필요)
max_value_size: 1048576 # 이보다 큰 값은 L2 전용 (기본값: 1 MiB)
l2: # L2 (웜) 티어 — S3
type: s3
endpoint: "https://s3.example.com:8080"
bucket: "llm-response-cache"
key_prefix: "response-cache/" # 객체 키 접두사 (기본값: "response-cache/")
region: "us-east-1" # 리전 문자열 (기본값: "us-east-1")
access_key: "${S3_ACCESS_KEY}" # 환경 변수 확장 지원
secret_key: "${S3_SECRET_KEY}"
ttl_override: "24h" # 선택적: L2 항목을 전역 TTL보다 오래 유지
tiered: # 프로모션/강등 동작
promote_on_hit: true # L2 히트를 L1으로 프로모션 (기본값: true)
l1_promotion_ttl: "5m" # 프로모션된 항목의 TTL (기본값: "5m")
메트릭: s3-cache 빌드는 L1/L2 히트율, 프로모션/강등 카운트, S3 작업 지연 히스토그램(continuum_cache_s3_latency_seconds)에 대한 Prometheus 카운터를 추가합니다.
Redis를 L1으로 사용: redis-cache와 s3-cache 기능을 결합하여 Redis를 L1 티어로 사용할 수 있습니다:
response_cache:
backend: tiered
l1:
type: redis # 인스턴스 간 공유를 위해 Redis를 L1으로 사용
max_value_size: 1048576
redis: # l1에서 재사용되는 공유 Redis 설정
url: "redis://localhost:6379"
pool_size: 8
l2:
type: s3
endpoint: "https://s3.example.com:8080"
bucket: "llm-response-cache"
access_key: "${S3_ACCESS_KEY}"
secret_key: "${S3_SECRET_KEY}"
제한 사항¶
- 스트리밍 캐시는 OpenAI 호환 백엔드에서만 지원됩니다. Anthropic 및 Gemini 네이티브 스트리밍 형식은 캐싱되지 않습니다.
- Anthropic(
/anthropic/v1/messages) 및 OpenAI(/v1/chat/completions) 엔드포인트는response_cache.enabled: true일 때 동일한 공유 캐시 인스턴스를 유지합니다. - Redis 백엔드를 설정하지 않으면 캐시는 인메모리에 저장되며 서버 재시작 후 유지되지 않습니다.
KV 캐시 인덱스 섹션¶
정밀한 오버랩 점수 기반 라우팅을 위한 실시간 KV 캐시 상태 추적을 활성화합니다. vLLM과 SGLang은 KV 이벤트를 ZMQ로 continuum-kv-listener에 게시합니다. listener는 block chain을 detokenize하고 라우터와 호환되는 prefix hash를 계산한 뒤 백엔드별 SSE 스트림을 노출합니다. 라우터는 이러한 listener 스트림을 구독하고, 모든 백엔드의 캐시 상태 인덱스를 유지하며, 이 데이터를 사용하여 관련 KV 캐시 데이터를 가지고 있을 가능성이 가장 높은 백엔드로 요청을 라우팅합니다.
kv_cache_index:
enabled: true # KV 캐시 인덱스 활성화 (기본값: false)
backend: memory # 인덱스 백엔드: "memory" (기본값) 또는 "redis"
max_entries: 100000 # 추적할 최대 접두사 해시 항목 수
entry_ttl_seconds: 600 # 인덱스 항목 TTL (초)
scoring: # 백엔드 선택을 위한 스코어링 가중치
overlap_weight: 0.6 # 캐시 오버랩 신호 가중치 (0.0-1.0)
load_weight: 0.3 # 백엔드 부하 신호 가중치 (0.0-1.0)
health_weight: 0.1 # 백엔드 헬스 신호 가중치 (0.0-1.0)
min_overlap_threshold: 0.3 # KV 인식 라우팅 활성화 최소 오버랩
gpu_tier_weight: 1.0 # GPU 상주(핫) 캐시 데이터에 대한 계층 배수
storage_tier_weight: 0.6 # 스토리지 오프로드(웜) 캐시 데이터에 대한 계층 배수
storage_offloading: # 계층형 스토리지 인식 (GPU 핫 / 스토리지 웜)
enabled: false # 스토리지 계층 추적 활성화 (기본값: false)
treat_eviction_as_offload: true # 퇴거를 웜 스토리지로의 오프로드로 처리 (기본값: true)
event_sources: # continuum-kv-listener SSE 엔드포인트
- backend_name: "vllm-1"
endpoint: "http://kv-listener.internal:7817/events/vllm-1"
reconnect_interval_ms: 5000
- backend_name: "vllm-2"
endpoint: "http://kv-listener.internal:7817/events/vllm-2"
reconnect_interval_ms: 5000
동작 방식¶
- 이벤트 소비: 엔진은 msgpack KV block 이벤트를 ZMQ로
continuum-kv-listener에 게시하고, 라우터는 listener의 SSE 스트림을 구독합니다. 각 SSE 이벤트는 접두사 해시와 토큰 수를 포함한CacheCreated,CacheEvicted,CacheOffloaded,CacheReloaded, 또는CachePurged액션을 보고합니다. - 인덱스 업데이트: 이벤트가 접두사 해시를 캐시된 토큰 수와 스토리지 계층(
GpuHot또는StorageWarm)과 함께 백엔드에 매핑하는 인메모리(또는 Redis 기반) 인덱스를 업데이트합니다. - 스코어링: 각 요청 시 오버랩 스코어러가 요청의 접두사 키에 대한 인덱스를 쿼리하고 캐시 오버랩(계층 배수로 조정됨), 백엔드 부하, 백엔드 헬스를 결합한 가중 점수를 계산합니다.
- 라우팅 결정: 최고 점수가
min_overlap_threshold를 초과하면 해당 백엔드를 선택합니다. 그렇지 않으면 설정된 선택 전략(예:PrefixAwareHash)이 폴백으로 사용됩니다.
스코어링 공식¶
final_score = overlap_weight * (raw_overlap * tier_multiplier)
+ load_weight * (1 - load_ratio)
+ health_weight * health_score
여기서:
raw_overlap= backend_token_count / max_token_count_across_backends (0.0 ~ 1.0)tier_multiplier= GPU 상주 데이터는gpu_tier_weight, 오프로드 데이터는storage_tier_weightload_ratio= in_flight_requests / max_in_flight (0.0 ~ 1.0)health_score= 백엔드 success_rate (0.0 ~ 1.0)
storage_offloading.enabled가 false이면 모든 데이터가 GpuHot으로 처리되며 tier_multiplier는 항상 gpu_tier_weight(기본값: 1.0)입니다.
설정 옵션¶
| 옵션 | 기본값 | 설명 |
|---|---|---|
enabled | false | KV 캐시 인덱스 활성화 여부 |
backend | "memory" | 인덱스 저장 백엔드: "memory" 또는 "redis". Redis는 redis-cache 빌드 기능과 response_cache.redis의 공유 Redis 설정이 필요. |
max_entries | 100000 | 최대 접두사 해시 항목 수. 용량 도달 시 LRU 퇴거 적용. |
entry_ttl_seconds | 600 | 인덱스 항목의 TTL. 오래된 항목은 주기적으로 정리됨. |
scoring.overlap_weight | 0.6 | 캐시 오버랩 신호의 가중치. 높은 값은 캐시 친화성을 선호. |
scoring.load_weight | 0.3 | 백엔드 부하 신호의 가중치. 높은 값은 부하가 적은 백엔드를 선호. |
scoring.health_weight | 0.1 | 백엔드 헬스 신호의 가중치. 높은 값은 더 건강한 백엔드를 선호. |
scoring.min_overlap_threshold | 0.3 | KV 인식 라우팅 활성화를 위한 최소 오버랩 점수. 이를 초과하는 백엔드가 없으면 폴백 전략 사용. |
scoring.gpu_tier_weight | 1.0 | 데이터가 GPU 상주(GpuHot)일 때 오버랩 점수에 적용되는 배수. storage_offloading.enabled가 true일 때만 유효. |
scoring.storage_tier_weight | 0.6 | 데이터가 스토리지 오프로드(StorageWarm)일 때 오버랩 점수에 적용되는 배수. storage_offloading.enabled가 true일 때만 유효. |
storage_offloading.enabled | false | 스토리지 계층 추적 활성화. false이면 퇴거 이벤트가 항목을 완전히 제거. |
storage_offloading.treat_eviction_as_offload | true | true이면 cache_evicted 이벤트가 항목을 제거하는 대신 GpuHot에서 StorageWarm으로 다운그레이드. storage_offloading.enabled가 true일 때만 유효. |
event_sources[].backend_name | -- | 백엔드 이름 (설정된 백엔드 이름과 일치해야 함) |
event_sources[].endpoint | -- | 해당 백엔드의 continuum-kv-listener SSE 엔드포인트 URL (http, https, ws 또는 wss) |
event_sources[].reconnect_interval_ms | 5000 | 연결 끊김 후 재연결 지연 |
Redis 백엔드¶
backend: redis일 때, KV 인덱스는 response_cache.redis의 공유 Redis 연결을 사용합니다. 여러 라우터 인스턴스가 동일한 인덱스를 공유하여 전체 플릿에서 일관된 라우팅 결정을 내릴 수 있습니다.
response_cache:
redis:
url: "redis://localhost:6379"
pool_size: 8
kv_cache_index:
enabled: true
backend: redis # response_cache.redis의 Redis 설정 사용
Admin 엔드포인트¶
GET /admin/kv-index/stats— 인덱스 크기, 이벤트 비율, 연결 상태, 스코어링 분포GET /admin/kv-index/backends— 백엔드별 캐시 상태 요약POST /admin/kv-index/clear— 인덱스 초기화 (디버깅용)
전체 응답 스키마는 Admin API를 참조하세요.
제한 사항¶
- 이벤트 소스는 현재 vLLM 호환 SSE 엔드포인트만 지원합니다.
backend를memory와redis간에 변경하려면 재시작이 필요합니다.- KV 인덱스는 선택적 개선 기능이며, 사용 불가 시에도 라우팅은 정상 작동합니다.
로깅 섹션¶
로깅 출력을 설정합니다:
로그 레벨:
trace: 매우 상세, 모든 세부 정보 포함debug: 상세한 디버깅 정보info: 일반적인 운영 정보warn: 경고 메시지 및 잠재적 문제error: 오류 조건만
로그 형식:
json: 구조화된 JSON 로깅 (프로덕션 권장)pretty: 사람이 읽기 쉬운 형식 (개발에 적합)