TraceQL 심화: Grafana Tempo 2.x 에러 필터링·P99·Mimir 크로스-시그널 실전 가이드
payment-service 에러율 12% 급증, P99 4.3초 — 이 상황에서 원인 트레이스를 식별하는 데 걸린 시간은 단 8초였습니다. 분산 시스템 장애 대응에서 "어떤 서비스의 어떤 요청이 문제였는가"를 수 초 안에 특정할 수 있는 것, Grafana Tempo 2.x와 TraceQL이 열어주는 가능성입니다. SQL이 관계형 데이터를 다루듯, TraceQL은 분산 트레이스 데이터를 스팬 단위로 정밀하게 쿼리할 수 있는 전용 언어입니다. Prometheus의 PromQL이 시계열 메트릭(숫자의 흐름)을 집계하는 언어라면, TraceQL은 개별 요청의 전체 실행 경로를 탐색하는 언어로, 두 언어는 관측 가능성의 서로 다른 차원을 담당합니다. TraceQL Metrics 기능을 활용하면 트레이스에서 PromQL 유사 집계를 즉석에서 추출할 수도 있어, 두 세계를 하나의 도구로 연결합니다.
이 글에서는 TraceQL의 에러 스팬 필터링, 서비스별 P99 레이턴시 분석, 그리고 Mimir 메트릭과의 크로스-시그널 쿼리라는 세 가지 핵심 역량을 실전 예시와 함께 체계적으로 살펴봅니다. 단순한 문법 소개를 넘어, 실제 온콜 상황에서 바로 꺼낼 수 있는 쿼리 패턴과 운영 노하우를 담았습니다.
이 글의 전제 조건: Grafana Tempo가 이미 배포되어 있고, OpenTelemetry SDK로 애플리케이션 계측이 완료된 환경을 전제합니다. Tempo 설치가 아직 되지 않았다면 Grafana Tempo 공식 설치 가이드를 먼저 참고하세요.
핵심 개념
TraceQL이란
TraceQL은 Grafana Tempo에서 분산 트레이싱 데이터를 쿼리하기 위해 설계된 전용 쿼리 언어입니다. 로그를 위한 LogQL, 메트릭을 위한 PromQL과 같은 맥락에서, 트레이스 전용 쿼리 언어로 자리잡고 있습니다.
PromQL과의 핵심 차이: PromQL은 "지난 5분간 에러율이 얼마인가"를 묻고, TraceQL은 "어떤 요청이 에러였고 어느 스팬에서 얼마나 걸렸는가"를 묻습니다. TraceQL Metrics 기능을 사용하면 트레이스에서 PromQL 유사 집계를 즉석으로 추출할 수 있어, 사전 계측 없이도 P99나 에러율을 실시간으로 확인할 수 있습니다.
기본 문법 구조는 다음과 같습니다.
{ <span-filter> } | <pipeline-function>중괄호 {}는 스팬 셀렉터로, 내부 조건이 각 스팬에 적용되어 매칭된 스팬을 반환합니다. 파이프 | 이후에는 rate(), quantile_over_time() 같은 집계 함수를 연결할 수 있습니다.
주요 필드 네임스페이스
| 네임스페이스 | 설명 | 예시 |
|---|---|---|
span.* |
스팬 속성 | span.http.status_code |
resource.* |
리소스 속성 (서비스 메타) | resource.service.name |
status |
스팬 상태 | error, ok, unset |
duration |
스팬 소요 시간 | duration > 500ms |
name |
스팬 이름 | name = "POST /api/orders" |
OpenTelemetry 시맨틱 컨벤션:
span.http.request.method,resource.service.name등의 필드명은 OpenTelemetry 표준 속성 이름을 그대로 따릅니다. OTEL SDK로 계측된 애플리케이션이라면 별도 매핑 없이 바로 사용할 수 있습니다.
구조적 연산자 (Tempo 2.2+)
Tempo 2.2부터 트레이스 내 스팬 간 계층 관계를 기반으로 필터링하는 구조적 연산자가 도입되었습니다.
| 연산자 | 의미 |
|---|---|
>> (descendant) |
A의 하위(후손) 스팬 중 B를 찾음 |
> (child) |
A의 직접 자식 스팬 B |
<< (ancestor) |
A의 상위(조상) 스팬 중 B |
< (parent) |
A의 직접 부모 스팬 B |
~ (sibling) |
A와 같은 레벨의 형제 스팬 B |
이 연산자들은 "어떤 에러의 근본 원인이 어느 레이어에 있는가"를 분산 호출 트리 구조 그대로 추적할 때 매우 유용합니다.
TraceQL Metrics 버전별 진화
| 버전 | 출시 | 주요 TraceQL 기능 |
|---|---|---|
| 2.2 | 2023.08 | 구조적 연산자 (>>, >, ~, <<, <) |
| 2.4 | 2024.02 | TraceQL Metrics 실험적 도입 (rate()) |
| 2.5 | 2024.06 | quantile_over_time(), histogram_over_time() 추가, gRPC 스트리밍 |
| 2.6 | 2024.09 | native histogram, TraceQL Metrics Exemplars, 즉시(instant) 쿼리 |
| 2.7 | 2025.01 | sum_over_time() 등 신규 메트릭 함수 |
실전 적용
이어지는 다섯 가지 예시는 하나의 시나리오를 점진적으로 심화합니다. payment-service 에러율 12% 급증, P99 4.3초 — 온콜 알림을 받은 순간부터 근본 원인을 파악하고, 장기 알림 규칙까지 구축하는 전 과정을 TraceQL로 풀어봅니다.
에러 스팬 필터링: SLO 위반 트레이스 즉시 식별
온콜 알림을 받은 직후, 가장 먼저 할 일은 에러가 발생하는 트레이스를 빠르게 특정하는 것입니다.
단계 1 — 서비스 에러 기본 필터:
{ resource.service.name = "payment-service" && status = error }HTTP 상태 코드 기반으로도 필터링할 수 있습니다. status = error는 OTEL 스팬 상태 기준이고, span.http.status_code >= 400은 HTTP 프로토콜 코드 기준으로 서로 다른 레이어를 확인합니다.
{ span.http.status_code >= 400 }단계 2 — SLO 조건을 동시에 적용한 복합 쿼리:
{ resource.service.name = "payment-service"
&& status = error
&& duration > 2s
&& span.http.route = "/api/v1/orders" }이 쿼리의 출력: Grafana Explore의 트레이스 목록 뷰에서 조건을 만족하는 개별 트레이스가 나열됩니다. 각 트레이스를 클릭하면 전체 스팬 계층과 속성을 확인할 수 있습니다.
| 조건 | 의미 |
|---|---|
status = error |
OTEL 스팬 상태가 Error인 스팬만 |
duration > 2s |
SLO 임계값(2초) 초과 |
span.http.route |
특정 엔드포인트로 범위 한정 |
에러 전파 경로 추적: 구조적 연산자로 근본 원인 탐색
payment-service에서 에러가 발생했지만, 실제 원인은 하위 의존성에 있을 수 있습니다. 구조적 연산자로 에러 전파 경로를 추적합니다.
{ resource.service.name = "payment-service" && status = error }
>> { span.db.system = "postgresql" }
>>연산자 동작 방식: 왼쪽 조건의 스팬을 조상으로 갖는 트레이스 내에서, 오른쪽 조건에 맞는 후손 스팬을 반환합니다. "payment-service 에러의 하위에 PostgreSQL 스팬이 있는가"를 한 번에 탐색합니다.
더 넓게, frontend에서 발생한 에러가 payment-service에 기인하는지도 같은 방식으로 확인할 수 있습니다.
{ resource.service.name = "frontend" && status = error }
>> { resource.service.name = "payment-service" }서비스별 P99 레이턴시 분석: 계측 없이 병목 서비스 특정
에러의 근원을 좁혔다면, 이제 레이턴시 분포를 정량적으로 파악합니다. Tempo 2.5부터 사용 가능한 quantile_over_time()으로 별도 계측 없이 P99를 실시간 추출할 수 있습니다.
전체 서비스 P99 레이턴시 한눈에 보기:
{ resource.service.name =~ ".+" }
| quantile_over_time(duration, 1m, 0.99)
by (resource.service.name)
.+는 비어 있지 않은 모든 문자열에 매칭됩니다.service.name속성이 존재하는 모든 서비스를 대상으로 합니다.
payment-service P99 레이턴시 집중 확인:
{ resource.service.name = "payment-service" }
| quantile_over_time(duration, 1m, 0.99)이 쿼리의 출력: Grafana Explore에서 시계열 그래프로 렌더링됩니다. X축이 시간, Y축이 레이턴시(초)로 P99 트렌드를 시각적으로 확인할 수 있습니다.
복수 분위수 쿼리 주의:
quantile_over_time(duration, 5m, 0.50, 0.90, 0.99)형태로 복수 분위수를 한 번에 지정하는 문법은 Tempo 버전에 따라 지원 여부가 다를 수 있습니다. 사용 중인 Tempo 버전의 공식 함수 레퍼런스에서 시그니처를 먼저 확인하는 것을 권장합니다.
에러 스팬만의 레이턴시 분포 확인:
{ status = error }
| histogram_over_time(duration, 1m)
by (resource.service.name)서비스별 에러 발생률 추적:
{ status = error }
| rate()
by (resource.service.name, span.http.route)TraceQL Metrics 시간 범위 제한: TraceQL Metrics 쿼리는 최대 24시간 범위만 지원됩니다. 장기 트렌드 분석이나 알림 규칙 설정에는 다음에서 설명할 metrics-generator + Mimir 조합이 필요합니다.
Mimir 크로스-시그널: 트레이스-메트릭 양방향 드릴다운
LGTM 스택이란 Loki(로그)·Grafana(시각화)·Tempo(트레이스)·Mimir(메트릭)를 묶어 부르는 Grafana 오픈소스 관측 가능성 스택입니다. 각 시그널이 유기적으로 연결되어 있어, 메트릭 이상 감지 → 해당 트레이스 드릴다운 → 연관 로그 확인까지 하나의 흐름으로 이어집니다.
이 연결을 가능하게 하는 핵심 컴포넌트가 metrics-generator입니다. Tempo 내부 컴포넌트로, 수집된 트레이스 스트림을 실시간으로 PromQL 호환 메트릭으로 변환하여 Mimir에 원격 쓰기(remote_write)하는 역할을 합니다. 이 컴포넌트가 활성화되어 있어야 아래 세 가지 크로스-시그널 방식을 모두 활용할 수 있습니다.
방법 1 — Exemplars: 메트릭 → 트레이스로 드릴다운
metrics-generator가 자동으로 Exemplar를 생성하여 Mimir 메트릭 포인트에 traceID를 연결합니다. Grafana 데이터소스 설정에 다음을 추가하면 됩니다.
# Grafana datasource 설정 (Mimir/Prometheus)
exemplarTraceIdDestinations:
- name: traceID
datasourceUid: tempo_uid설정 필드명 주의: Grafana 버전 및 설정 방식(YAML·UI·JSON provisioning)에 따라
exemplarTraceIdDestinations(camelCase) 형태가 사용됩니다. 사용 중인 Grafana 버전의 공식 문서를 기준으로 확인하는 것을 권장합니다.
설정 후 Mimir 대시보드에서 레이턴시 스파이크 포인트를 클릭하면, Tempo에서 해당 트레이스가 자동으로 열립니다.
방법 2 — Trace to Metrics: 트레이스 → 메트릭으로 점프
Tempo 데이터소스의 "Trace to metrics" 링크 설정으로, 특정 트레이스 뷰에서 관련 Mimir 메트릭으로 바로 이동할 수 있습니다.
# Grafana Tempo datasource 설정
traceToMetrics:
datasourceUid: mimir_uid
queries:
- name: "Request Rate"
query: 'rate(traces_spanmetrics_calls_total{service_name="${__span.resource.service.name}"}[5m])'
- name: "P99 Latency"
query: 'histogram_quantile(0.99, rate(traces_spanmetrics_duration_seconds_bucket{service_name="${__span.resource.service.name}"}[5m]))'변수 문법 주의:
${__span.resource.service.name}변수는 Grafana 버전에 따라${__span.tags.service.name}형태가 사용될 수도 있습니다. 사용 중인 Grafana 버전의 Trace to Metrics 공식 문서에서 정확한 문법을 확인하세요.
방법 3 — metrics-generator: 트레이스를 장기 메트릭으로 저장
# tempo.yaml
metrics_generator:
processor:
service_graphs:
enabled: true
span_metrics:
enabled: true
storage:
remote_write:
- url: http://mimir:9009/api/v1/push
# TLS, 인증 등 실무 필수 설정은 공식 문서 참조:
# https://grafana.com/docs/tempo/latest/configuration/#metrics_generator생성되는 주요 메트릭:
| 메트릭 이름 | 설명 |
|---|---|
traces_spanmetrics_calls_total |
호출 건수 |
traces_spanmetrics_duration_seconds_bucket |
레이턴시 히스토그램 |
traces_service_graph_request_total |
서비스 간 요청 수 |
traces_service_graph_request_failed_total |
서비스 간 실패 수 |
Mimir에서 PromQL로 payment-service P99를 조회하는 방법:
histogram_quantile(0.99,
rate(traces_spanmetrics_duration_seconds_bucket{
service_name="payment-service"
}[5m])
)레이블 이름 주의:
service_name등 실제 레이블 키는 metrics-generator 설정 및 환경에 따라 다를 수 있습니다. Grafana Explore에서traces_spanmetrics_calls_total메트릭을 먼저 조회하여 실제 레이블 키를 확인한 후 쿼리를 작성하는 것을 권장합니다.
RED 메트릭 Ad-hoc 분석: 전체 서비스 현황 한눈에
장애 대응이 일단락된 후, 전체 서비스의 현황을 파악하고 싶다면 다음 한 줄이면 충분합니다.
이것이 TraceQL Ad-hoc 분석의 핵심 패턴입니다.
{ resource.service.name =~ ".+" }
| rate() by (resource.service.name, status)이 쿼리의 출력: 서비스별·상태별(ok/error/unset) 요청률을 시계열 그래프로 확인할 수 있습니다. payment-service의 error 라인이 급증한 시점을 시각적으로 바로 확인할 수 있습니다.
이 한 줄로 별도 계측 코드 없이 모든 서비스의 Rate(처리율)·Error(에러 상태)를 실시간으로 확인할 수 있습니다. quantile_over_time()을 추가하면 Duration(지연 시간)까지 RED 메트릭 세 가지를 완성합니다.
RED 메트릭: Rate(처리율), Errors(에러율), Duration(지연 시간) 세 가지를 핵심 서비스 건강 지표로 삼는 관측 가능성 방법론입니다. TraceQL의
rate()와quantile_over_time()으로 이 세 가지를 별도 계측 없이 추출할 수 있습니다.
장단점 분석
장점
| 항목 | 내용 |
|---|---|
| Ad-hoc 유연성 | 사전 계측 없이 임의 속성 기반 실시간 집계 가능 |
| 풍부한 컨텍스트 | 메트릭에서 불가능한 개별 요청 단위 드릴다운 |
| 구조적 쿼리 | 스팬 계층 관계 기반 필터링으로 복잡한 분산 호출 패턴 탐색 |
| OTEL 완전 호환 | OpenTelemetry 시맨틱 컨벤션 속성을 바로 활용 |
| 크로스-시그널 | Exemplar를 통한 Mimir ↔ Tempo 원활한 전환 |
단점 및 주의사항
| 항목 | 내용 | 대응 방안 |
|---|---|---|
| 시간 범위 제한 | TraceQL Metrics는 최대 24시간 | metrics-generator → Mimir로 장기 보관 |
| 알림 불가 | TraceQL Metrics는 Grafana Managed Alerts 소스 미지원 | traces_spanmetrics_* 메트릭으로 alert rule 작성 |
| 쿼리 비용 | 대규모 트레이스에 복잡한 쿼리는 리소스 집약적 | 시간 범위 제한, 속성 인덱싱 최적화 |
| 샘플링 영향 | 샘플링 시 에러 스팬 필터링 결과의 대표성 저하 가능 | 에러 스팬 100% 수집하는 tail-based sampling 설정 |
| vParquet4 마이그레이션 | 이벤트/링크 쿼리는 vParquet4 블록만 지원 | Tempo 2.6+ 업그레이드 및 블록 재생성 고려 |
| Cardinality 폭발 | by() 절에 고카디널리티 속성(user_id 등) 사용 시 메모리 과다 |
by() 절은 저카디널리티 속성(service.name 등)만 사용 |
Cardinality(카디널리티): 특정 레이블이 가질 수 있는 고유 값의 수입니다.
user_id처럼 수백만 개의 고유값을 가지는 속성을 집계 키로 사용하면 시스템 메모리가 급격히 증가할 수 있습니다.
vParquet4: Grafana Tempo의 스토리지 포맷입니다. Tempo 2.6부터 기본값으로 변경되었으며, 이벤트·링크·배열 컬럼을 추가로 지원합니다.
실무에서 가장 흔한 실수
- TraceQL Metrics로 알림을 설정하려는 시도: TraceQL Metrics는 탐색용이지 알림 소스로 사용할 수 없습니다. 알림은 반드시 metrics-generator가 Mimir에 저장한
traces_spanmetrics_*메트릭 기반으로 작성하는 것을 권장합니다. by()절에 고카디널리티 속성 사용:by(span.user_id)처럼 수백만 개의 고유값을 가진 속성을 집계 키로 사용하면 메모리 폭발로 이어질 수 있습니다.resource.service.name,span.http.route처럼 수십~수백 개 수준의 속성을 사용하는 것이 안전합니다.- 24시간 이상 TraceQL Metrics 쿼리 시도: 범위 초과 시 쿼리가 실패하거나 결과가 잘립니다. 장기 분석이 필요한 경우 처음부터 Mimir의 PromQL 쿼리로 설계하는 편이 좋습니다.
마치며
오늘 다룬 것: payment-service 장애 대응 시나리오를 통해 에러 스팬 필터링, 구조적 연산자를 활용한 근본 원인 추적, TraceQL Metrics로 P99 실시간 추출, metrics-generator를 통한 Mimir 장기 메트릭 연동, 그리고 Exemplar 기반 메트릭-트레이스 양방향 드릴다운 워크플로우를 살펴봤습니다. TraceQL은 "트레이스에서 메트릭을 뽑아내는" 패러다임 전환을 통해, 개별 요청의 맥락과 시계열 집계 분석을 하나의 도구로 연결해줍니다.
지금 바로 시작해볼 수 있는 3단계:
- Grafana Explore에서 기본 에러 필터 실행해보기: Tempo 데이터소스를 선택한 후
{ status = error }를 입력하고,| rate() by (resource.service.name)을 추가해 서비스별 에러 발생 추이를 시각화해볼 수 있습니다. (Tempo 데이터소스가 아직 연결되어 있지 않다면 공식 데이터소스 설정 가이드를 참조하세요.) - metrics-generator 활성화하기:
tempo.yaml에metrics_generator.processor.span_metrics.enabled: true와 Mimirremote_write설정을 추가하면, 이후부터는 PromQL로 P99/에러율 알림 규칙 작성이 가능해집니다. - Exemplar 연동 설정하기: Grafana의 Mimir/Prometheus 데이터소스 설정에
exemplarTraceIdDestinations를 추가하면, 레이턴시 스파이크 포인트에서 클릭 한 번으로 원인 트레이스로 바로 이동하는 워크플로우를 경험해볼 수 있습니다.
시리즈 다음 편
Grafana Tempo의 tail-based sampling 설정으로 에러 스팬 100% 수집과 비용 최적화를 동시에 달성하는 방법을 다룰 예정입니다.
참고 자료
- TraceQL 공식 문서 | Grafana Tempo
- TraceQL 쿼리 구성 가이드 | Grafana Tempo
- TraceQL Metrics 함수 레퍼런스 | Grafana Tempo
- TraceQL Metrics 쿼리 | Grafana Tempo
- Metrics from Traces | Grafana Tempo
- Grafana Tempo 2.4 릴리즈: TraceQL Metrics 도입 | Grafana Blog
- Grafana Tempo 2.5 릴리즈: vParquet4, 스트리밍, quantile_over_time | Grafana Blog
- Grafana Tempo 2.6 릴리즈 | Grafana Blog
- Grafana Tempo 2.7 릴리즈 | Grafana Blog
- Tempo 2.2: 구조적 연산자 소개 | Grafana Blog
- Trace to Metrics 설정 | Grafana 공식 문서
- TraceQL로 문제 해결하기 | Grafana Tempo
- Traces to Metrics Ad-hoc 쿼리 | Grafana Blog
- 고급 TraceQL 튜토리얼 | Giant Swarm
- Service Graph 메트릭 분석 | Grafana Tempo