메트릭 및 모니터링¶
이 문서는 Continuum Router의 메트릭 및 모니터링 기능을 설명합니다.
목차¶
개요¶
Continuum Router는 시스템 상태, 성능, 사용 패턴을 다룰 수 있도록 Prometheus 호환 메트릭을 제공합니다. 메트릭 시스템은 다음과 같이 설계되었습니다:
- 가벼움: 최소한의 성능 오버헤드
- 넓은 범위: HTTP, 백엔드, 라우팅, 모델, 스트리밍, 캐시 서브시스템 전반을 포괄
- 프로덕션 준비: 카디널리티 제한 및 적절한 레이블링 포함
- 쉬운 통합: 표준 Prometheus/Grafana 설정과 작동
전체 Prometheus 스택을 구축하지 않고도 재시작 전후의 메트릭 이력을 보존하려면 영속 메트릭 로그를 참고하세요. 레지스트리를 로컬 SQLite 저장소에 스냅샷으로 저장하고 GET /admin/metrics/history를 통해 최근 이력을 조회할 수 있습니다.
메트릭은 얼마나 많이, 얼마나 자주를 답합니다. 요청 하나의 인과관계, 즉 어떤 백엔드가 선택됐고 몇 번 시도했으며 각 시도가 왜 그렇게 끝났는지를 보려면 분산 트레이스 내보내기를 참고하세요. 라우터의 스팬을 OTLP로 OpenTelemetry 컬렉터에 보내는 기능입니다.
빠른 시작¶
1. 메트릭 활성화¶
메트릭은 기본적으로 활성화되지만 metrics.auth를 설정하기 전에는 /metrics가 401 Unauthorized를 반환합니다. Prometheus에는 Bearer 토큰을 사용하십시오:
2. Prometheus 설정¶
prometheus.yml에 라우터를 타겟으로 추가:
scrape_configs:
- job_name: 'continuum-router'
static_configs:
- targets: ['localhost:9090']
scrape_interval: 15s
authorization:
credentials: '<metrics-token>'
3. Grafana 대시보드 가져오기¶
monitoring/grafana/dashboards/router-overview.json에서 제공된 대시보드를 가져옵니다.
설정¶
메트릭 설정은 메인 설정 파일을 통해 수행됩니다:
metrics:
enabled: true
port: 9090 # 전용 메트릭 리스너, 0이면 끔
# bind_address: 0.0.0.0 # 기본값: server.bind_address 첫 번째 TCP 항목의 호스트
path: /metrics
auth:
method: bearer
bearer_token: "${METRICS_TOKEN}"
max_model_labels: 1000
# 이 프로필에서는 외부 Prometheus가 영속 저장소입니다.
persistence:
enabled: false
max_model_labels:model_usage_total,model_tokens_processed,llm_tokens_total에서 추적하는 고유 모델 수의 상한입니다(기본값1000, 최댓값1000, 최솟값10). 시작 시 적용되므로 변경하려면 재시작해야 합니다. 상한을 초과한 모델은 해당 메트릭에 기록되지 않으며, 상한을 낮춰도 이미 추적 중인 모델은 제거되지 않고 계속 내보내집니다.max_backend_labels및cardinality_limits(더 이상 사용되지 않음): 하위 호환성을 위해 파싱만 수행하고 무시됩니다(기본값이 아닌 값을 설정하면continuum-router config validate가 경고합니다).cardinality_limits.max_models는max_model_labels와 중복되고, 샘플링 필드는 구현된 적이 없으며, 백엔드는 런타임에 등록될 수 있으므로 백엔드 레이블 상한은1000으로 고정됩니다.latency_buckets(더 이상 사용되지 않음): 하위 호환성을 위해 파싱만 수행하고 무시됩니다(기본값과 다른 값을 설정하면continuum-router config validate가 경고합니다).http_request_duration_seconds는 프로세스마다 한 번, 대시보드와le선택자가 의존하는 고정 버킷 구성(0.01초부터1200초까지)으로 등록되므로 설정으로 버킷을 바꿀 수 없습니다.
auth를 생략하면 접근을 거부합니다. 신뢰할 수 있는 네트워크에서 인증 없는 엔드포인트를 의도적으로 노출하려면 auth.method: none을 명시하십시오. 시작 로그는 이 선택을 UNPROTECTED로 표시하고 인증을 켜는 정확한 설정을 안내합니다.
설정에 metrics: 섹션이 없으면 기본값이 적용됩니다. 엔드포인트는 메인 리스너와 9090 포트의 전용 메트릭 리스너에서 /metrics로 제공되고(auth를 설정하기 전까지는 접근 거부, 포트가 사용 중이면 경고를 남기고 이 리스너 없이 시작), 요청 메트릭이 기록되며, 영속 메트릭 로그는 꺼진 상태로 유지됩니다. 메트릭을 끄려면 metrics.enabled: false로 설정하십시오.
전용 메트릭 리스너¶
메트릭이 켜져 있으면 라우터는 metrics.port(기본값 9090)에 두 번째 TCP 리스너를 바인딩합니다. 이 리스너는 메인 리스너와 같은 metrics.auth 뒤에서 메트릭 경로만 제공합니다. API, 관리자, 상태 확인, WebUI 경로는 이 포트에서 404를 반환하므로, 네트워크 정책으로 모니터링 네임스페이스만 이 포트에 접근하게 해도 API는 노출되지 않습니다. 함께 배포되는 Kubernetes 매니페스트와 Helm 차트는 이 포트를 스크레이프합니다.
- 호스트:
metrics.bind_address(포트 없는 IP 주소 또는localhost)를 설정하면 그 값을 쓰고, 설정하지 않으면server.bind_address의 첫 번째 TCP 항목의 호스트를 씁니다.server.bind_address에unix:항목만 있고metrics.bind_address가 없으면, 설정하지 않은 TCP 포트를 열지 않도록 리스너를 시작하지 않고 경고를 남깁니다(config validate도 이를 보고합니다). - 바인딩 실패: 포트를 바인딩할 수 없을 때의 동작은 리스너를 직접 설정했는지에 따라 달라집니다.
metrics.port나metrics.bind_address를 설정한 경우:metrics.port와 주소를 밝힌 오류와 함께 시작이 실패합니다. 이 포트를 스크레이프하는 배포가 모니터링 없이 올라오지 않게 하기 위해서입니다. 함께 배포되는 Helm values와 Kubernetes 매니페스트,production-ha설정 템플릿은port: 9090을 설정합니다. 이전 릴리스의config show나 Admin API 내보내기로 저장한 파일에는port: 9090이 들어 있으며, 이는 설정한 것으로 간주합니다.- 둘 다 설정하지 않은 경우: 주소와 OS 오류를 담은 경고를 남기고 리스너 없이 시작합니다.
/metrics는 메인 리스너에 계속 남습니다. 이 실패를 치명적 오류로 만들려면metrics.port를 설정하고, 리스너를 끄고 경고를 없애려면metrics.port: 0으로 설정하십시오.
- 포트 충돌:
metrics.port가 메인 TCP 리스너의 포트와 같으면, 그 리스너에서 이미/metrics를 제공하므로, 두 번째 리스너를 시작하지 않습니다. AppProxy ROUTER 모드에서appproxy_router.traffic_port가metrics.port와 같으면 시작 시 거부됩니다. - 끄기: 이 변경 이전 릴리스처럼 메인 리스너에서만 메트릭을 제공하려면
metrics.port: 0으로 설정하십시오. - 메인 리스너: 기존 스크레이프 설정을 위해
/metrics는 메인 리스너에도 계속 남습니다. 메인 리스너의 레이트 리밋 예외도 그대로입니다. 전용 리스너에는 레이트 리미터가 없으므로,metrics.auth와 네트워크 정책으로 보호하십시오. - 수명 주기: 리스너는 서버와 함께 시작하고, 종료 시 API 리스너와 함께 드레인됩니다.
metrics.enabled,metrics.port,metrics.bind_address는 시작 시 한 번만 읽으며, 이 값을 바꾸는 핫 리로드는 재시작이 필요하다고 로그에 남깁니다.
환경 변수¶
환경 변수를 사용하여 메트릭을 설정할 수도 있습니다:
# 메트릭 활성화/비활성화
METRICS_ENABLED=true
# 메트릭 엔드포인트 변경
METRICS_ENDPOINT=/custom/metrics
# 선택적 메트릭 활성화
METRICS_ENABLE_BODY_SIZE=true
사용 가능한 메트릭¶
HTTP 메트릭¶
| 메트릭 | 유형 | 설명 | 레이블 |
|---|---|---|---|
http_requests_total | Counter | 총 HTTP 요청 수 | method, endpoint, status_code, backend |
http_request_duration_seconds | Histogram | 요청 지연 시간 | method, endpoint, backend |
http_active_connections | Gauge | 현재 활성 요청 | backend |
http_request_size_bytes | Histogram | 요청 본문 크기 | endpoint |
http_response_size_bytes | Histogram | 응답 본문 크기 | endpoint |
http_request_duration_seconds의 버킷은 0.01, 0.05, 0.1, 0.25, 0.5, 1, 2.5, 5, 10, 30, 60, 120, 300, 600, 1200초입니다. 10초를 넘는 버킷 덕분에 오래 걸리는 모델 호출도 +Inf에만 쌓이지 않고 구분되며, 가장 큰 버킷은 continuum_backend_response_wait_seconds와 같은 20분입니다. 측정은 핸들러가 응답 헤더를 반환할 때 끝납니다. 따라서 스트리밍 응답에서는 본문을 스트리밍하는 시간이 포함되지 않습니다.
endpoint 레이블은 경로의 첫 세그먼트를 사용하며, /v1/과 /admin/ 아래에서는 처음 두 세그먼트를 사용합니다. Anthropic Messages API는 /anthropic/v1/messages와 /v1/messages 두 곳에서 제공되며, 두 마운트 지점 모두(count_tokens 포함) endpoint="/v1/messages"로 보고합니다.
백엔드 선택은 핸들러 내부에서 수행되고 모든 라우트가 백엔드를 선택하는 것은 아니므로 HTTP 미들웨어는 backend="unknown"으로 보고합니다. 백엔드별 트래픽과 부하는 백엔드 및 prefix-routing 메트릭 패밀리에서 확인할 수 있으며 클라이언트가 제공한 헤더는 메트릭 귀속에 신뢰하지 않습니다.
오류 및 재시도 메트릭¶
| 메트릭 | 유형 | 설명 | 레이블 |
|---|---|---|---|
errors_total | Counter | 요청 미들웨어가 관찰한 HTTP 클라이언트/서버 오류 | error_type, backend |
retry_attempts_total | Counter | 최초 시도 이후의 백엔드 재시도 | backend, attempt_number |
retry_success_total | Counter | 재시도에서 성공한 요청 | backend |
retry_exhausted_total | Counter | 한 번 이상 백엔드를 재시도한 뒤 실패한 요청 | backend |
timeout_errors_total | Counter | 최종 백엔드 또는 요청 타임아웃 응답 | operation, backend |
provider_failures_total | Counter | 모든 경로, 재시도, 폴백 홉에서 HTTP 오류 상태로 응답한 백엔드 홉. status는 400에서 599까지는 정확한 코드이고 그 밖에서는 other이며 backend는 백엔드 레이블 카디널리티 한도를 공유합니다. 마스킹된 프로바이더 메시지가 포함된 최근 실패는 GET /admin/provider-failures에서 볼 수 있습니다 | backend, status |
서킷 브레이커 메트릭¶
metrics 기능과 circuit_breaker가 모두 활성화되면 일반 프록시 트래픽이 채웁니다.
| 메트릭 | 유형 | 설명 | 레이블 |
|---|---|---|---|
circuit_breaker_state | Gauge | 현재 백엔드별 서킷 상태(0 Closed, 1 Open, 2 HalfOpen) | backend |
circuit_breaker_failures_total | Counter | 백엔드 서킷에 기록된 실패 | backend, error_type |
circuit_breaker_successes_total | Counter | 백엔드 서킷에 기록된 성공 | backend |
circuit_breaker_transitions_total | Counter | 서킷 상태 전환 | backend, from_state, to_state, reason |
error_type은 circuit_breaker.latency_as_failure 임계값을 넘은 성공에 대해 slow이며, 그에 대응하는 전환은 reason="slow"를 담습니다. 나머지 reason 값은 cardinality가 제한되어 있습니다: failure_threshold, failure_rate, timeout, probe, manual.
요청 파라미터 정책 메트릭¶
| 메트릭 | 유형 | 설명 | 레이블 |
|---|---|---|---|
request_param_policy_total | Counter | 적용된 파라미터 동작과 거부된 정책 결정 | protocol, parameter, action, outcome |
request_param_policy_composition_total | Counter | 로컬 정책과 Hub 정책의 조합 | protocol, source, outcome |
두 메트릭 패밀리는 사전에 정의된 고정 레이블 집합만 사용합니다. source는 none, local, hub, intersection 중 하나이며 요청 값, 모델 ID, 티어 ID, 키 ID, 정책 커서는 어느 쪽에도 내보내지 않습니다.
백엔드 메트릭¶
| 메트릭 | 유형 | 설명 | 레이블 |
|---|---|---|---|
backend_healthy | Gauge | 백엔드 헬스(1=정상, 0=정상 아님), URL 레이블은 마스킹됨 | backend, url |
backend_health_status | Gauge | 백엔드 헬스를 숫자 enum으로 표현(0=알 수 없음, 1=정상, 2=워밍업, 3=비정상). 워밍업 중인 백엔드와 죽은 백엔드를 구분할 수 있습니다. URL 레이블은 마스킹됨 | backend, url |
backend_health_check_duration_seconds | Histogram | 능동 헬스 체크 지속 시간 | backend |
backend_health_check_failures_total | Counter | 실패한 능동 헬스 체크 | backend |
backend_current_load | Gauge | 백엔드에 디스패치된 실시간 진행 중 요청 | backend |
backend_weight | Gauge | 설정된 부하 분산 가중치 | backend |
continuum_backend_cached_prompt_tokens_total | Counter | 요청을 처리한 백엔드가 프리픽스 캐시에서 읽었다고 보고한 프롬프트 토큰 | backend |
continuum_backend_ttft_seconds | Histogram | 프로바이더 디스패치부터 잰 백엔드별 첫 토큰까지의 시간(라우터 관측값) | backend, source (streamed_first_token, response_headers) |
continuum_request_priority_requests_total | Counter | 유효 vLLM 우선순위 클래스 및 HTTP 상태 클래스별 프록시 백엔드 요청 수 | priority (정수 문자열, unmanaged, other), status_class (2xx, 4xx, 5xx, error) |
continuum_request_priority_ttft_seconds | Histogram | 프로바이더 디스패치부터 잰 유효 vLLM 우선순위 클래스별 첫 토큰까지의 시간(라우터 관측값) | priority (정수 문자열, unmanaged, other) |
continuum_request_priority_skipped_total | Counter | 티어 request_priority 적용이 건너뛰어진 백엔드 홉 수 | reason (backend_type) |
continuum_backend_http_in_flight | Gauge | 스크레이프 시점에 샘플링한 백엔드의 진행 중 요청 수. 설정된 http_version으로 레이블링 | backend, http_version (auto, http1, http2) |
continuum_backend_response_wait_seconds | Histogram | 프록시 요청을 HTTP 클라이언트에 넘긴 시점부터 전송이 끝날 때(응답 헤더, 전송 오류, 또는 취소)까지의 시간 | backend, http_version, outcome (response, error, cancelled) |
continuum_response_format_inferred_dispatch_total | Counter | supports_response_format 선언 없이 백엔드 유형 추론으로 이 백엔드를 후보로 유지한 response_format 요청 수. 요청당 한 번 집계 | backend |
백엔드별 캐시 프롬프트 토큰과 TTFT¶
이 두 패밀리는 KV 오프로딩의 효과를 백엔드별로 보여 줍니다. metrics 기능이 있는 모든 빌드, 즉 기본 릴리스 빌드에서도 기록되며 Continuum Hub 에이전트에 의존하지 않습니다.
continuum_backend_cached_prompt_tokens_total은 백엔드가 자신이 처리한 요청에 대해 직접 보고한 캐시 프롬프트 토큰 수를 더합니다. OpenAI 호환 chat과 completions는 usage.prompt_tokens_details.cached_tokens, Responses API는 usage.input_tokens_details.cached_tokens, Anthropic은 usage.cache_read_input_tokens를 읽습니다. 캐시 생성 토큰은 더하지 않습니다. 요청은 버퍼링 응답이든 스트리밍이든 한 번만 셉니다. 로컬 응답 캐시 재생, 실패한 요청, usage 블록이 없는 요청은 아무것도 더하지 않습니다.
vLLM은 --enable-prompt-tokens-details를 켜야 캐시 토큰을 보고합니다
vLLM은 서버를 --enable-prompt-tokens-details(기본값 꺼짐)로 실행하지 않으면 prompt_tokens_details를 생략하므로 이 옵션 없이 띄운 vLLM 백엔드에서는 이 카운터가 0에 머뭅니다. vLLM이 보고하는 값은 로컬 GPU 프리픽스 캐시 적중과 KV 커넥터(예: LMCache) 적중을 합친 것입니다. 둘을 나눠 보려면 아래의 backend_engine_prefix_cache_hit_ratio와 backend_engine_external_prefix_cache_hit_ratio를 사용하세요.
continuum_backend_ttft_seconds는 프로바이더 디스패치 시점에서 시작하므로 로컬 라우팅, 요청 변환, 가드레일 사전 검사 시간은 포함하지 않습니다. source 레이블은 서로 다른 두 측정을 분리합니다.
streamed_first_token: 디스패치부터 스트리밍 응답의 첫 스트리밍 프레임까지. 콜드, 로컬 적중, 외부 적중 요청을 비교할 때 보는 시리즈입니다.response_headers: 디스패치부터 비스트리밍 호출의 업스트림 응답 헤더까지. 대부분의 엔진은 응답 전체를 생성한 뒤에야 응답하므로 이 값은 업스트림 전체 지연에 가깝고 비스트리밍 클라이언트가 처음으로 토큰을 보는 시점이기도 합니다.
버킷(25 ms부터 60 s)은 로컬 GPU 프리픽스 적중(수십 밀리초), CPU나 디스크에서 프리픽스를 다시 읽어 오는 KV 커넥터 적중(긴 컨텍스트에서 대략 0.1 s에서 2 s), 긴 컨텍스트의 콜드 프리필(수 초에서 수십 초)이 서로 다른 버킷에 들어가도록 나눴습니다.
TTFT는 라우터가 두 시점을 모두 관측한 경우에만 기록하며 관측하지 못한 시점을 다른 값으로 대신하지 않습니다. 실패한 시도(사용량 레코드의 결과가 completed, length, usage_missing이 아닌 dispatch나 홉)는 response_headers 샘플을 남기지 않고 그 사용량 레코드도 ttft_ms를 담지 않습니다. 스트림의 streamed_first_token 샘플은 스트림이 어떻게 끝났는지와 관계없습니다. 경로별 적용 범위는 다음과 같습니다.
| 경로 | 캐시 토큰 | TTFT |
|---|---|---|
/v1/chat/completions 스트리밍(Responses 전용 우회, 미드스트림 폴백, Unix 소켓 백엔드 포함) | 예 | streamed_first_token |
| 비스트리밍 chat, completions, Responses, Anthropic, Gemini 요청 | 예 | response_headers |
/anthropic/v1/messages 스트리밍(네이티브 패스스루, Bedrock Runtime, OpenAI 와이어 변환과 Responses 변환, Unix 소켓 백엔드 포함) | 예 | streamed_first_token |
/v1/responses 스트리밍 | 예 | streamed_first_token |
backend 레이블은 다른 요청 경로 백엔드 시리즈와 같은 정제기와 카디널리티 가드를 거치며 /metrics 스크레이프가 풀에 더 이상 없는 백엔드의 시리즈를 지우므로 레이블 집합은 설정된 백엔드 수 안에 머뭅니다.
# 백엔드별 초당 캐시 프롬프트 토큰
sum by (backend) (rate(continuum_backend_cached_prompt_tokens_total[5m]))
# 백엔드별 스트리밍 TTFT p50, p95
histogram_quantile(0.5, sum by (backend, le) (rate(continuum_backend_ttft_seconds_bucket{source="streamed_first_token"}[5m])))
histogram_quantile(0.95, sum by (backend, le) (rate(continuum_backend_ttft_seconds_bucket{source="streamed_first_token"}[5m])))
우선순위 클래스별 요청 수와 TTFT¶
서로 다른 request_priority 하한을 가진 여러 Hub 티어가 --scheduling-policy priority로 실행 중인 하나의 vllm 백엔드를 공유할 때, continuum_backend_ttft_seconds{backend,source}는 이들의 지연을 하나로 합산합니다. continuum_request_priority_requests_total{priority,status_class}와 continuum_request_priority_ttft_seconds{priority}는 요청에 적용된 유효 우선순위별로 요청 처리량과 라우터 관측 TTFT를 분리합니다.
priority는 10진수로 포맷된 유효 정수 우선순위(예: 대화형 티어는"-10", 배치 티어는"10"), 적용된 우선순위가 없을 때(티어request_priority가 없거나 우선순위 강제가 건너뛰어진 비vllm백엔드 홉)는"unmanaged", 이미 16개의 서로 다른 정수 우선순위 레이블이 등록된 이후의 추가 값은"other"입니다("unmanaged"는 16개의 동적 슬롯을 소비하지 않습니다).continuum_request_priority_requests_total의status_class는2xx,4xx,5xx,error중 하나입니다.continuum_request_priority_ttft_seconds는continuum_backend_ttft_seconds와 동일한 버킷 구성(25 ms부터 60 s)과 관측 규칙을 그대로 재사용합니다. 즉,continuum_backend_ttft_seconds가 샘플을 기록하는 동일한 완료 및 스트림 종료 지점에서만 샘플을 기록합니다.
# 우선순위 클래스 및 상태 클래스별 초당 요청 수
sum by (priority, status_class) (rate(continuum_request_priority_requests_total[5m]))
# 우선순위 클래스별 TTFT p50, p95
histogram_quantile(0.5, sum by (le, priority) (rate(continuum_request_priority_ttft_seconds_bucket[5m])))
histogram_quantile(0.95, sum by (le, priority) (rate(continuum_request_priority_ttft_seconds_bucket[5m])))
Grafana 패널 JSON (monitoring/grafana/dashboards/router-overview.json):
{
"datasource": "Prometheus",
"description": "Router-observed time to first token per effective vLLM priority class (continuum_request_priority_ttft_seconds). Compare interactive (for example -10) and batch (10) tiers sharing a vLLM backend with --scheduling-policy priority.",
"fieldConfig": {
"defaults": {
"unit": "s"
},
"overrides": []
},
"gridPos": {
"h": 8,
"w": 12,
"x": 0,
"y": 52
},
"id": 16,
"targets": [
{
"expr": "histogram_quantile(0.5, sum by (le, priority) (rate(continuum_request_priority_ttft_seconds_bucket[5m])))",
"legendFormat": "p50 priority={{priority}}",
"refId": "A"
},
{
"expr": "histogram_quantile(0.95, sum by (le, priority) (rate(continuum_request_priority_ttft_seconds_bucket[5m])))",
"legendFormat": "p95 priority={{priority}}",
"refId": "B"
}
],
"title": "TTFT by Request Priority Class (p50 / p95)",
"type": "timeseries"
}
HTTP 프로토콜과 스트림 제한 대기¶
이 두 시리즈는 패킷 캡처 없이도 HTTP/2 스트림 제한 정체를 볼 수 있게 해 줍니다. HTTP/2에서는 라우터가 한 호스트로 가는 모든 요청을 하나의 연결에 멀티플렉싱하며 서버의 SETTINGS_MAX_CONCURRENT_STREAMS를 넘는 요청은 스트림이 닫힐 때까지 HTTP 클라이언트 내부에서 대기합니다. HTTP 클라이언트는 이 대기를 보고하지 않으므로 두 시리즈 모두 라우터 측 관측값입니다. 이 패턴을 보이는 백엔드의 해결책은 http_version: http1입니다.
continuum_backend_http_in_flight는 백엔드 풀의 진행 중 요청 수(backend_current_load와 같은 값)이며 백엔드에 설정된http_version으로 레이블링됩니다.auto또는http2백엔드에서 트래픽이 계속 들어오는데도 값이 100, 128, 250 같은 딱 떨어지는 수에서 고정되면 그것이 서버의 스트림 제한입니다.continuum_backend_response_wait_seconds는 chat 및 completions 디스패치 경로(비스트리밍 chat, completions, embeddings, 그리고 폴백 홉과 미드스트림 폴백을 포함한 스트리밍 chat completions)에서 요청을 HTTP 클라이언트에 넘긴 시점부터 전송이 끝날 때까지의 시간입니다. 가득 찬 연결 뒤에 대기한 요청이+Inf로 묶이지 않고 구분되도록 버킷이 20분까지 이어집니다. 각 시도는 정확히 한 번 기록되며,continuum_backend_ttft_seconds와 달리 헤더가 도착하기 전에 실패하거나 중단된 시도도 기록합니다.outcome레이블은 전송이 끝난 방식을 나타냅니다:response: 응답 헤더가 도착한 경우(HTTP 상태 코드 무관).error: 전송 오류 또는 타임아웃이 발생한 경우.cancelled: 응답 헤더가 오기 전에 요청이 중단된 경우로, 주로 스트림 슬롯을 기다리던 중 클라이언트가 연결을 끊은 상황입니다.
backend 레이블은 다른 백엔드별 시리즈와 동일한 정제 및 카디널리티 가드를 따르며 스크레이프 시 더 이상 풀에 없는 백엔드의 시리즈는 제거됩니다. http_version과 outcome은 값이 세 개로 닫힌 집합입니다. 진행 중 요청 게이지는 스크레이프마다 누적이 아니라 설정(set)되므로 동시 스크레이프도 일관된 값을 읽으며 백엔드가 풀에서 빠졌거나 http_version이 바뀐 시리즈는 제거됩니다.
이 패턴에는 warn 로그가 함께 남습니다. HTTP/2를 협상할 수 있는 백엔드(http2, 또는 https:// URL의 auto)의 진행 중 요청이 100개에 도달하면 백엔드당 최대 1분에 한 번, 라우터가 해결책으로 http_version: http1을 언급합니다. 평문 http:// URL의 auto 백엔드는 항상 HTTP/1.1을 쓰므로 경고하지 않습니다.
# Backends whose in-flight count is flat at a stream limit
max_over_time(continuum_backend_http_in_flight{http_version!="http1"}[15m])
# 95th percentile wait for response headers, per backend and protocol
histogram_quantile(0.95, sum by (backend, http_version, le) (rate(continuum_backend_response_wait_seconds_bucket{outcome="response"}[5m])))
# Requests per second abandoned while waiting for headers
sum by (backend) (rate(continuum_backend_response_wait_seconds_count{outcome="cancelled"}[5m]))
# Requests per second that waited more than 60 s for headers
sum by (backend) (rate(continuum_backend_response_wait_seconds_bucket{le="+Inf"}[5m]))
- sum by (backend) (rate(continuum_backend_response_wait_seconds_bucket{le="60"}[5m]))
엔진 통계 메트릭¶
engine_stats.enabled: true일 때만, 그리고 엔진 통계를 지원하는 백엔드 유형(vllm, sglang, llamacpp, mlxcel, ollama, lmstudio)에 대해서만 노출됩니다. 폴러는 각 엔진 자체의 부하 또는 메트릭 엔드포인트(vLLM GET /metrics, SGLang GET /v1/loads와 /get_load·/metrics 폴백, llama.cpp는 /props의 기능 불리언으로 GET /slots·GET /metrics 선택, Ollama GET /api/ps, LM Studio GET /api/v0/models)를 읽어 백엔드당 하나의 스냅샷으로 정규화합니다.
| 메트릭 | 유형 | 설명 | 레이블 |
|---|---|---|---|
backend_engine_requests_running | Gauge | 엔진이 실행 중이라고 보고한 요청 수 | backend |
backend_engine_requests_waiting | Gauge | 엔진이 대기 중이라고 보고한 요청 수 | backend |
backend_engine_kv_cache_usage_ratio | Gauge | 엔진 KV 캐시 사용률(0..1) | backend |
backend_engine_kv_tokens_capacity | Gauge | 엔진 KV 캐시 토큰 용량 | backend |
backend_engine_generation_tokens_per_second | Gauge | 엔진이 보고한 생성 처리량 | backend |
backend_engine_prefix_cache_hit_ratio | Gauge | 엔진 프리픽스 캐시 적중률(0..1). vLLM은 로컬 GPU 프리픽스 캐시 | backend |
backend_engine_external_prefix_cache_hit_ratio | Gauge | KV 커넥터(외부) 프리픽스 캐시 적중률(0..1), vLLM 전용 | backend |
backend_engine_context_length | Gauge | 단일 요청이 쓸 수 있는 최대 컨텍스트(llama.cpp는 슬롯당 값) | backend |
backend_engine_slots_total | Gauge | 엔진이 알리는 병렬 처리 슬롯 수 | backend |
backend_engine_stats_scrape_success | Gauge | 최근 엔진 스크레이프 성공(1)/실패(0) | backend |
backend_engine_stats_scrape_duration_seconds | Gauge | 최근 엔진 스크레이프 소요 시간 | backend |
backend_engine_stats_age_seconds | Gauge | 라우터 스크레이프 시점의 스냅샷 나이 | backend |
backend_engine_prompt_tokens_total | Counter | 엔진이 처리한 프롬프트 토큰(리셋 감지로 미러링) | backend |
backend_engine_generation_tokens_total | Counter | 엔진이 생성한 토큰(리셋 감지로 미러링) | backend |
시리즈의 정확성은 다음 세 가지 표현 규칙으로 유지합니다:
- 0이 아닌 미노출(Absent, not zero): 엔진에 해당 개념이 없는 값의 시리즈는 아예 노출하지 않으며, 이는 어댑터별로 정적으로 결정합니다. 유일한 예외는 SGLang
/v1/loads입니다. 이 와이어 포맷은 0 값 필드를 생략하므로(msgspecomit_defaults), 어댑터는 누락된 숫자를 실제0으로 읽습니다. 덕분에 유휴 SGLang 백엔드도 시리즈가 사라지지 않고backend_engine_requests_running 0을 계속 보고합니다. - 스크레이프 실패 시 단일 지표만 노출: 스크레이프 오류가 발생하면 해당 백엔드는
backend_engine_stats_scrape_success 0만 노출하고, 다시 성공할 때까지 나머지 시리즈는 제거됩니다. 스크레이프 실패가 유휴 엔진으로 오인되는 일을 막기 위함입니다. 마지막 스냅샷은 내부에 유지되며 Admin API에서staleness_seconds가 증가하는 형태로 확인할 수 있습니다. - 멀티 랭크 집계: SGLang 데이터 병렬 엔진은 DP 랭크마다 항목을 하나씩 보고합니다. 여러 랭크는 개수·용량·처리량을 합산하고 사용률·적중률은 최댓값을 취해 하나의 스냅샷으로 병합합니다(포화된 랭크가 병목이 되기 때문입니다). Prometheus 응답 본문에 한 시리즈의 레이블 세트가 여러 개일 때도 같은 규칙을 적용합니다.
두 *_tokens_total 카운터는 리셋 감지를 적용합니다(직전 관측값보다 낮은 엔진 값은 재시작으로 간주하고 새 값을 재시작 이후 증가분으로 반영합니다). 리셋 감지는 엔진이 카운터로 선언한 시리즈에만 적용합니다. llama.cpp의 /metrics는 카운터와 게이지를 함께 내보내며 게이지는 정상적으로 감소할 수 있습니다.
로컬 대 외부 프리픽스 캐시 적중(vLLM)¶
vLLM은 토큰 단위로 집계하는 프리픽스 캐시 카운터 쌍을 두 개 노출하며, 어댑터는 각각을 별도의 누적 비율로 읽습니다:
- 로컬 (
backend_engine_prefix_cache_hit_ratio):vllm:prefix_cache_hits / vllm:prefix_cache_queries, 엔진 자체의 GPU 상주 프리픽스 캐시입니다. - 외부 (
backend_engine_external_prefix_cache_hit_ratio):vllm:external_prefix_cache_hits / vllm:external_prefix_cache_queries, vLLM이 LMCache 같은 KV 커넥터에 요청한 토큰 중 커넥터가 오프로드 계층에서 제공한 비율입니다. 여기의 적중은 GPU 캐시가 축출했거나 보유한 적이 없는 프리픽스를 엔진이 다시 계산하지 않고 커넥터가 다시 불러온 경우입니다.
외부 시리즈는 엔진이 커넥터 카운터를 노출할 때(vLLM이 KV 커넥터와 함께 실행될 때), 그리고 커넥터에 한 번이라도 질의한 뒤에만 존재합니다. 커넥터가 없는 엔진은 0이 아니라 외부 시리즈 자체를 내보내지 않습니다. SGLang에는 이에 대응하는 질의/적중 카운터 쌍이 없으므로(HiCache는 대신 cache_source별 sglang:cached_tokens_total을 노출함) 외부 게이지는 vLLM 전용입니다. SGLang의 호스트 및 스토리지 계층 적중은 SGLang 자체의 /metrics에서 수집하세요.
핫 리로드로 제거된 백엔드의 backend_engine_* 시리즈는 다음 스크레이프에서 사라지고, 추가된 백엔드는 한 폴링 주기 안에 보고를 시작합니다. 설정 항목은 config.yaml.example의 engine_stats를, JSON 스냅샷은 GET /admin/backends/{name}/engine-stats를 참고하세요.
서빙 엔진은 라우터와 무관하게 버그가 있거나 침해될 수 있으므로, 엔진의 응답은 신뢰할 수 없는 입력으로 처리합니다:
- 제한된 도달 범위: 폴러는 설정된 백엔드 URL, 또는 그와 같은 호스트로 해석되는
metrics_url만 조회하며 리다이렉트를 따르지 않습니다. 동일 호스트 규칙은 설정 로드 시점과 매 요청 직전에 두 번 검사하며, 백엔드 URL이 파싱되지 않으면 요청을 차단합니다(fail-closed). 메트릭 요청에는 백엔드 API 키가 함께 전송되므로metrics_url은https백엔드를 평문http로 낮출 수 없습니다.allow_external_metrics_url은 호스트 규칙만 완화할 뿐 HTTPS 다운그레이드 금지는 해제하지 않습니다. - 응답 본문 로그 미기록: 파싱 실패 시 구조적 분류와 위치만 기록하고 문제가 된 값은 기록하지 않습니다. 따라서 스크레이프 실패가 발생해도 엔진 응답 본문의 일부(특히 llama.cpp
/slots본문)가 라우터 로그로 유출되지 않습니다. 전송 실패는 자격 증명이 제거된 URL만 기록합니다. - 입력 크기 제한: 응답은
max_body_bytes한도 내에서 청크 단위로 읽고, Prometheus 파서는 본문당 샘플 수와 샘플당 레이블 수를 제한합니다. 스냅샷이 보관하는 엔진 자유 텍스트(모델 이름과 엔진 버전)는 길이를 제한하고 보이지 않는 제어 문자를 제거하며, 숫자 집계는 오버플로 대신 최댓값으로 포화(saturate)됩니다.
사용량 대조 메트릭¶
usage.reconciliation이 설정되고 활성화되어 있을 때만 노출됩니다. 대조기는 백엔드별, 윈도별로 사용량 원장이 기록한 출력 토큰과 엔진 생성 카운터의 증가분(위의 backend_engine_generation_tokens_total이 읽는 것과 같은 카운터)을 비교하므로, 이 시리즈를 통해 원장이 엔진의 실제 생성량을 정확히 집계하고 있는지 검증할 수 있습니다. 모든 패밀리는 엔진 패밀리와 같은 정제된 backend 레이블을 쓰며, 대조 대상에서 제외된 백엔드(backends[].reconciliation: false, 핫 리로드로 제거, 블록 비활성화)는 시리즈가 즉시 제거됩니다.
| 메트릭 | 유형 | 설명 | 레이블 |
|---|---|---|---|
usage_reconciliation_drift_ratio | Gauge | 백엔드의 마지막 within_tolerance 또는 drift 윈도의 (engine - ledger) / max(engine, 1). 양수면 원장이 적게 집계한 상태 | backend |
usage_reconciliation_ledger_tokens_total | Counter | 관측된 윈도에서 집계한 원장 출력 토큰(이월된 양은 중복 집계하지 않음) | backend |
usage_reconciliation_engine_tokens_total | Counter | 엔진 생성 카운터 증가분(엔진 재시작 뒤에는 전체 값) | backend |
usage_reconciliation_windows_total | Counter | result별로 마감된 윈도 수: within_tolerance, drift, no_engine_data, reset | backend, result |
usage_reconciliation_dropped_samples_total | Counter | 대조기의 8,192칸 큐가 가득 차 버려진 원장 샘플 수 | 없음 |
두 토큰 카운터는 1:1로 일치하지 않습니다. 첫 엔진 기준값(baseline) 이전에 기록된 원장 토큰은 기준값에 이미 해당 생성분이 포함되어 있으므로 원장 카운터에만 집계됩니다. 윈도 비교에는 usage_reconciliation_drift_ratio나 GET /admin/usage/reconciliation을 사용하세요. 해당 엔드포인트의 합계는 reset 윈도를 포함해 비교가 수행된 윈도만 합산합니다. reset 윈도는 엔진 재시작 직전 마지막 스크레이프부터 재시작 시점까지 생성된 토큰을 원장 쪽에만 집계하므로, 재시작할 때마다 합계가 그만큼 음수 방향으로 이동합니다. 생성 카운터가 없는 백엔드 유형(vllm, sglang, llamacpp, mlxcel 외 전부)은 no_engine_data 윈도만 집계합니다.
프롬프트 로깅 메트릭¶
prompt_logging.enabled가 true일 때 노출됩니다. 읽는 방법은 프롬프트 로깅 가이드를 참고하세요.
| 메트릭 | 유형 | 설명 | 레이블 |
|---|---|---|---|
prompt_log_records_total | Counter | 캡처 경로가 만든 레코드 수. result는 written, queue_full, sink_error | result |
prompt_log_export_total | Counter | 싱크별 레코드 수. result는 delivered, rejected, failed, dropped, malformed, pruned, consent_withdrawn, consent_key_absent | sink, result |
prompt_log_export_backlog_bytes | Gauge | 싱크가 아직 전달하지 않은 디스크의 레코드 바이트 수 | sink |
prompt_log_capture_disabled | Gauge | 시작 시 읽을 수 없는 커서 때문에 이 프로세스의 캡처와 내보내기가 꺼졌으면 1. 그 외에는 시리즈 없음 | reason(cursor_unreadable) |
모델 서비스 메트릭¶
| 메트릭 | 유형 | 설명 | 레이블 |
|---|---|---|---|
model_cache_size | Gauge | 집계 캐시의 모델 수 | -- |
model_cache_hits_total | Counter | 모델 집계 캐시 적중 | -- |
model_cache_misses_total | Counter | 모델 집계 캐시 미스 | -- |
model_refresh_failures_total | Counter | 실패한 백엔드 모델 조회 | -- |
model_refresh_duration_seconds | Histogram | 모델 목록 집계 1회 전체 소요 시간 | -- |
model_backend_fetch_duration_seconds | Histogram | 백엔드 1개의 모델 조회 소요 시간 | backend |
model_last_refresh_duration_seconds | Gauge | 가장 최근 집계의 소요 시간 | -- |
model_fetch_attempts_total | Counter | 재시도를 포함한 백엔드 모델 조회 시도 수 | -- |
model_empty_responses_total | Counter | 빈 모델 목록을 만든 갱신 수 | -- |
model_backends_unavailable_total | Counter | 어떤 백엔드도 모델을 반환하지 않은 갱신 수 | -- |
model_rate_limit_exceeded_total | Counter | 속도 제한으로 거부된 모델 엔드포인트 요청 수 | -- |
model_transient_errors_total | Counter | 재시도 가능한 백엔드별 조회 실패 | -- |
model_permanent_errors_total | Counter | 재시도 불가능한 백엔드별 조회 실패 | -- |
model_stale_while_revalidate_total | Counter | 갱신 중 스테일 캐시로 응답한 요청 수 | -- |
model_coalesced_requests_total | Counter | 진행 중인 집계에 합쳐진 요청 수 | -- |
model_background_refreshes_total | Counter | 백그라운드 재검증이 시작한 백엔드 팬아웃 수(제자리 재시도를 포함해 집계 패스마다 1) | -- |
model_background_refresh_successes_total | Counter | 결과를 저장하고 끝난 백그라운드 재검증 수(패스 수와 무관하게 재검증마다 1) | -- |
model_background_refresh_failures_total | Counter | 실패한 백그라운드 재검증 수(재검증마다 1) | -- |
model_singleflight_lock_acquired_total | Counter | 싱글플라이트용 집계 잠금 획득 수 | -- |
model_superseded_aggregations_total | Counter | 팬아웃 도중 풀 멤버십이나 캐시 무효화 epoch가 바뀌어 만료 상태로 저장된 갱신 수 | -- |
model_background_refreshes_total과 완료 카운터의 차이: 진행 중 대체된 패스는 제자리에서 재시도되며 재시도할 때마다model_background_refreshes_total이 다시 증가합니다. 반면model_background_refresh_successes_total과model_background_refresh_failures_total은 재검증 1회당 1씩만 증가합니다.model_superseded_aggregations_total: 저장 후 재확인(post-store re-check)에서 감지된 멤버십 변경도 포함합니다. 각 대체가 발생하면 즉시 재집계가 이어지지만, 세 번 연속 대체된 뒤에는 다음 재검증이 2초의 재가동 간격(re-arm interval)을 기다립니다.
느린 모델 목록 진단¶
model_refresh_duration_seconds는 집계가 느렸다는 사실을 알려주고 model_backend_fetch_duration_seconds는 어느 백엔드 때문에 느렸는지 알려줍니다. 모델 목록 팬아웃은 동시에 실행되므로 전체 소요 시간은 가장 느린 백엔드 하나와 같습니다.
# 5분 구간에서 갱신을 지배하는 백엔드, 95 백분위수
histogram_quantile(0.95, sum by (backend, le) (rate(model_backend_fetch_duration_seconds_bucket[5m])))
두 히스토그램 모두 버킷이 60초까지 있습니다. 시도별 request_timeout을 초과한 백엔드는 이름, 소요 시간, 시도 횟수를 담은 WARN 로그를 한 줄 남기므로, Prometheus 없이도 그리고 전역 로그 수준을 DEBUG로 올리지 않고도 같은 사실을 확인할 수 있습니다.
WARN Slow model fetch from backend backend="claude-bedrock" duration_ms=15503 attempts=3 threshold_ms=5000 outcome="error"
model_backend_fetch_duration_seconds는 백엔드 이름당 시계열 1개를 가집니다. 백엔드 이름은 운영자가 정의하고 설정으로 한정되므로 카디널리티는 보통 작습니다. 백엔드를 동적으로 등록하는 배포(AppProxy 레플리카)에서는 서로 다른 레플리카 이름마다 시계열 1개가 생긴다고 예상해야 합니다.
라우팅 텔레메트리는 아래의 실제 KV 캐시 및 스마트 라우팅 메트릭 패밀리에서 내보냅니다. 기존 범용 라우팅 및 모델 서비스 collector 중 지원되지 않는 항목은 등록하지 않습니다.
활성 스트리밍 응답 본문은 http_active_connections에 포함됩니다. 현재 범용 스트리밍 지속 시간 또는 응답 이후 오류 메트릭은 내보내지 않으며 미드스트림 폴백에는 아래의 전용 실제 메트릭이 있습니다.
미드스트림 폴백 메트릭¶
미드스트림 폴백 기능이 활성화된 경우(streaming.mid_stream_fallback.enabled: true)에 방출되는 메트릭입니다.
| 메트릭 | 유형 | 설명 | 레이블 |
|---|---|---|---|
streaming_fallback_total | Counter | 스트리밍 폴백 시도 총 횟수. mid-stream 경로가 SSE 응답 확정 전에 수행한 pre-stream 홉도 포함합니다. backend는 시도가 실패한 백엔드의 설정 이름입니다(recovered 시리즈에서는 스트림을 복구한 백엔드) | reason, backend |
streaming_fallback_success_total | Counter | 성공한 미드스트림 폴백 복구 | original_backend, fallback_backend |
streaming_fallback_accumulated_tokens | Histogram | 폴백 전까지 센 스트리밍 델타 이벤트 수. 라우터가 직접 세는 출력 수로, 청크마다 콘텐츠, 추론, 도구 인자 델타당 하나 | outcome (success, failure) |
streaming_fallback_total의 reason 레이블 값¶
| 값 | 설명 |
|---|---|
timeout | 백엔드 비활성 타임아웃 초과 |
connection_error | TCP/TLS 연결 오류 |
stream_read_error | 스트림에서 바이트 읽기 오류 |
stream_ended_unexpectedly | [DONE] 마커 없이 스트림 종료 |
too_many_stream_errors | 연속 오류 이벤트 임계값 도달 |
same_model_sibling | 실패한 시도를 같은 모델을 제공하는 다른 백엔드에서 계속함 (same_model_first) |
other | 기타 실패 원인 |
주요 PromQL 쿼리¶
# 미드스트림 폴백 비율
rate(streaming_fallback_total[5m])
# 폴백 복구 성공률
sum(rate(streaming_fallback_success_total[5m])) /
sum(rate(streaming_fallback_total[5m]))
# 폴백 트리거 시점의 누적 토큰 중앙값
histogram_quantile(0.5, rate(streaming_fallback_accumulated_tokens_bucket[5m]))
Responses 우회 메트릭¶
라우터가 /v1/chat/completions 요청을 업스트림 /v1/responses로 대신 보낸 횟수를 셉니다. 항상 등록되며 우회가 없으면 카운터가 0에 머뭅니다.
| 메트릭 | 유형 | 설명 | 레이블 |
|---|---|---|---|
responses_bridge_total | Counter | /v1/responses로 보낸 Chat Completions 요청 수 | reason |
responses_bridge_total의 reason 레이블 값¶
| 값 | 설명 |
|---|---|
responses_only_model | 모델 메타데이터에 responses_only가 설정되어 있거나, 백엔드가 Responses API만 제공합니다(Codex/ChatGPT 구독 백엔드). 해당 모델의 모든 요청이 우회됩니다. |
tools_with_reasoning | 모델은 Chat Completions로 제공되며 비어 있지 않은 tools 배열과 업스트림이 거절하는 reasoning effort가 함께 온 이 요청 형태만 우회됩니다. Reasoning Effort 문서의 '함수 도구와 reasoning effort 조합' 절을 참고하세요. |
두 값은 운영상 의미가 다릅니다. responses_only_model은 배포 구성 자체의 성질이며 해당 모델로 가는 트래픽에 비례합니다. tools_with_reasoning은 트래픽의 성질입니다. 에이전트 트래픽 중 얼마나 많은 비율이 변환 왕복 비용을 치르고 있는지 알려주며 OpenAI가 제약을 풀고 메타데이터 플래그를 끄면 저절로 0으로 떨어집니다.
# 우회된 트래픽 중 조건부 tools 우회가 차지하는 비율
sum(rate(responses_bridge_total{reason="tools_with_reasoning"}[5m])) /
sum(rate(responses_bridge_total[5m]))
도구 변환 메트릭¶
Anthropic과 Gemini 요청 변환은 function 도구만 전달합니다. Gemini 변환은 web_search, web_search_preview, code_interpreter와 Gemini 고유의 google_search, code_execution도 Gemini 내장 도구로 바꿉니다. 그 밖의 유형으로 선언한 도구는 업스트림 요청에서 제거되며, 유형과 함께 warn 수준으로 기록되고 이 카운터에 집계됩니다. 클라이언트에는 알리지 않으므로, OpenAI 내장 도구를 Anthropic이나 Gemini 백엔드에 보내는 클라이언트가 있다면 이 카운터를 확인하세요.
| 메트릭 | 유형 | 설명 | 레이블 |
|---|---|---|---|
tool_conversion_dropped_total | Counter | 대상 변환에 대응하는 도구가 없어 제거된 요청 도구 수 | target, tool_type |
tool_choice_unenforced_total | Counter | 대상 변환이 강제할 수 없었던 tool_choice 값의 수 | target, reason |
target은 anthropic 또는 gemini입니다. tool_type은 도구의 type이 web_search, web_search_preview, file_search, code_interpreter, computer_use_preview, image_generation, mcp, local_shell, custom 중 하나이면 그 값이고 그 밖의 값이면 other입니다.
tool_choice_unenforced_total은 라우터가 검증했지만 완전히 적용하지 못한 Gemini 네이티브 tool_choice를 집계합니다. 함수 선언 없이 required나 이름 지정 선택이 오면 reason이 forced_without_functions이고, none 요청에서 내장 도구를 제거해야 했다면 none_with_built_ins입니다. 두 경우 모두 클라이언트에는 정상 응답하므로, 이 카운터와 warn 로그가 유일한 신호입니다.
모델 실험 메트릭¶
모델 실험의 변형별 귀속 메트릭입니다. 전용 계열이므로 기존 요청, 지연 시간, 토큰 시계열의 레이블 집합은 그대로입니다. 레이블 값은 model_experiments 설정(실험 최대 32개, 실험당 변형 최대 16개)이나 닫힌 집합에서 옵니다.
| 메트릭 | 유형 | 설명 | 레이블 |
|---|---|---|---|
model_experiment_requests_total | Counter | 실험으로 해석된 요청 수 | experiment, variant, status(success, client_error, server_error), assignment(sticky, random) |
model_experiment_request_duration_seconds | Histogram | 응답 헤더까지의 시간 | experiment, variant |
model_experiment_tokens_total | Counter | prompt 및 completion 토큰 | experiment, variant, kind |
model_experiment_fallbacks_total | Counter | 모델 간 폴백 홉이 응답한 요청 수(fallback: cross_variant) | experiment, variant |
model_experiment_reserved_refusals_total | Counter | hide_variant_models 실험이 예약한 변형 모델을 지정해 거부된 요청 수 | experiment |
섀도 트래픽 메트릭¶
섀도 트래픽 미러링 메트릭입니다. 시작 시 등록되므로 섹션을 활성화하지 않은 라우터도 0 값으로 보고합니다. model과 backend는 shadow 설정과 선택된 백엔드에서 오므로, 카디널리티는 트래픽이 아니라 설정으로 제한됩니다.
| 메트릭 | 유형 | 설명 | 레이블 |
|---|---|---|---|
router_shadow_requests_total | Counter | 고려된 미러 수 | result (mirrored, skipped_size, shed, timeout, error) |
router_shadow_latency_seconds | Histogram | 섀도 디스패치부터 섀도 응답 종료까지의 시간 | model, backend |
router_shadow_usage_tokens_total | Counter | 섀도 응답이 보고한 토큰 수 | model, backend, kind (prompt, completion) |
폴백 메트릭¶
| 메트릭 | 유형 | 설명 | 레이블 |
|---|---|---|---|
fallback_attempts_total | Counter | 총 폴백 시도 | original_model, fallback_model, backend |
fallback_success_total | Counter | 성공한 폴백 | original_model, fallback_model, backend |
fallback_exhausted_total | Counter | 소진된 폴백 체인 | original_model |
cross_provider_fallback_total | Counter | 크로스 프로바이더 폴백 | original_model, fallback_model, original_provider, fallback_provider |
fallback_duration_seconds | Histogram | 폴백 작업 지속 시간 | original_model, success |
fallback_dial_bound_saturated_total | Counter | 백엔드별 다이얼 상한 (fallback.fallback_policy.max_concurrent_dials_per_backend)에서 대기 상한이 만료될 때까지 기다리다 해당 백엔드에 대한 홉이 실패한 폴백 홉 다이얼 수 | backend |
fallback_default_chain_hop_skipped_total | Counter | 요청 키의 allowed_models가 허용하지 않아 요청의 해석된 체인에서 건너뛴 fallback.default_chain 대상 수. 여러 단계에서 체인을 구체화하더라도 한 요청은 건너뛴 홉을 한 번만 집계합니다 | original_model, fallback_model |
fallback_quality_triggered_total | Counter | fallback.quality_check가 디스패치한 품질 홉 수, 판정된 문제별 (not_answered, refusal, truncated) | reason |
fallback_quality_checks_total | Counter | fallback.quality_check가 검사한 응답 수, 결과별 (passed, triggered, shadow, error, skipped, budget_spent) | outcome |
fallback_quality_hops_total | Counter | 디스패치된 품질 홉의 결과별 수 (served, failed, blocked). failed나 blocked 홉은 원래 응답을 제공합니다 | result |
fallback_content_policy_hops_total | Counter | fallback.fallback_policy.content_policy_fallback 실행 결과별 수. triggered는 트리거에 도달한 입력 가드레일 차단당 1회, hopped는 디스패치된 홉당 1회, 그리고 종료 결과 no_chain, blocked_again, budget_spent | result |
fallback_empty_reply_retry_total | Counter | fallback.empty_reply_retry가 검사한 빈 비스트리밍 채팅 응답 수, 결과별. hopped는 홉 응답이 출력 게이트를 통과해 제공됨, triggered는 홉을 디스패치했지만 실패, 시간 초과, 차단됨, skipped_no_hop은 허용된 다음 대상이 없거나 홉 상한에 도달함, budget_spent는 예산 소진, not_enforced는 서빙 백엔드가 트리거 적용 범위 밖(네이티브 프로바이더, unix:, replay: disabled) | result |
usage_quality_judgments_total | Counter | usage.quality_judgment가 평가한 요청 수(outcome 및 stream별) | outcome, stream |
usage_tokenize_corrections_total | Counter | usage.tokenize_correction이 처리한 스트림 수(응답 텍스트가 보관된 router_delta_count 레코드당 1회, result별) | result |
usage_quality_judgments_total의 모든 outcome 값을 합산하면 전체 대상 요청 수와 같습니다:
attached: 판정 결과가 사용량 레코드를 실은 푸시 배치에 포함됨(허브가 나중에 배치 전체를 거부해도 차감하지 않음).late: 레코드의 푸시 배치가 만들어진 뒤나 레코드가 버려진 뒤에 판정이 준비됨(late판정은 무작위 표본이 아니라 가장 느린 평가들입니다).sampled_out: 샘플링에서 제외됨.skipped: 판정할 내용이 없거나, 응답이 교체·차단되었거나, 사용량 레코드가 생성되지 않았거나, 응답 제공 전에 요청이 종료됨. 다음 경우도skipped로 기록됩니다:- 스트리밍 채팅 응답(
stream="true")에서 출력 가드레일이 응답을 자르거나 재작성한 경우, 미드스트림 폴백이 두 생성을 이어 붙인 경우, 오류가 발생한 경우, 또는 클라이언트가 연결을 끊은 경우. /anthropic/v1/messages응답에tool_use나server_tool_use블록이 있거나pause_turn으로 멈춘 경우./v1/responses응답과responses_only브리지 응답에서input에 사용자 턴이 없거나, 클라이언트가 실행하는 호출 항목이 있거나,failed또는cancelled로 끝난 경우.
- 스트리밍 채팅 응답(
shed:max_in_flight가 가득 차 건너뜀.failed: 결정 호출이 실패했거나 타임아웃됨.invalid: 확률 값이 없거나 유한하지 않거나, 프로토콜이 거부하는 판정임.unresolved_model: 결정 응답에 모델 이름이 없음.
usage_tokenize_corrections_total은 다음 result 값 중 하나를 기록합니다(보정된 레코드의 outcome은 바뀌지 않으므로 usage_missing 스트림은 여전히 usage_missing_total에 집계됩니다):
applied: 엔진/tokenize결과가 라우터 집계값을 대체함(token_source="engine_tokenize").timeout:timeout_ms가 먼저 경과해 라우터 집계값을 유지함.unsupported: 백엔드 유형에 엔진 토크나이저 경로가 없거나, URL이http(s)가 아니거나, 설정에서 제거됨.skipped_truncated: 보관된 텍스트가 100,000바이트 상한에 도달함.shed:max_concurrent가 가득 참.error: 전송 실패, 비 2xx 상태, 1 MiB 초과 또는 읽을 수 없는 응답, 또는 비정상적인 토큰 수(비어 있지 않은 텍스트에 대해0이거나 텍스트의 UTF-8 바이트 길이보다 큰 값). 라우터 집계값을 유지하며 백엔드, 엔진, 실패 종류를 담은WARN로그를 한 줄 남깁니다.
fallback_attempts_total의 backend 레이블은 실패한 시도가 다이얼한 백엔드이고 fallback_success_total의 backend 레이블은 응답한 홉이 다이얼한 백엔드입니다. not_dispatched 값은 모델의 모든 백엔드가 비정상인 경우처럼 다이얼 없이 체인이 넘어갔음을 뜻하며 unknown은 기록되지 않습니다.
original_model과 fallback_model 레이블은 체인에 설정된 모델 ID를 /와 :까지 그대로 담으므로 meta-llama/Llama-3.1-8B-Instruct와 meta-llama_Llama-3.1-8B-Instruct는 서로 다른 시계열로 남습니다. 값은 문자 경계에서 128바이트로 잘리며 체인 문자 집합 밖의 문자(공백, 제어 문자, 그 밖의 문장 부호)는 _로 내보냅니다. 이 메트릭들은 체인이 있는 모델과 그 체인이 가리키는 홉에 대해서만 기록되며, fallback.default_chain은 제한된 키가 도달할 수 있는 모델과 체인이 가리키는 대상을 짝지으므로 시계열 수는 설정으로 제한됩니다.
응답 캐시 메트릭¶
| 메트릭 | 유형 | 설명 | 레이블 |
|---|---|---|---|
continuum_response_cache_requests_total | Counter | 결과별 캐시 조회 | result (hit, miss, skip) |
continuum_response_cache_entries | Gauge | 현재 캐시된 항목 수 | -- |
continuum_response_cache_size_bytes | Gauge | 대략적인 캐시 메모리 사용량 | -- |
continuum_response_cache_evictions_total | Counter | LRU 퇴거 | -- |
continuum_response_cache_hit_rate | Gauge | 롤링 캐시 적중률 (0.0--1.0) | -- |
continuum_cache_backend_type | Gauge | 활성 캐시 백엔드 (1 = 활성) | backend (memory, redis) |
퇴거 카운터는 인메모리 LRU 저장소에서 정확합니다. Redis와 S3는 만료 또는 퇴거를 서버 측에서 수행하고 라우터에 퇴거 이벤트를 제공하지 않으므로, 해당 백엔드에서는 값을 임의로 만들지 않습니다.
Redis 캐시 백엔드 메트릭¶
이 메트릭은 Redis 캐시 백엔드가 활성화된 경우(backend: redis) 수집됩니다.
| 메트릭 | 유형 | 설명 | 레이블 |
|---|---|---|---|
continuum_cache_redis_connections_active | Gauge | 풀의 활성 Redis 연결 | -- |
continuum_cache_redis_connections_idle | Gauge | 풀의 유휴 Redis 연결 | -- |
continuum_cache_redis_latency_seconds | Histogram | Redis 작업 지연 시간 | operation (get, set, delete) |
continuum_cache_redis_errors_total | Counter | 유형별 Redis 오류 | type (connection, timeout, other) |
continuum_cache_fallback_active | Gauge | 인메모리 폴백이 활성화되었는지 여부 (0 또는 1) | -- |
KV 이벤트 컨슈머 메트릭¶
이 메트릭은 KV 이벤트 컨슈머가 활성화된 경우(src/infrastructure/kv_index/) 수집됩니다. vLLM, SGLang, TensorRT-LLM 리스너는 router salt echo를 사용해 모든 블록 이벤트를 detokenize하지 않고도 같은 접두사 identity를 유지할 수 있습니다. 모든 백엔드 레이블 값은 카디널리티 폭발을 방지하기 위해 정규화됩니다.
| 메트릭 | 유형 | 설명 | 레이블 |
|---|---|---|---|
continuum_kv_event_received_total | Counter | 각 백엔드에서 수신된 KV 캐시 이벤트 | backend |
continuum_kv_event_processed_total | Counter | 채널을 통해 성공적으로 전달된 KV 캐시 이벤트 | backend |
continuum_kv_event_dropped_total | Counter | 백프레셔로 인해 삭제된 KV 캐시 이벤트 | backend |
continuum_kv_consumer_connected | Gauge | KV 이벤트 컨슈머 연결 여부 (1 = 연결됨, 0 = 연결 끊김) | backend |
continuum_kv_consumer_reconnects_total | Counter | 각 백엔드 컨슈머의 총 재연결 시도 횟수 | backend |
Prefix 라우팅 메트릭¶
이 메트릭은 접두사 인식 스티키 라우팅 결정 및 백엔드 분포를 추적합니다.
| 메트릭 | 유형 | 설명 | 레이블 |
|---|---|---|---|
continuum_prefix_routing_requests_total | Counter | 전략 유형별 총 접두사 라우팅 결정 | strategy (prefix_hash, overflow, fallback, unknown) |
continuum_prefix_routing_backend_distribution | Gauge | 백엔드별 인플라이트 요청 (로드 밸런싱용) | backend |
continuum_prefix_routing_prefix_cardinality | Gauge | 확인된 고유 접두사 키의 대략적인 수 | -- |
continuum_prefix_routing_requests_total은 백엔드 선택마다 한 번, PrefixAwareHash 전략 분기가 실행됐을 때만 기록됩니다. 다른 selection_strategy에서는 0으로 남습니다. 세 레이블은 ring 탐색과 그대로 대응합니다.
prefix_hash: 접두사 해시가 지목한 소유 백엔드가 요청을 받았습니다. CHWBL 부하 상한 아래였던 경우와, 모든 백엔드가 상한에 걸려 ring을 한 바퀴 돈 뒤 접두사 지역성을 지키려고 소유 백엔드를 그대로 쓴 경우를 함께 포함합니다.overflow: 소유 백엔드가 부하 상한에 도달했거나 넘어서 ring에서 시계 방향 뒤쪽 노드가 요청을 받았습니다.fallback: 요청에 접두사 키가 없어 모델 이름을 해싱했고 부하 상한 없이 ring을 걸었습니다.fallback비율이 계속 100%라면 거의 언제나prefix_routing.enabled가 false입니다.
unknown은 라우팅 경로가 아니라 정제 결과입니다. 허용 목록에 없는 레이블이 기록 함수에 도달했을 때만 나타납니다.
주요 PromQL 쿼리¶
# 접두사 라우팅 적중률 (접두사 해시 사용 비율 vs 폴백)
sum(rate(continuum_prefix_routing_requests_total{strategy="prefix_hash"}[5m])) /
sum(rate(continuum_prefix_routing_requests_total[5m]))
# 오버플로율 (CHWBL 로드 밸런싱 활성화)
rate(continuum_prefix_routing_requests_total{strategy="overflow"}[5m])
# 백엔드 부하 분포 (대체로 균등해야 함)
continuum_prefix_routing_backend_distribution
KV 캐시 인덱스 메트릭¶
이 메트릭은 인덱스 상태, 쿼리 성능, 라우팅 결정 및 오버랩 스코어링을 포함한 KV 캐시 인덱스 서브시스템을 추적합니다.
| 메트릭 | 유형 | 설명 | 레이블 |
|---|---|---|---|
continuum_kv_index_entries | Gauge | KV 캐시 인덱스의 현재 항목 수 | -- |
continuum_kv_index_events_total | Counter | KV 캐시 인덱스 변경 이벤트 (생성/퇴거) | backend, type (created, evicted) |
continuum_kv_index_query_latency_seconds | Histogram | KV 인덱스 쿼리 작업의 지연 시간 | -- |
continuum_kv_index_routing_decisions_total | Counter | 결과와 선택된 백엔드의 보유 계층별 KV 인식 라우팅 결정 | decision (kv_aware, fallback), tier (gpu_hot, storage_warm, none) |
continuum_kv_index_overlap_score | Histogram | 스코어러가 결정한 선택의 최고 복합 점수 | -- |
continuum_kv_index_event_source_status | Gauge | 이벤트 소스 연결 상태 (1 = 연결됨, 0 = 연결 끊김) | backend, status |
routing_engine_load_decisions_total | Counter | 엔진 부하 인식 라우팅 결정 | backend, reason (engine_load, stale_fallback, hysteresis_hold, admission_reject) |
routing_usage_weight_decisions_total | Counter | 사용량 가중 인식 라우팅 결정 | backend, reason (applied, no_declaration, no_differentiation) |
routing_quota_aware_decisions_total | Counter | 백엔드 선택에 입력된 업스트림 쿼터 잔량 | backend, reason (observed_used, stale, absent, unusable) |
routing_engine_load_decisions_total은 routing.engine_load.enabled가 켜져 있는 동안 스코어링 패스마다 한 번(어드미션 힌트가 거부한 선택마다 추가로 한 번) 기록됩니다:
engine_load: 엔진 부하 항이 실제로 후보 순위를 매긴 경우로,backend가 엔진이 선호한 후보를 가리킵니다.stale_fallback: 신선한 엔진 스냅샷이 하나도 없어 패스가 기본 스코어링과 동일하게 동작한 경우입니다(backend="none").hysteresis_hold: 신선한 데이터는 있었으나waiting_requests편차가 균형 임계값 안에 머문 경우입니다.admission_reject: 모든 정상 후보의kv_cache_usage가 임계값을 넘어 포화 어드미션 힌트가 선택을 거부한 경우입니다(backend="none").
routing_usage_weight_decisions_total은 routing.usage_weight.enabled가 켜져 있는 동안 사용량 가중 항이 결정하거나 판정하지 못한 스코어링 패스마다 한 번 기록됩니다. KV 오버랩이나 엔진 부하 항만으로 선택이 이미 정해진 패스는 KV 메트릭에 남고 이 카운터에는 포함되지 않습니다.
applied: 사용량 가중 항이 한 후보를 엄격히 앞세워 선택을 결정한 경우로,backend가 그 후보를 가리킵니다.no_declaration: 적격 후보 중provider_tpm이나provider_rpm을 선언한 후보가 없어 항이 순위를 매길 대상이 없었던 경우입니다(backend="none").no_differentiation: 하나 이상이 한도를 선언했지만 엄격한 우위가 나오지 않아(모두 소진되어 0, 잔량 비율 동일, 또는weight가 0) 설정된 전략이 결정한 경우입니다(backend="none").
이 항은 라우터 로컬 소비 윈도를 읽으므로 stale 사유가 없습니다. 윈도는 이번 구간의 사용량을 갖거나 0에서 시작할 뿐입니다.
routing_quota_aware_decisions_total은 routing.quota_aware.enabled와 routing.usage_weight.enabled가 모두 켜져 있는 동안 스코어링 패스의 후보마다 한 번 기록되며, backend가 그 후보를 가리킵니다:
observed_used: 신선한 업스트림 스냅샷이 쓸 수 있는 잔량 비율을 하나 이상 제공해 해당 축의 라우터 로컬 추정을 대체한 경우입니다.stale: 스냅샷은 있지만routing.quota_aware.ttl보다 오래되어 선언한 추정을 그대로 둔 경우입니다.absent: 인식 가능한 쿼터 헤더가 담긴 응답을 이 후보에서 아직 보지 못한 경우입니다.unusable: 신선한 스냅샷은 있지만 잔량 값과 0보다 큰-limit분모를 함께 가진 축이 없어 순위에 쓰지 못한 경우입니다.
backend 레이블 값은 다른 백엔드 레이블 시리즈와 동일한 정제기를 거칩니다. 게이트 판정, 부하 정규화, 캐시된 결과, backend 레이블은 요청의 실제 라이브 후보 집합으로 한정되므로 모델 가시성, 상태, 재시도 상태, 키별 권한으로 제외된 엔진은 해당 스코어링 결정에 영향을 주거나 레이블에 나타날 수 없습니다.
라우팅 레이블 두 개는 같은 지점에서 선택마다 한 번 기록됩니다. kv_aware는 KV 오버랩 스코어러가 이겨서 그 백엔드로 전달한 경우이고 fallback은 스코어러가 접두사 키를 가지고 실행됐지만 풀에 컴파일된 복합 점수 임계값 0.3을 넘은 백엔드가 없어 설정된 selection_strategy가 대신 결정한 경우입니다. 스코어러가 시도조차 하지 않은 선택(스코어러 미등록, 접두사 키 없음)은 둘 다 기록하지 않으므로 두 카운터의 합은 전체 트래픽이 아니라 스코어링을 거친 선택의 수입니다. 히스토그램은 kv_aware 결정의 최고 복합 점수를 관측하며 이 값은 오버랩 항목만이 아니라 오버랩, 부하, 헬스를 모두 반영한 가중 합입니다.
tier 레이블은 선택된 백엔드가 요청 프리픽스를 KV 인덱스상 어디에 보유하는지를 그 백엔드를 고른 같은 스코어링 패스에서 읽어 나타냅니다. 프리픽스 블록이 가속기에 상주하면 gpu_hot, 인덱스가 스토리지 계층으로 오프로드된 것으로 기록해(KV 커넥터 오프로드 이벤트) 엔진이 다시 불러와야 하면 storage_warm, 모든 fallback 결정과 해당 프리픽스의 인덱스 항목이 없는 kv_aware 승자는 none입니다. 이 집합 밖의 값은 unknown으로 기록되지만 프로덕션 경로에서는 쓰이지 않습니다. tier를 합산하면 결정별 합계가 됩니다.
주요 PromQL 쿼리¶
# KV 인식 라우팅 비율
sum(rate(continuum_kv_index_routing_decisions_total{decision="kv_aware"}[5m])) /
sum(rate(continuum_kv_index_routing_decisions_total[5m]))
# KV 인식 결정 중 웜(오프로드된) 보유자를 고른 비율
sum(rate(continuum_kv_index_routing_decisions_total{decision="kv_aware",tier="storage_warm"}[5m])) /
sum(rate(continuum_kv_index_routing_decisions_total{decision="kv_aware"}[5m]))
# 라우팅된 요청의 평균 오버랩 점수
histogram_quantile(0.5, rate(continuum_kv_index_overlap_score_bucket[5m]))
# KV 인덱스 쿼리 P99 지연 시간
histogram_quantile(0.99, rate(continuum_kv_index_query_latency_seconds_bucket[5m]))
# 이벤트 소스 연결 상태
continuum_kv_index_event_source_status{status="connected"}
스마트 라우팅 메트릭¶
분류 및 라우팅 파이프라인 전체를 추적하는 메트릭입니다.
분류 및 라우팅¶
| 메트릭 | 유형 | 설명 | 레이블 |
|---|---|---|---|
smart_routing_classifications_total | Counter | 수행된 총 분류 횟수 | complexity, domain, classifier_type |
smart_routing_decisions_total | Counter | 내려진 총 라우팅 결정 수 | source_model, target_model, policy, tier |
smart_routing_classifier_duration_seconds | Histogram | 분류기 지연 시간 | classifier_type |
smart_routing_policy_no_match_total | Counter | 매칭 정책이 없는 요청 수 | - |
smart_routing_tier_no_model_total | Counter | 정책은 매칭됐으나 해당 티어에 모델이 없는 경우 | tier |
부하 관리¶
| 메트릭 | 유형 | 설명 | 레이블 |
|---|---|---|---|
smart_routing_load_state | Gauge | 현재 부하 상태: 0=Normal, 1=Warning, 2=Critical | - |
smart_routing_tier_degradation_total | Counter | 부하로 인한 티어 하향 횟수 | load_state |
smart_routing_load_transitions_total | Counter | 부하 상태 전환 횟수 | from_state, to_state |
LLM 분류기¶
| 메트릭 | 유형 | 설명 | 레이블 |
|---|---|---|---|
smart_routing_llm_classifier_calls_total | Counter | LLM 분류기 호출 총 횟수 | - |
smart_routing_hybrid_gate_total | Counter | 하이브리드 게이트의 라우팅 대 에스컬레이션 결정 수(Admin API classify 및 simulate 호출 포함) | method (hybrid, hybrid_system_one), outcome (routed, escalated) |
smart_routing_llm_classifier_cache_hits_total | Counter | 캐시에서 반환된 분류 결과 수 | - |
smart_routing_llm_classifier_duration_seconds | Histogram | LLM 분류 전체 지연 시간 (버킷: 50ms~5s) | - |
smart_routing_llm_classifier_fallbacks_total | Counter | LLM 결과를 버리고 규칙 기반 결과를 사용한 횟수 | - |
smart_routing_llm_classifier_parse_errors_total | Counter | 재시도 전 응답 파싱 실패 횟수 | - |
smart_routing_llm_classifier_retries_total | Counter | 초기 파싱 실패 후 재시도 횟수 | - |
smart_routing_hybrid_gate_total은 규칙 분류기 신뢰도가 rule.confidence_threshold에 도달해 바로 라우팅하면 outcome="routed"를, LLM 분류기나 System One으로 에스컬레이션하면 outcome="escalated"를 기록합니다(실제 후속 호출이 미설정·부하 생략·캐시 적중으로 생략되었는지나 무엇을 반환했는지와 무관하게 집계됩니다).
System One 분류기¶
결정 호출 자체는 공유 클라이언트가 systemone_requests_total{consumer="classifier", result}로 집계하고 systemone_request_duration_seconds{consumer="classifier"}로 측정합니다(System One 결정 메트릭 참고). 결과는 위의 분류 카운터와 지연 시간 히스토그램에 classifier_type="system_one"으로 실립니다.
| 메트릭 | 유형 | 설명 | 레이블 |
|---|---|---|---|
smart_routing_system_one_classifier_cache_hits_total | Counter | System One 캐시에서 반환된 분류 결과 수 | - |
smart_routing_system_one_classifier_fallbacks_total | Counter | 라우팅에 쓰이지 않은 System One 결과: error(호출 실패 또는 형식 오류), low_confidence(min_confidence 미만), load(설정된 부하 상태에서 건너뜀) | reason |
smart_routing_system_one_classifier_input_tokens_total | Counter | System One 서버가 분류 호출에 대해 보고한 입력 토큰. 라우터 내부 사용량으로 기록되며 counted는 count_toward_budgets를 따름 | counted |
smart_routing_shadow_agreement_total | Counter | 섀도 모드에서 System One 질문 묶음과 요청을 라우팅한 분류기를 필드별로 비교한 횟수 | field (complexity, domain), agree (true, false) |
집계 및 운영¶
| 메트릭 | 유형 | 설명 | 레이블 |
|---|---|---|---|
smart_routing_requests_total | Counter | 스마트 라우팅된 요청 총 수 | source_model, target_model, policy, load_state |
smart_routing_tier_usage_total | Counter | 티어 사용 분포 | tier, domain |
smart_routing_cost_estimate_total | Counter | 티어 최적화로 절감된 예상 비용 | tier |
smart_routing_policy_evaluations_total | Counter | 정책 평가 빈도 | policy_name, result |
smart_routing_model_availability | Gauge | 티어별 사용 가능한 모델: 스마트 라우팅 후보는 1, 비정상이거나 프로필이 없어 제외된 사용자 가시 모델은 0. 후보를 수집할 때마다 다시 만들므로 풀을 떠났거나 티어가 바뀐 모델의 예전 시계열은 사라집니다 | model, tier |
주요 PromQL 쿼리¶
# 정책별 스마트 라우팅 요청 속도
rate(smart_routing_requests_total[5m])
# 티어 사용 분포
sum by(tier) (rate(smart_routing_tier_usage_total[5m]))
# LLM 분류기 캐시 히트율
rate(smart_routing_llm_classifier_cache_hits_total[5m]) /
rate(smart_routing_llm_classifier_calls_total[5m])
# LLM 분류기 P95 지연 시간
histogram_quantile(0.95, rate(smart_routing_llm_classifier_duration_seconds_bucket[5m]))
# LLM 분류기 폴백 비율 (안정성 지표)
rate(smart_routing_llm_classifier_fallbacks_total[5m]) /
rate(smart_routing_llm_classifier_calls_total[5m])
# 하이브리드 관문이 에스컬레이션하는 요청 비율 (방식별)
sum by(method) (rate(smart_routing_hybrid_gate_total{outcome="escalated"}[1h])) /
sum by(method) (rate(smart_routing_hybrid_gate_total[1h]))
# LLM 기반 분류 비율
rate(smart_routing_classifications_total{classifier_type="llm_based"}[5m]) /
rate(smart_routing_classifications_total[5m])
# 필드별 System One 섀도 일치율 (방식을 바꾸기 전에 섀도 모드로 측정)
sum by(field) (rate(smart_routing_shadow_agreement_total{agree="true"}[1h])) /
sum by(field) (rate(smart_routing_shadow_agreement_total[1h]))
# 낮은 신뢰도로 버려진 System One 답의 비율 (min_confidence 조정용)
rate(smart_routing_system_one_classifier_fallbacks_total{reason="low_confidence"}[5m]) /
rate(systemone_requests_total{consumer="classifier",result="ok"}[5m])
# 정책 평가 성공률
sum by(policy_name) (rate(smart_routing_policy_evaluations_total{result="matched"}[5m]))
비즈니스 메트릭¶
| 메트릭 | 유형 | 설명 | 레이블 |
|---|---|---|---|
model_usage_total | Counter | 성공한 모델 요청 횟수 | model, backend |
model_tokens_processed | Counter | 성공한 모델 응답이 보고한 토큰 수 | model, type (input, output) |
모델 레이블 표기 및 카디널리티 규칙:
- 모델 ID 원문 유지:
model_usage_total,model_tokens_processed,llm_tokens_total,smart_routing_model_availability의model레이블과smart_routing_decisions_total및smart_routing_requests_total의source_model·target_model레이블은 폴백 메트릭의original_model·fallback_model과 동일한 표기 규칙을 따릅니다:- 대소문자를 유지하고 유니코드 문자·숫자,
-,_,.와 함께/와:도 그대로 남깁니다(그 밖의 문자는_로 대체하며, 문자 경계 기준 128바이트로 자르고 빈 ID는unknown으로 내보냅니다). - 따라서
meta-llama/Llama-3.1-8B-Instruct,llama3:8b,hf.co/org/repo:Q4_K_M은 설정된 그대로 표시되며,Qwen/Qwen3-8B와qwen/qwen3-8b는 별개의 시계열로 구분됩니다. backend,api_key_id,type등 모델이 아닌 레이블은 기존 소문자 변환 정제 규칙을 따릅니다.
- 대소문자를 유지하고 유니코드 문자·숫자,
- 카디널리티 가드(
metrics.max_model_labels):model_usage_total,model_tokens_processed,llm_tokens_total의 고유 모델 수를 위 표기 기준 기본 1,000개로 제한합니다. 스마트 라우팅 메트릭 패밀리는 이 가드의 제한을 받지 않습니다. smart_routing_requests_total의source_model축약(folding):smart_routing.intercept_all이 켜져 있으면 클라이언트가 보낸 모델 이름과 무관하게 모든 요청이 스마트 라우팅됩니다. 카디널리티 폭발을 막기 위해 다음과 같이 기록합니다:- 요청 모델이
smart_routing.virtual_model이거나 라우터가 현재 서빙 중인 모델일 때만 이름을 그대로 내보냅니다. model이 없는 요청은none으로, 모델 별칭을 포함한 그 밖의 이름은 모두other로 묶어 내보냅니다(실제 서빙 모델 이름이other나none인 경우 해당 시계열을 공유합니다).x-smart-route-source디버그 헤더(smart_routing.debug_headers활성화 시)와 라우팅 로그에는 클라이언트가 보낸 원래 이름이 그대로 남습니다.
- 요청 모델이
System One 결정 메트릭¶
공유 System One 결정 클라이언트(src/services/systemone/)가 기록합니다. 가드레일 프로바이더, 스마트 라우팅 분류기, 액션 안전성 평가기, 응답 품질 폴백 검사(consumer="fallback_quality"), 사용량 품질 판정(consumer="usage_quality")이 이 클라이언트로 결정 모델에 접근합니다. consumer는 호출한 소비자이므로 공유 Jev 키의 속도 제한을 어느 소비자가 소진했는지 구분할 수 있습니다.
| 메트릭 | 타입 | 설명 | 레이블 |
|---|---|---|---|
systemone_requests_total | Counter | 소비자와 결과별 결정 호출 수 (ok, rejected, unavailable, saturated, transport, rate_limited, overloaded, validation, status, malformed) | consumer, result |
systemone_request_duration_seconds | Histogram | 디스패치된 결정 호출의 벽시계 시간(한 번의 제한된 재시도 포함) | consumer |
액션 세이프티 메트릭¶
safeguards.action_safety의 System One 평가기가 기본값인 advisory 모드에서 기록합니다. 두 레이블 모두 판정(allow, deny, indeterminate)을 값으로 가지므로 시리즈는 최대 아홉 개입니다. 두 레이블이 다른 시리즈가 불일치를 셉니다: 예를 들어 {would_verdict="allow", actual_verdict="deny"}는 의사 결정 모델은 허용했을 것이지만 채팅 모델 평가기가 거부한 액션입니다. mode: enforce로 전환하기 전에 이 쌍들을 확인하십시오.
| 메트릭 | 타입 | 설명 | 레이블 |
|---|---|---|---|
safeguards_action_safety_advisory_total | Counter | advisory 모드에서 판단된 액션 수, 결정이 매핑되는 판정과 실제 방출된 판정별 | would_verdict, actual_verdict |
Guardrail Metrics¶
가드레일이 설정되고 metrics 기능이 활성화되면 내보내집니다. 모든 가드레일 결정이 기록되므로, 운영자는 enforce 전후로 정책이 무엇을 하는지(monitor 모드에서는 무엇을 할지)를 관찰할 수 있습니다.
| 메트릭 | 유형 | 설명 | 레이블 |
|---|---|---|---|
guardrail_checks_total | Counter | 단계·판정 결과별 프로바이더 검사 | stage, provider, result |
guardrail_blocks_total | Counter | 단계·프로바이더·카테고리·티어 라우트별 차단 판정 | stage, provider, category, route |
guardrail_check_duration_seconds | Histogram | 프로바이더별 검사 지연(초) | stage, provider |
guardrail_errors_total | Counter | 프로바이더 오류(타임아웃 / 하드 실패) | provider, kind |
guardrail_fail_open_total | Counter | fail-open으로 해소된 프로바이더 실패(요청 허용) | provider |
guardrail_fail_closed_total | Counter | fail-closed로 해소된 프로바이더 실패(요청 차단) | provider |
guardrail_degraded_total | Counter | 선택적 외부 PII 분석기를 사용할 수 없어 내장 탐지만 적용한 경우처럼 정밀도가 낮아진 상태로 완료된 프로바이더 검사 | provider, kind |
guardrail_verdicts_total | Counter | 모드 의미를 적용한 후 요청당 집계 판정 | stage, mode, result, route |
guardrail_route_unknown_total | Counter | 허브 티어의 guardrail route가 미정의 라우트를 가리켜 전역 정책으로 폴백한 요청 수 | - |
guardrail_stream_buffer_cap_trips_total | Counter | 스트리밍 출력 게이트의 4 MiB 유지 바이트 상한에 도달한 스트림 수(스트림당 1회) | strategy, outcome |
레이블 값:
stage는input,output,streaming.result는allow,block,transform,flag.kind는timeout또는error.mode는monitor또는enforce.guardrail_verdicts_total이mode를 담으므로, 요청을 차단하지 않는 monitor 모드 판정도 확인할 수 있습니다. 덕분에 monitor 모드로 먼저 관찰한 뒤 enforce로 전환하는 롤아웃이 가능합니다.route는 적용된 허브 티어guardrail_route이름 또는 미설정 시"none". 최대 16개 고유 라벨 값으로 제한되며 초과분은"other"로 축약되어 기수성 폭발을 방지합니다.provider는 설정된 프로바이더 이름이거나, 예약된 매치 리스트 라벨 두 개 중 하나입니다.guardrails.deny규칙이 검사를 결정했으면match_list_deny, allow 규칙이 결정했으면match_list_allow입니다. 차단 리스트 차단은guardrail_blocks_total에category="deny_list"도 함께 기록합니다. 허용/차단 리스트를 참고하세요.strategy는 상한에 도달한 시점의 전략(buffer_full/chunked/monitor),outcome은 상한 이후 출력 검사가 이어진 방식(degraded_chunked/compacted/truncated). 각outcome이 안전 보장에 어떤 의미인지는 버퍼 상한 절을 참고하세요.strategy="monitor"는 안전 관련 이벤트가 아니라, monitor 모드 스트림이 길어서 앞쪽 4 MiB만 관찰되었다는 뜻입니다.
주요 PromQL 쿼리¶
# 카테고리별로 무엇이 차단될지(monitor 모드 튜닝)
sum by (category) (rate(guardrail_blocks_total[1h]))
# enforce 후 단계별 차단율
sum by (stage) (rate(guardrail_verdicts_total{result="block", mode="enforce"}[5m]))
# 프로바이더 오류율(타임아웃 대 하드 실패)
sum by (provider, kind) (rate(guardrail_errors_total[5m]))
# 프로바이더별 P95 가드레일 검사 지연
histogram_quantile(0.95, sum by (le, provider) (rate(guardrail_check_duration_seconds_bucket[5m])))
전체 가드레일 가이드(개념, 프로바이더, 설정, 임계값 튜닝 워크플로)는 가드레일을 참고하세요.
API 키별 LLM 토큰 사용량¶
라우터는 LLM 토큰 소비를 API 키 단위로 쪼개어 노출합니다. 운영자는 '지난 1시간 동안 어느 키가 completion 토큰을 가장 많이 썼나', '오늘 X팀이 모델 Y에서 prompt 토큰을 얼마나 썼나'와 같은 질문에 바로 답할 수 있습니다. model_tokens_processed 집계 카운터와 독립적인 별도 메트릭이고 용량 산정과 공정 사용 정책, 외부 비용 귀속 산정에 쓰입니다.
메트릭 정의¶
| 메트릭 | 유형 | 설명 | 레이블 |
|---|---|---|---|
llm_tokens_total | Counter | 요청당 소비된 LLM 토큰 수 | api_key_id, model, backend, kind, outcome |
usage_records_total | Counter | 생성된 컨트롤 플레인 사용량 레코드 수. 사용량 루프의 수신 시점에 레코드마다 한 번 | outcome, token_source, purpose |
usage_missing_total | Counter | 백엔드가 사용량을 보고하지 않은 채 끝나 라우터가 센 값으로 레코드를 만든 스트리밍 시도 수 | backend |
usage_export_records_evicted_total | Counter | Admin 사용량 내보내기 링에서 제거된 레코드 수 | reason (retention, max_records) |
api_key_info | Gauge (상수 1) | 설정된 API 키 어노테이션을 레이블로 노출하는 정보 메트릭 | api_key_id + 어노테이션 허용 목록 |
kind 값은 다음 두 가지입니다:
prompt: 업스트림 요청 프롬프트의 토큰 수completion: 업스트림 응답 completion의 토큰 수
사용량 카운터의 레이블 및 시계열 규칙:
outcome: 시도가 어떻게 끝났는지를 나타내며, 고정 집합인completed,length,client_cancelled,timeout_first_byte,timeout_mid_stream,upstream_error,fallback_source,guardrail_blocked,usage_missing,router_restart중 하나입니다(정상 완료는completed, 토큰 한도 도달은length를 내보내며 전체 레이블 합산 값은 변하지 않습니다).usage_records_total: 동일한outcome집합과 함께token_source(engine_final,engine_continuous,router_delta_count,engine_tokenize,char_estimate,none),purpose(client,guardrail,classifier,action_safety,quality_judgment)를 사용합니다. 모든 값은 라우터 내부 열거형의 고정 문자열이므로unknown은 내보내지 않으며,control-plane기능이 없는 빌드에서는0에 머뭅니다. 각 값의 의미는 아키텍처 문서의 '사용량 레코드의 결과와 시도 식별자'를 참고하세요.usage_missing_total{backend}: 백엔드가 usage 블록을 보고하지 않은 채 정상 종료한 스트림 수를 집계합니다(레코드는 라우터가 직접 센 델타 수를token_source="router_delta_count"또는 스트리밍된 내용이 없으면none으로 담고outcome="usage_missing"을 기록합니다):- 마지막 이벤트가 업스트림 오류였던 스트림(
outcome="upstream_error")이나, OpenAI 와이어 릴레이·Unix 소켓 릴레이·채팅 파이프라인·미드스트림 폴백·Anthropic Messages·/v1/responses·responses_only브리지·트랜스포머 릴레이에서 중도 중단된 스트림은 각자의 결과(client_cancelled,timeout_first_byte,timeout_mid_stream,guardrail_blocked,fallback_source)를 기록하므로 여기에는 집계되지 않습니다(반면 엔진 사용량 없이 정상 완료된/v1/responsesChat Completions 변환은 집계됩니다). - 스트림 종료 파이널라이저가 사용량 이벤트를 큐에 넣기 전에
metrics기능이 있는 모든 빌드에서 증가시키므로, 사용량 채널이 가득 차 이벤트가 버려지면 실제 생성된 레코드 수는usage_records_total{outcome="usage_missing"}로 확인하세요. - 특정 백엔드에서 이 값이 계속 증가하면 엔진이
stream_options.include_usage사용량을 보내지 않거나, 중간 프록시가 이를 제거하거나, 마지막 SSE 프레임이 유실되고 있다는 신호입니다. 아키텍처 문서의 '라우터가 직접 세는 출력 토큰과 스트림 사용량 파이널라이저'를 참고하세요.
- 마지막 이벤트가 업스트림 오류였던 스트림(
usage_export_records_evicted_total은 GET /admin/usage/export가 읽는 시도 단위 링에서 제거된 레코드를 셉니다. retention은 admin.stats.usage_export.retention보다 오래된 레코드(다음 추가 시점이나 매시간 실행되는 시계열 정리 시점), max_records는 admin.stats.usage_export.max_records를 넘지 않도록 제거된 레코드입니다. 내보내기가 꺼져 있으면 0에 머뭅니다.
OpenAI 호환(prompt_tokens / completion_tokens) 응답과 Anthropic(input_tokens / output_tokens) 응답 모두 동일한 카운터로 정규화됩니다. 라우터는 OpenAI 호환 스트리밍 요청에 stream_options.include_usage=true를 자동 주입하므로, 클라이언트 동작과 무관하게 마지막 SSE 청크에서 사용량 정보를 받습니다.
api_key_id 도출 규칙¶
api_key_id는 원본 API 키가 노출되지 않도록, 라우터가 안정적이면서 역방향 복원이 불가능한 식별자를 다음 우선순위로 만들어 사용합니다:
- 요청의 bearer 토큰이 설정된 API 키 항목과 일치하면 해당 항목의
id필드를 씁니다(예:key-production-1). - 일치하지 않으면 원본 토큰의 SHA-256 해시 앞 12자에
k_접두어를 붙입니다(예:k_3f5a7c9b1e2d). - 토큰이 아예 없으면
anonymous리터럴이 들어갑니다.
모든 레이블 값은 기존 CardinalityManager를 거치므로, 토큰을 무작위로 갈아끼우는 공격이 들어와도 Prometheus 시리즈가 폭발하지 않습니다.
어노테이션 레이블과 api_key_info¶
각 API 키 항목에는 자유로운 annotations: { key: value } 맵을 달 수 있고 운영자는 그중 어떤 키를 Prometheus 레이블로 승격할지 metrics.annotation_labels 허용 목록으로 명시합니다. 허용 목록에 없는 어노테이션은 내부 데이터로만 남습니다.
설정 스키마(기존 api_keys 블록 안):
api_keys:
api_keys:
- key: "${API_KEY_1}"
id: "key-production-1"
user_id: "user-admin"
organization_id: "org-main"
annotations:
email: "ops@example.com"
team: "platform"
environment: "prod"
owner: "alice"
metrics:
enabled: true
annotation_labels: [email, team] # 레이블 키 허용 목록
권장 표준 어노테이션 키는 email, uuid, owner, team, environment인데, 강제는 아니고 운영자가 자체 키를 추가해도 됩니다.
metrics.annotation_labels가 비어 있지 않으면 라우터는 등록된 키마다 api_key_info{api_key_id, email, team, ...} = 1을 한 번씩 발행합니다. 이 정보 메트릭을 PromQL 조인으로 llm_tokens_total에 투영하면 카운터의 레이블 집합을 키우지 않고도 메타데이터로 필터링·그룹핑할 수 있습니다:
# 이메일별 토큰 소비량 (지난 24시간, prompt + completion 합산)
sum by (email) (
increase(llm_tokens_total[24h])
* on (api_key_id) group_left(email) api_key_info
)
카디널리티와 핫 리로드¶
api_key_id카디널리티는 기본 1,000개로 제한됩니다.- API 키 어노테이션은 기존 설정 리로드 파이프라인을 통해 핫 리로드되고
api_key_info정보 메트릭은 리로드마다 원자적으로 다시 발행됩니다.llm_tokens_total카운터 값은 리셋되지 않습니다. - 단,
api_key_info의 레이블 집합 (즉annotation_labels항목들)은 시작 시점에 고정됩니다. 허용 목록에서 키를 추가하거나 빼려면 재시작이 필요한데, Prometheus가 등록된 메트릭의 레이블 이름 변경을 허용하지 않기 때문입니다.
PromQL 예제¶
# 지난 1시간 동안 API 키별 prompt 토큰 합계
sum by (api_key_id) (
increase(llm_tokens_total{kind="prompt"}[1h])
)
# 지난 24시간 동안 completion 토큰 상위 10개 키
topk(10,
sum by (api_key_id) (
increase(llm_tokens_total{kind="completion"}[24h])
)
)
# 팀별 토큰 사용량 (annotation_labels에 team이 포함되어 있어야 함)
sum by (team) (
increase(llm_tokens_total[24h])
* on (api_key_id) group_left(team) api_key_info
)
Grafana 패널 예제¶
지난 24시간 동안 completion 토큰 상위 10개 팀을 보여주는 stat 패널:
{
"title": "Top 10 teams by completion tokens (24h)",
"type": "stat",
"targets": [
{
"expr": "topk(10, sum by (team) (increase(llm_tokens_total{kind=\"completion\"}[24h]) * on (api_key_id) group_left(team) api_key_info))",
"legendFormat": "{{team}}"
}
],
"options": {
"reduceOptions": {
"values": false,
"calcs": ["lastNotNull"]
}
}
}
지출 추이를 따라가려면 rate(llm_tokens_total[5m])를 team이나 model로 그룹화한 시계열 패널과 함께 쓰면 됩니다.
검증 절차¶
기능을 활성화한 뒤 다음 순서로 동작을 확인합니다:
- 설정된 API 키로 chat-completion 요청을 한 번 보냅니다.
/metrics를 스크랩해서llm_tokens_total{...}과api_key_info{...}시리즈가 노출되는지 확인합니다.- 스트리밍의 경우에도 카운터가 증가하는지 봅니다. 사용량은 마지막 SSE 청크에서 잡고 라우터가 OpenAI 호환 백엔드에는
stream_options.include_usage=true를 자동 주입하므로 클라이언트 동작과 무관하게 동작합니다. - 일상적인 워크로드에서
/metrics의 카디널리티(예:wc -l < /metrics)를 측정해, 도입 전 베이스라인 대비 회귀가 없는지 확인합니다.
사용량 원장 내구성¶
네 개의 메트릭 계열은 컨트롤 플레인 사용량 레코드가 생성된 뒤 허브의 확인 응답을 받기까지 어떻게 되는지를 보여 줍니다. metrics 기능이 있는 모든 빌드에 존재하며 control_plane.enabled가 아니면 0에 머뭅니다.
| 메트릭 | 유형 | 설명 | 레이블 |
|---|---|---|---|
usage_spool_records | Gauge | 디스크 스풀 <state_file>.usage-spool/에 현재 보관된 레코드 수 | 없음 |
usage_spool_bytes | Gauge | 디스크 스풀에 현재 보관된 바이트 수 | 없음 |
usage_records_dropped_total | Counter | 허브 확인 응답 전에 라우터가 포기한 레코드 수 | reason |
usage_durability_corrupt_total | Counter | 파싱할 수 없어 건너뛴 선행 기록 로그 줄과 스풀 파일 수 | kind |
reason은 닫힌 집합이며 모든 값이 시작 시점부터 0으로 존재합니다.
channel_full: 10,000칸짜리 생산자 채널이 가득 찼거나 닫혔습니다.buffer_overflow: 스풀이 꺼져 있거나(control_plane.usage_spool_max_bytes: 0) 사용할 수 없는 상태에서 메모리 버퍼가 넘쳐 가장 오래된 레코드를 버렸습니다.spool_overflow: 스풀 바이트 상한 때문에 가장 오래된 파일을 지웠거나, 상한보다 큰 배치를 거부했거나, 스풀 쓰기가 실패했습니다. 해당 레코드의router_usage_seq는 폐기됩니다.batch_rejected_413: 허브가 레코드 하나짜리 배치를 너무 크다며 거부했습니다. 허브가413으로 거부한 더 큰 배치는 절반 크기로 재분할해 다시 보내므로 여기에 계수하지 않습니다.ack_oversized: 더 이상 증가하지 않습니다. 디코딩 한도를 넘는 2xx 확인 응답은 허브가 배치를 처리했다는 뜻이므로, 라우터는 배치를 전달된 것으로 처리하고 경고 로그만 남깁니다. 쿼리 호환성을 위해 레이블은 0으로 유지됩니다.wal_open_bound: 선행 기록 로그 작성기가 이미 최대치인 65,536개의 열린 시도를 추적하고 있어, 새 시도의 시작 줄을 쓰지 못했습니다. 레코드 자체는 시도가 끝날 때 정상적으로 전송되지만, 그 전에 충돌하면router_restart로 재생할 수 없습니다. 한도를 알리는 경고 로그는 빈도가 제한됩니다.wal_dropped: 선행 기록 로그 줄을 쓰지 못했습니다(250 ms 전송 대기 동안 채널이 계속 가득 찼거나 그런 대기로 로그가 이미 포화 상태인 경우, 부팅 재생의 열린 시도 한도, 쓰기 실패). 종료 줄이 버려지면 거짓으로 열린 시작 줄이 남고 이후 충돌 시router_restart로 재생됩니다.
usage_durability_corrupt_total의 kind 값과 운영 신호:
kind="wal_line":<state_file>.usage-wal.jsonl에서 파싱할 수 없어 건너뛴 한 줄입니다. 심볼릭 링크이거나 일반 파일이 아니거나 소유자 전용(0600)이 아닌 WAL 파일은 경고를 남기고 아무것도 재생하지 않으며, 정상 재생된 시도는usage_records_total{outcome="router_restart"}로 집계됩니다.kind="spool_file": 파싱할 수 없거나, 심볼릭 링크·비일반 파일·비0600권한이어서 삭제된 스풀 배치 파일, 또는 다른 라우터 등록(enrollment) 상태에서 쓰여 삭제된 스풀 배치 파일입니다(다른 등록의 레코드는usage_records_dropped_total{reason="spool_overflow"}로도 함께 집계됩니다).- 운영 상태 지표:
usage_spool_records가0이 아니고 줄어들지 않으면 허브가 계속 푸시를 거부하고 있다는 뜻이며,rate(usage_records_dropped_total{reason!~"wal_.*"}[5m]) > 0은 허브가 보유한 원장이 불완전하다는 신호입니다.wal_open_bound와wal_dropped는 전달된 레코드가 아니라 충돌 복구 범위를 잃는 경우이므로 별도로 알림을 설정하세요.
사용량 원장 변조 증거¶
네 개의 메트릭 계열은 허브로 보내는 사용량 배치의 해시 체인과 로컬 원장 내보내기를 보여 줍니다. 해시 정의와 공백 규칙은 변조 증거를 참고하세요. metrics 기능이 있는 모든 빌드에 존재하며 control_plane.enabled가 꺼져 있으면 0에 머뭅니다.
| 메트릭 | 유형 | 설명 | 레이블 |
|---|---|---|---|
usage_batches_hashed_total | Counter | 라우터가 생성하고 체인 해시를 찍은 사용량 배치 수(푸시, 스풀, 종료 시 플러시 포함) | 없음 |
usage_chain_gaps_total | Counter | 허브가 수신하기 전에 버려진 사용량 배치 수 | 없음 |
usage_ledger_export_failures_total | Counter | 로컬 usage.ledger_export 파일 쓰기 실패(export) 및 체인 상태 저장 실패(chain_state) | target |
usage_ledger_export_bytes | Gauge | 마지막 쓰기 뒤 현재 내보내기 파일의 크기 | 없음 |
두 target 값은 시작 시점부터 0으로 초기화됩니다:
usage_chain_gaps_total: 이미 생성된 배치가 HTTP413, 스풀 거부, 쓰기 실패, 상한 초과 삭제, 파일 손상, 종료 플러시 실패, 스풀 비활성 상태에서의 미확인 종료 배치, 또는 다른 등록(enrollment)으로 스풀된 배치여서 버려질 때 증가합니다. 이 값이 증가하면 허브 측 체인에 라우터가 선언한 공백(gap)이 생겼다는 뜻입니다.usage_ledger_export_failures_total{target="export"}: 파일 열기·쓰기 실패(control_plane.state_file이나 그 사이드카 경로여서 거부된 경우 포함), 가득 찬 writer 채널, 또는rotate: false에서max_bytes에 도달해 거부된 줄이 발생할 때 증가합니다.0보다 크면 로컬 내보내기 파일에 검증기가 끊김(break)으로 보고할 공백이 있다는 뜻입니다.usage_ledger_export_failures_total{target="chain_state"}:<state_file>.usage-chain.json쓰기 실패 또는 배치 해시 계산 실패 시 증가합니다.0보다 크면 다음 재시작 시 새 체인으로 시작할 수 있습니다.
통합¶
Prometheus 설정¶
완전한 Prometheus 설정 예제:
global:
scrape_interval: 15s
evaluation_interval: 15s
scrape_configs:
- job_name: 'continuum-router'
static_configs:
- targets: ['router1:9090', 'router2:9090']
authorization:
credentials: '<metrics-token>'
metric_relabel_configs:
# 필요시 높은 카디널리티 메트릭 삭제
- source_labels: [__name__]
regex: 'http_request_duration_seconds_bucket'
action: drop
Kubernetes 통합¶
Kubernetes 배포의 경우 ServiceMonitor 사용:
apiVersion: monitoring.coreos.com/v1
kind: ServiceMonitor
metadata:
name: continuum-router
namespace: monitoring
spec:
selector:
matchLabels:
app.kubernetes.io/name: continuum-router
endpoints:
- port: metrics
interval: 15s
path: /metrics
bearerTokenSecret:
name: continuum-router-metrics
key: token
Prometheus Operator CRD가 설치된 경우 Helm 차트에서 serviceMonitor.enabled=true로 이 객체를 렌더링할 수 있습니다. Operator가 없는 클러스터를 위해 저장소는 Kubernetes endpoint 검색과 체크인된 알림 규칙을 포함한 독립형 Prometheus Kustomize 번들도 제공합니다.
kubectl apply -k monitoring/prometheus
kubectl -n monitoring rollout status deployment/continuum-router-prometheus
kubectl -n monitoring port-forward service/continuum-router-prometheus 9090:9090
독립형 프로필은 15일 보존 기간과 10 GiB ReadWriteOnce PersistentVolumeClaim을 요청합니다. 기본 StorageClass를 확인하고 예상 series 양에 맞게 claim 크기를 조정하거나 고가용성 모니터링에는 관리형 Prometheus 서비스를 사용하십시오. 적용 전에 클러스터 전체 검색 RBAC와 NetworkPolicy를 검토해야 합니다.
Grafana 대시보드¶
monitoring/grafana/dashboards/router-overview.json에는 다음 패널이 있습니다.
- 요청 속도
- 오류율
- P95 지연 시간
- 백엔드 상태
- 엔드포인트별 요청 속도
- 응답 시간 백분위
- 모델 사용 분포
- 백엔드별 현재 처리 중 부하
- 엔진 요청 수(running / waiting),
engine_stats기반 - 엔진 KV 캐시 사용률,
engine_stats기반 - 이유별 엔진 부하 라우팅 결정,
routing.engine_load기반
가져오는 방법:
- Grafana를 엽니다.
- 대시보드 → 가져오기를 선택합니다.
monitoring/grafana/dashboards/router-overview.json을 업로드합니다.- Prometheus 데이터 소스를 선택하고 가져옵니다.
알림¶
사전 설정된 알림 규칙이 monitoring/prometheus/alerts.yml에 있습니다:
중요 알림¶
{% raw %}
- alert: HighErrorRate
expr: sum(rate(http_requests_total{status_code=~"5.."}[5m])) / sum(rate(http_requests_total[5m])) > 0.05
for: 5m
annotations:
summary: "높은 오류율: {{ $value | humanizePercentage }}"
경고 알림¶
{% raw %}
- alert: HighLatency
expr: histogram_quantile(0.95, http_request_duration_seconds) > 1
for: 5m
annotations:
summary: "P95 지연 시간 1초 초과: {{ $value | humanizeDuration }}"
- alert: TimeoutErrors
expr: sum(rate(timeout_errors_total[5m])) > 0.1
for: 10m
annotations:
summary: "빈번한 타임아웃 오류: 초당 {{ $value }}건"
예제¶
쿼리 예제¶
상태별 요청 속도¶
엔드포인트별 P95 지연 시간¶
백엔드 부하 개요¶
모델 사용량 순위¶
오류율 백분율¶
프로그래밍 방식 접근¶
메트릭에 프로그래밍 방식으로 접근할 수도 있습니다:
import os
import requests
from prometheus_client.parser import text_string_to_metric_families
# 메트릭 가져오기
response = requests.get(
'http://localhost:9090/metrics',
headers={'Authorization': f"Bearer {os.environ['METRICS_TOKEN']}"},
)
metrics = text_string_to_metric_families(response.text)
# 메트릭 처리
for family in metrics:
for sample in family.samples:
if sample.name == 'http_requests_total':
print(f"엔드포인트: {sample.labels['endpoint']}, 카운트: {sample.value}")
사용자 정의 메트릭 수집¶
#!/bin/bash
# 30초마다 메트릭을 수집하고 파일에 저장
while true; do
timestamp=$(date +%s)
curl -s -H "Authorization: Bearer $METRICS_TOKEN" \
http://localhost:9090/metrics > "metrics_${timestamp}.txt"
sleep 30
done
모범 사례¶
1. 레이블 카디널리티¶
메트릭 폭발을 방지하기 위해 레이블 카디널리티를 낮게 유지:
# 좋음: 낮은 카디널리티
labels:
status: "200" # ~5개 가능한 값
method: "GET" # ~7개 가능한 값
---
# 나쁨: 높은 카디널리티
labels:
user_id: "12345" # 무제한
request_id: "abc-123" # 요청당 고유
2. 메트릭 명명¶
Prometheus 명명 규칙 준수:
snake_case사용- 메트릭 이름에 단위 포함 (
_seconds,_bytes,_total) - 표준 접두사 사용 (
http_,backend_,model_)
3. 대시보드 설계¶
- 관련 메트릭을 함께 그룹화
- 적절한 시각화 유형 사용 (현재 값에는 게이지, 시계열에는 그래프)
- 절대값과 비율 모두 포함
- 적절한 새로고침 간격 설정 (실시간에는 15-30초, 이력에는 1-5분)
4. 알림 설정¶
- 플래핑을 방지하기 위해 적절한 평가 기간 사용 (
for: 5m) - 알림 설명에 컨텍스트 포함
- 심각도에 따른 알림 라우팅 설정
- 프로덕션 전 스테이징에서 알림 테스트
5. 성능 고려 사항¶
- 필요하지 않은 경우 선택적 메트릭 비활성화
- 복잡한 쿼리에 레코딩 규칙 사용
- 적절한 메트릭 보존 정책 구현
- 장기 보존을 위해 원격 스토리지 고려
6. 보안¶
- 민감한 데이터가 노출된 경우 메트릭 엔드포인트 보호
- 프로덕션에서 Prometheus 스크래핑에 TLS 사용
- Grafana 대시보드에 인증 구현
- 메트릭 접근 로그 감사
문제 해결¶
메트릭이 나타나지 않음¶
- 설정에서 메트릭이 활성화되어 있는지 확인
- 메트릭 엔드포인트에 접근 가능한지 확인
- Prometheus 타겟 상태 확인
- 메트릭 초기화 오류에 대한 라우터 로그 검토
높은 메모리 사용량¶
- 카디널리티 제한 검토
- 무제한 레이블 확인
- 필요시 히스토그램 버킷 감소
- 메트릭 만료 활성화
잘못된 값¶
- 메트릭 유형 확인 (카운터 vs 게이지)
- 집계 함수 확인
- 레이블 선택기 검토
- 시간 범위 검증