Grafana Tempo TraceQL로 에러를 놓치지 않는 법: 샘플링 환경 실전 가이드
분산 시스템을 운영하다 보면 "샘플링 때문에 에러 트레이스가 누락되는 건 아닐까?" 하는 불안감이 생깁니다. 실제로 Head-based 샘플링 환경에서는 요청이 시작되는 순간 저장 여부가 결정되기 때문에, 나중에 에러가 발생해도 해당 트레이스가 아예 저장되지 않아 TraceQL 검색 결과에 등장하지 않는 일이 발생합니다. 에러가 났는데 흔적조차 없는 상황, 한 번쯤 겪어본 적 있을 것입니다.
이 글은 백엔드·프론트엔드 개발자 모두를 대상으로, 샘플링 환경에서도 에러를 놓치지 않기 위한 두 가지 경로—TraceQL 직접 쿼리와 Metrics-Generator를 활용한 RED 메트릭 기반 알림—를 실전 코드와 함께 소개합니다. 프론트엔드 개발자라면 브라우저에서 발생한 클라이언트 사이드 에러가 OpenTelemetry SDK를 통해 Tempo로 수집된 이후, 이 글의 방법으로 동일하게 탐지·알림 연결이 가능합니다. Tempo 2.9 이상, Grafana v12.1 실험 기능까지 포함해 현재 가장 최신의 접근법을 다룹니다.
사전 요구사항: 이 글의 실습을 따라 하려면 Tempo와 Prometheus가 이미 배포되어 있어야 합니다.
tempo.yaml설정 파일을 직접 수정하고 Prometheusremote_write를 구성할 수 있는 환경을 전제합니다.
핵심 개념
Grafana Tempo와 TraceQL이란
Grafana Tempo는 Jaeger, Zipkin, OpenTelemetry 포맷의 분산 트레이스를 저장하고 조회하는 오픈소스 백엔드입니다. 별도의 인덱스 없이 오브젝트 스토리지(S3, GCS 등)에 트레이스를 저장하기 때문에 비용 효율이 높습니다.
TraceQL은 Tempo 전용 쿼리 언어로, PromQL·LogQL과 유사한 문법을 사용합니다. 스팬 속성, 리소스 메타데이터, 상위-하위 스팬 관계를 기반으로 트레이스를 필터링할 수 있습니다.
{ status = error && resource.service.name = "payment-service" }TraceQL 기본 구조:
{ 필터 조건 }형태로 스팬을 선택하며,|파이프 연산자로 집계 함수를 연결합니다. PromQL의 label selector와 유사하게 읽히지만, 트레이스 계층 구조를 포함한 질의가 가능하다는 점이 다릅니다.
샘플링이 에러 탐지를 어떻게 방해하는가
샘플링은 트레이스 중 일부만 저장소에 기록하는 전략입니다. 저장되지 않은 트레이스는 TraceQL로 조회할 수 없습니다. 이것이 핵심 제약입니다.
| 샘플링 방식 | 동작 원리 | 에러 탐지 적합성 |
|---|---|---|
| Head-based | 요청 시작 시점에 저장 여부 결정 | 낮음 — 에러 여부를 모르는 상태에서 결정 |
| Tail-based | 트레이스 완료 후 에러 여부를 보고 저장 여부 결정 | 높음 — 에러 트레이스를 우선 보존 가능 |
| Adaptive | 쿼리 특성에 따라 샘플링 전략을 동적 전환 (로드맵 논의 중) | 높음 — 자동 최적화 예정 |
Tail-based Sampling: OpenTelemetry Collector의
tailsampling프로세서가 이를 지원합니다. 에러 상태 코드, 높은 레이턴시 등의 조건을 만족하는 트레이스를 우선 보존하도록 정책을 설정할 수 있습니다. 구체적인 Collector 설정 방법은 다음 글에서 다룹니다.
TraceQL Metrics와 local-blocks 프로세서
TraceQL Metrics는 { 필터 } | rate() 형태로 트레이스에서 시계열 메트릭을 직접 생성하는 기능입니다. 이 기능을 사용하려면 Tempo 설정에서 local-blocks 프로세서를 반드시 활성화해야 합니다.
아래 설정은 overrides.defaults 하위에 위치하며, 테넌트 전체에 기본값으로 적용됩니다. 이 섹션은 Metrics-Generator 컴포넌트 자체를 구성하는 최상위 metrics_generator 블록과는 별개입니다(실전 적용 예시 3 참고).
# tempo.yaml — 테넌트 기본값으로 local-blocks 활성화
overrides:
defaults:
metrics_generator:
processors:
- local-blocks # TraceQL Metrics 쿼리를 위한 필수 프로세서local-blocks 프로세서: 수집된 트레이스 블록을 로컬에 임시 보관하며 TraceQL Metrics 쿼리가 이를 실시간으로 집계할 수 있도록 합니다. Tempo 2.9에서 안정화되었습니다.
실전 적용
예시 1: TraceQL로 에러 스팬 직접 탐색
Tempo Explore 화면에서 다음 쿼리를 사용하면 특정 서비스의 에러 트레이스를 즉시 확인할 수 있습니다.
{ status = error && resource.service.name = "payment-service" }HTTP 상태 코드 기반으로 4xx/5xx를 필터링하고 싶다면 아래처럼 작성할 수 있습니다.
{ span.http.status_code >= 400 }특정 엔드포인트에서만 발생하는 에러를 좁히고 싶을 때는 속성을 조합합니다. OpenTelemetry Semantic Conventions 기준으로 URL 경로 속성은 url.path를 사용하는 것을 권장합니다(span.http.url은 deprecated).
{ status = error && url.path =~ ".*/checkout.*" && span.http.status_code >= 500 }| 속성 키 | 설명 | 예시 값 |
|---|---|---|
status |
OpenTelemetry 스팬 상태 | error, ok, unset |
resource.service.name |
서비스 식별자 | "payment-service" |
span.http.status_code |
HTTP 응답 코드 | 500, >=400 |
url.path |
요청 경로 (정규식 지원) | =~ ".*/api/.*" |
예시 2: TraceQL Metrics로 에러율 시계열 생성
local-blocks 프로세서 활성화 후 Grafana 대시보드에서 다음 쿼리로 에러율 그래프를 그릴 수 있습니다.
{ status = error && resource.service.name = "payment-service" } | rate()서비스 전체의 에러율을 서비스별로 분리해 비교하려면 by() 집계를 활용합니다.
{ status = error } | rate() by (resource.service.name)Dynamic Sampling 힌트로 쿼리 성능을 최적화할 수도 있습니다.
{ status = error } | rate() with(sample=true)
with(sample=true)트레이드오프: 이 힌트는 집계 결과를 근사값(approximate)으로 허용하는 대신 쿼리 속도를 높입니다. 대시보드 시각화처럼 정밀도보다 응답 속도가 중요한 경우에 적합하며, 정확한 에러 수 집계가 필요한 알림 임계값 설정에는 사용하지 않는 것을 권장합니다.
예시 3: Metrics-Generator로 RED 메트릭 생성 후 Prometheus 알림 설정
샘플링에 관계없이 모든 에러를 집계하려면 Tempo Metrics-Generator를 활성화하고, 생성된 메트릭을 Prometheus로 전송하는 방식이 가장 안정적입니다.
1단계 — Tempo 설정 (tempo.yaml)
아래 블록은 최상위 metrics_generator 섹션으로, Metrics-Generator 컴포넌트 자체의 동작을 구성합니다. 앞서 소개한 overrides.defaults 하위의 local-blocks와는 역할이 다릅니다.
# tempo.yaml — Metrics-Generator 컴포넌트 설정
metrics_generator:
processors:
- service-graphs # 서비스 간 요청·실패 메트릭 생성
- span-metrics # 스팬별 RED 메트릭 생성
storage:
remote_write:
- url: http://prometheus:9090/api/v1/write # Prometheus 엔드포인트
span_metrics:
dimensions:
- service.name # 저카디널리티 속성만 포함
- http.status_code2단계 — Prometheus에서 생성된 메트릭 확인
Metrics-Generator가 활성화되면 다음과 같은 메트릭이 자동으로 생성됩니다.
| 메트릭 이름 | 설명 |
|---|---|
traces_spanmetrics_calls_total |
서비스·상태 코드별 스팬 호출 수 |
traces_spanmetrics_duration_seconds_bucket |
레이턴시 히스토그램 |
traces_service_graph_request_total |
서비스 간 요청 수 |
traces_service_graph_request_failed_total |
서비스 간 실패 요청 수 |
실제 레이블 값은 Tempo 버전 및 설정에 따라 다를 수 있습니다. Alert Rule을 작성하기 전에 Prometheus UI에서 traces_spanmetrics_calls_total을 조회해 status_code 레이블의 실제 값을 먼저 확인하는 것을 권장합니다.
3단계 — Prometheus Alert Rule 작성
groups:
- name: tempo-error-alerts
rules:
- alert: HighErrorRate
expr: |
rate(traces_spanmetrics_calls_total{status_code="STATUS_CODE_ERROR"}[5m])
/ rate(traces_spanmetrics_calls_total[5m]) > 0.05
for: 2m
labels:
severity: warning
annotations:
summary: "{{ $labels.service }} 에러율 5% 초과"
description: "현재 에러율: {{ $value | humanizePercentage }}"
- alert: PaymentServiceCriticalError
expr: |
rate(traces_spanmetrics_calls_total{
service="payment-service",
status_code="STATUS_CODE_ERROR"
}[5m]) > 1
for: 1m
labels:
severity: critical
annotations:
summary: "payment-service 임계 에러 발생"
description: "초당 에러 스팬 수: {{ $value | humanize }}"예시 4: Grafana v12.1 TraceQL 직접 알림 (실험적 기능)
Grafana v12.1 이상에서는 TraceQL Metrics 쿼리를 Alert Rule에 직접 입력해 알림을 설정할 수 있습니다. UI 경로는 Alerting → Alert rules → New alert rule이며, 쿼리 편집기의 데이터소스 드롭다운에서 Tempo를 선택하면 TraceQL 입력란이 활성화됩니다.
{ status = error && resource.service.name = "checkout-service" }
| rate()
| by (resource.service.name)위 쿼리를 입력한 뒤 Threshold 조건으로 IS ABOVE 0.05를 설정하면 메트릭 변환 없이 트레이스 원본 데이터 기반 알림이 동작합니다.
주의: 이 기능은 Grafana v12.1 기준으로 실험적(Experimental) 상태입니다. 프로덕션 환경에서는 예시 3의 Metrics-Generator 기반 알림을 병행하는 것을 권장합니다.
장단점 분석
장점
| 기능 / 전략 | 내용 |
|---|---|
| TraceQL 직접 탐색 | 별도 파이프라인 없이 스팬 속성 기반 유연한 필터링 가능 |
| TraceQL Metrics | 저장된 트레이스에서 즉시 시계열 생성, 대시보드 연동 용이 |
| Metrics-Generator | Prometheus 생태계 완전 통합, 샘플링 여부와 무관한 전수 집계 |
| Tail-based Sampling 조합 | 에러 트레이스 보존률을 높여 TraceQL 탐지 정확도 향상 |
| Grafana Alerting 통합 | 단일 UI에서 알림 규칙·라우팅·수신처 관리 가능 |
단점 및 주의사항
| 기능 / 전략 | 내용 | 대응 방안 |
|---|---|---|
| Head-based 샘플링 드롭 | 에러 트레이스가 저장되지 않아 TraceQL 조회 불가 | Tail-based Sampling으로 전환 또는 Metrics-Generator 병행 |
| local-blocks 미활성화 | TraceQL Metrics 쿼리가 빈 결과 반환 | overrides.defaults에 local-blocks 프로세서 명시적 추가 |
| 메트릭 카디널리티 급증 | span-metrics에 고유도 속성 추가 시 Prometheus 부하 증가 | dimensions에 저카디널리티 속성(service.name, http.status_code)만 포함 |
| v12.1 실험적 알림 | TraceQL 직접 알림은 프로덕션 미권장 | 안정화 전까지 Prometheus Alert Rule 사용 |
| 알림 임계값 왜곡 | 낮은 샘플링 비율 환경에서 에러율 과소 집계 | Metrics-Generator(전수 집계) 기준으로 임계값 설정 |
카디널리티(Cardinality): 메트릭의 레이블 조합 수를 의미합니다. 사용자 ID나
trace_id처럼 고유값을 레이블로 추가하면 사용자 수만큼 시계열이 생성되어 Prometheus 메모리 사용량이 급증할 수 있습니다.service.name,http.method,http.status_code처럼 값이 고정된 속성만 dimensions에 포함하는 것을 권장합니다.
RED 메트릭: Rate(초당 요청 수), Error(에러율), Duration(응답 시간) 세 가지 지표를 말합니다. Metrics-Generator의 span-metrics 프로세서가 이 세 가지를 자동으로 생성합니다.
실무에서 가장 흔한 실수
-
local-blocks미활성화:overrides.defaults.metrics_generator.processors에local-blocks를 추가하지 않은 채 TraceQL Metrics 쿼리를 실행하면 항상 빈 결과가 반환됩니다. 이 설정은 최상위metrics_generator블록과 별개이므로 두 곳 모두 확인하는 것을 권장합니다. -
dimensions에 고카디널리티 속성 포함:
trace_id, 사용자 ID, 세션 ID 같은 고유값을span_metrics.dimensions에 추가하면 Prometheus OOM이 발생할 수 있습니다. 저카디널리티 속성만 포함하는 것을 권장합니다. -
Head-based 샘플링 환경에서 TraceQL 탐색 결과만으로 알림 설계: 샘플링으로 드롭된 트레이스는 조회 자체가 불가능하므로 TraceQL 탐색 결과가 "에러 없음"처럼 보여도 실제 에러가 존재할 수 있습니다. Metrics-Generator 기반 알림을 반드시 병행하는 것을 권장합니다.
마치며
이제 여러분의 시스템은 샘플링 여부와 관계없이 에러를 감지할 수 있는 이중 구조를 갖추게 됩니다. TraceQL로 저장된 트레이스를 유연하게 탐색하면서, Metrics-Generator가 전수 집계한 RED 메트릭을 Prometheus 알림과 연결하는 조합이 샘플링 환경에서 가장 신뢰도 높은 에러 탐지 전략입니다.
지금 바로 시작해볼 수 있는 3단계:
-
Tempo 설정 파일에 local-blocks와 span-metrics 프로세서를 추가합니다.
overrides.defaults.metrics_generator.processors에local-blocks추가- 최상위
metrics_generator.processors에span-metrics추가 storage.remote_write.url에 Prometheus 엔드포인트 입력
-
Grafana Explore에서 에러율 쿼리를 실행해 local-blocks가 정상 동작하는지 확인합니다.
- 쿼리:
{ status = error } | rate() by (resource.service.name) - 결과가 비어 있다면
local-blocks설정 여부를 재확인
- 쿼리:
-
Prometheus에
HighErrorRateAlert Rule을 추가하고, Grafana Alerting에서 Slack 또는 PagerDuty 수신처와 연결합니다.- 초기 임계값
> 0.05(5%)로 시작해 서비스 특성에 맞게 조정하는 것을 권장합니다 - Alert Rule 적용 전 Prometheus UI에서
status_code레이블 실제 값을 먼저 확인
- 초기 임계값
다음 글: OpenTelemetry Collector의 Tail-based Sampling 프로세서 설정으로 에러·고레이턴시 트레이스 보존률을 극대화하는 방법
참고 자료
- TraceQL 공식 문서 | Grafana Tempo
- TraceQL Metrics 쿼리 가이드 | Grafana Tempo
- TraceQL Metrics 샘플링 가이드 | Grafana Tempo
- Configure TraceQL Metrics | Grafana Tempo
- 트레이스 기반 에러 진단 가이드 | Grafana Tempo
- Metrics from Traces 개요 | Grafana Tempo
- Metrics-Generator 공식 문서 | Grafana Tempo
- Span Metrics 프로세서 | Grafana Tempo
- 트레이스 기반 알림 예시 | Grafana Alerting
- TraceQL Metrics 문제 해결 가이드 | Grafana Tempo
- Tempo 2.9 릴리스 노트 | Grafana Tempo
- Tempo Metrics-Generator RED 메트릭 설정 실습 (커뮤니티) | OneUptime Blog