콘텐츠로 이동

메트릭 및 모니터링

이 문서는 Continuum Router의 메트릭 및 모니터링 기능을 설명합니다.

목차

개요

Continuum Router는 시스템 상태, 성능, 사용 패턴을 다룰 수 있도록 Prometheus 호환 메트릭을 제공합니다. 메트릭 시스템은 다음과 같이 설계되었습니다:

  • 가벼움: 최소한의 성능 오버헤드
  • 넓은 범위: HTTP, 백엔드, 라우팅, 모델, 스트리밍, 캐시 서브시스템 전반을 포괄
  • 프로덕션 준비: 카디널리티 제한 및 적절한 레이블링 포함
  • 쉬운 통합: 표준 Prometheus/Grafana 설정과 작동

전체 Prometheus 스택을 구축하지 않고도 재시작 전후의 메트릭 이력을 보존하려면 영속 메트릭 로그를 참고하세요. 레지스트리를 로컬 SQLite 저장소에 스냅샷으로 저장하고 GET /admin/metrics/history를 통해 최근 이력을 조회할 수 있습니다.

메트릭은 얼마나 많이, 얼마나 자주를 답합니다. 요청 하나의 인과관계, 즉 어떤 백엔드가 선택됐고 몇 번 시도했으며 각 시도가 왜 그렇게 끝났는지를 보려면 분산 트레이스 내보내기를 참고하세요. 라우터의 스팬을 OTLP로 OpenTelemetry 컬렉터에 보내는 기능입니다.

빠른 시작

1. 메트릭 활성화

메트릭은 기본적으로 활성화되지만 metrics.auth를 설정하기 전에는 /metrics가 401 Unauthorized를 반환합니다. Prometheus에는 Bearer 토큰을 사용하십시오:

metrics:
  auth:
    method: bearer
    bearer_token: "${METRICS_TOKEN}"
# 메트릭 보기
curl -H "Authorization: Bearer $METRICS_TOKEN" http://localhost:9090/metrics

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 값 필드를 생략하므로(msgspec omit_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/responses Chat 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 키가 노출되지 않도록, 라우터가 안정적이면서 역방향 복원이 불가능한 식별자를 다음 우선순위로 만들어 사용합니다:

  1. 요청의 bearer 토큰이 설정된 API 키 항목과 일치하면 해당 항목의 id 필드를 씁니다(예: key-production-1).
  2. 일치하지 않으면 원본 토큰의 SHA-256 해시 앞 12자에 k_ 접두어를 붙입니다(예: k_3f5a7c9b1e2d).
  3. 토큰이 아예 없으면 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
)
# 모델별 prompt+completion 합산 속도 (토큰/초)
sum by (model) (rate(llm_tokens_total[5m]))
# 백엔드별·키별 소비량 (비용 귀속 산정용)
sum by (api_key_id, backend) (
  increase(llm_tokens_total[24h])
)

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로 그룹화한 시계열 패널과 함께 쓰면 됩니다.

검증 절차

기능을 활성화한 뒤 다음 순서로 동작을 확인합니다:

  1. 설정된 API 키로 chat-completion 요청을 한 번 보냅니다.
  2. /metrics를 스크랩해서 llm_tokens_total{...}과 api_key_info{...} 시리즈가 노출되는지 확인합니다.
  3. 스트리밍의 경우에도 카운터가 증가하는지 봅니다. 사용량은 마지막 SSE 청크에서 잡고 라우터가 OpenAI 호환 백엔드에는 stream_options.include_usage=true를 자동 주입하므로 클라이언트 동작과 무관하게 동작합니다.
  4. 일상적인 워크로드에서 /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: 이미 생성된 배치가 HTTP 413, 스풀 거부, 쓰기 실패, 상한 초과 삭제, 파일 손상, 종료 플러시 실패, 스풀 비활성 상태에서의 미확인 종료 배치, 또는 다른 등록(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 기반

가져오는 방법:

  1. Grafana를 엽니다.
  2. 대시보드 → 가져오기를 선택합니다.
  3. monitoring/grafana/dashboards/router-overview.json을 업로드합니다.
  4. 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 }}건"

예제

쿼리 예제

상태별 요청 속도

sum(rate(http_requests_total[5m])) by (status)

엔드포인트별 P95 지연 시간

histogram_quantile(0.95,
  sum(rate(http_request_duration_seconds_bucket[5m])) by (endpoint, le)
)

백엔드 부하 개요

backend_current_load

모델 사용량 순위

topk(10, sum(rate(model_usage_total[1h])) by (model))

오류율 백분율

sum(rate(http_requests_total{status_code=~"5.."}[5m])) /
sum(rate(http_requests_total[5m])) * 100

프로그래밍 방식 접근

메트릭에 프로그래밍 방식으로 접근할 수도 있습니다:

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 대시보드에 인증 구현
  • 메트릭 접근 로그 감사

문제 해결

메트릭이 나타나지 않음

  1. 설정에서 메트릭이 활성화되어 있는지 확인
  2. 메트릭 엔드포인트에 접근 가능한지 확인
  3. Prometheus 타겟 상태 확인
  4. 메트릭 초기화 오류에 대한 라우터 로그 검토

높은 메모리 사용량

  1. 카디널리티 제한 검토
  2. 무제한 레이블 확인
  3. 필요시 히스토그램 버킷 감소
  4. 메트릭 만료 활성화

잘못된 값

  1. 메트릭 유형 확인 (카운터 vs 게이지)
  2. 집계 함수 확인
  3. 레이블 선택기 검토
  4. 시간 범위 검증

추가 리소스