OpenTelemetry를 사용해 느린 쿼리를 실행 가능한 신뢰성 메트릭으로 전환하는 방법
Source: Dev.to
번역할 텍스트를 제공해 주시면, 해당 내용을 한국어로 번역해 드리겠습니다.
소개
느린 SQL 쿼리는 사용자 경험을 저하시하고, 연쇄적인 장애를 일으키며, 단순한 작업을 생산 사고로 전환시킵니다. 전통적인 해결책? 더 많은 텔레메트리를 수집하는 것입니다. 하지만 텔레메트리가 늘어난다고 해서 이해가 깊어지는 것은 아니며, 단지 살펴볼 항목이 늘어날 뿐입니다.
언젠가 분석할 수 있는 데이터 스트림으로 트레이스를 다루는 대신, 의사결정 순간에 무엇이 중요한지에 대해 확고히 해야 합니다. The Signal in the Storm에서 논의했듯이, 원시 텔레메트리는 의미 있는 패턴을 추출할 때 비로소 유용해집니다.
이 가이드에서는 OpenTelemetry 데이터베이스 스팬을 대시보드와 알림에 사용할 수 있는 스팬 기반 메트릭으로 변환하는 반복 가능한 워크플로우를 구축하게 됩니다—이를 통해 무엇이 느린지, 가장 중요한 것이 무엇인지, 그리고 무엇이 최근에 악화되었는지를 식별할 수 있습니다.
사용 사례
우리는 느린 SQL 쿼리를 예시로 들어 두 가지 사용 사례를 제시합니다:
- 최적화 – 트래픽을 가중치로 하여 더 빠르게 만들면 가장 큰 가치를 제공하는 쿼리는 무엇인가요?
- 사고 대응 – 현재 비정상적으로 동작하고 있는 쿼리는 무엇인가요?
Lab Overview
우리는 애플리케이션이 OpenTelemetry 트레이스를 내보내는 실험실을 구축하고, 이를 실행 가능한 메트릭으로 정제합니다. 먼저 간단한 느린 쿼리 감지를 시작으로, 트래픽 가중 영향을 추가하고, 마지막으로 이상 감지를 수행합니다.
이론을 건너뛰시겠습니까? 아래 실험실 섹션으로 이동하세요.
왜 “느림”은 문제라기보다 증상인가
50 ms 쿼리는 보고 대시보드에는 괜찮을 수 있지만 결제 단계에서는 치명적일 수 있습니다. High Performance MySQL이 강조하듯, 쿼리가 느린 이유를 이해하는 것이 해결 방법을 결정합니다. 가장 흔한 원인은 다음과 같습니다:
| 문제 | 설명 |
|---|---|
| 인덱스 누락 또는 사용 불가 | 전체 테이블 스캔; 예: SELECT * FROM orders WHERE customer_id = $1는 10 K 행에서는 20 ms이지만 10 M 행에서는 몇 분이 걸립니다. |
| 잘못된 조인/집계 계획 | 플래너가 카디널리티를 잘못 판단해 잘못된 조인 전략을 선택합니다. |
| 자원 경쟁 | 락 경쟁, 연결 풀 고갈, CPU/I/O 포화, 메모리 압박. |
| 플랜 퇴보 | 파라미터에 민감한 플랜, 대량 로드 후 오래된 통계. |
| N+1 문제 | 많은 빠른 쿼리(예: 100 × 2 ms)가 합쳐져 큰 지연을 초래합니다. |
| 맥락 부족 | 데이터베이스 도구는 무엇이 느린지 보여주지만, 사용자 서비스에 왜 중요한지는 알려주지 않습니다. |
컨텍스트‑풍부한 트레이스의 가치
분산 트레이스는 각 데이터베이스 스팬을 요청 컨텍스트(서비스, 엔드포인트, 사용자)에 포함합니다. 로그와 트레이스를 사후에 연관시키는 대신, 전체 애플리케이션 컨텍스트를 활용해 트레이스에서 직접 느린 쿼리를 분석할 수 있습니다.
빌딩 블록
- OpenTelemetry Collector와
docker-otel-lgtm을 결합한 (Grafana 스택: Loki, Grafana, Tempo, Mimir). - 샘플 앱: Go 기반 “Album API”로 PostgreSQL에서 음악 앨범 데이터를 제공하며
otelsql로 계측됨. - 세 개의 대시보드:
- 지속 시간별 쿼리
- 트래픽(영향도) 가중치가 적용된 쿼리
- 이상 탐지
Lab Setup
git clone https://github.com/causely-oss/slow-query-lab
cd slow-query-lab
docker-compose up -d
실행이 완료되면 http://localhost:3001에서 Grafana를 열어 대시보드를 탐색하세요.
Dashboard 1 – 지속 시간별 느린 SQL
TraceQL query
{ span.db.system != "" } | select(span.db.query.text, span.db.statement)
- 루트 오퍼레이션(API 엔드포인트)과 SQL 문을 기준으로 그룹화합니다.
- 지속 시간을 평균, 최대값, 카운트로 집계합니다.
- 평균 지속 시간(가장 느린 순)으로 정렬합니다.
What You Get
- 전체 애플리케이션 컨텍스트와 함께 가장 느린 쿼리 목록 테이블.
- 발생 횟수 카운트.
- 디버깅을 위한 개별 트레이스로 클릭‑스루.
Limitation
평균 지속 시간으로 정렬하면 트래픽 양을 무시합니다. 2 초가 5번 실행된 쿼리는 150 ms가 10 K번 실행된 쿼리보다 “더 나쁘게” 보이지만, 후자는 더 많은 사용자에게 영향을 미칩니다.
대시보드 2 – 트래픽 가중 임팩트
Impact formula
Impact = Avg Duration × Count
같은 TraceQL 쿼리를 사용하지만, 계산된 Impact 필드를 추가하고 이를 기준으로 정렬합니다.
추가 인사이트
- 서비스 분류 – 각 쿼리를 트리거한 서비스가 무엇인지.
- 지연 시간 분포 – 시간에 따른 지속 시간을 시각화합니다.
- 임팩트 기준 상위 쿼리 – 최적화 작업의 우선순위를 정합니다.
왜 중요한가
대용량이면서 중간 정도로 느린 쿼리가 드물지만 매우 느린 쿼리보다 먼저 나타나, “어떤 느린 쿼리를 먼저 최적화해야 할지”에 대한 설득력 있는 답을 제공합니다.
Dashboard 3 – 이상 탐지
“무엇이 바뀌었는가?”에 답하기 위해 spanmetrics 커넥터를 사용해 스팬에서 메트릭을 추출합니다.
Collector Configuration (excerpt)
connectors:
spanmetrics:
dimensions:
- name: db.system
default: "unknown"
- name: db.query.text
- name: db.statement
- name: db.name
default: "unknown"
exemplars:
enabled: true
service:
pipelines:
traces:
receivers: [otlp]
processors: [transform, batch]
exporters: [spanmetrics, otlphttp/lgtm]
metrics:
receivers: [spanmetrics]
processors: [batch]
exporters: [otlphttp/lgtm]
이 커넥터는 쿼리 지연 시간에 대한 히스토그램 메트릭을 생성하며, 라벨은 다음과 같습니다:
service_name– 호출한 서비스db_system– 예:postgresqldb_query_text/db_statement– SQL 쿼리(템플릿)db_name– 데이터베이스 이름
이 메트릭은 Mimir(Prometheus 호환) 에 저장되고, 여기서 PromQL 기반 이상 탐지를 적용합니다.
Prometheus Recording Rules (from Grafana’s Anomaly Detection framework)
- Baseline – 과거 값들의 평활화된 평균.
- Upper/Lower bands – baseline ± N 표준편차.
- Anomaly – 현재 값이 밴드를 초과할 때.
Anomaly Detection Dashboard
- 현재 지연 시간을 적응형 baseline 밴드와 함께 표시합니다.
- 쿼리별 이상 현상을 강조합니다.
- 빠른 식별을 위해 쿼리별 세부 정보를 보여줍니다.
How It Answers “What Has Changed?”
- 항상 느린 쿼리(예: baseline = 450 ms)는 450 ms를 유지하는 한 이상으로 감지되지 않습니다.
- 보통 빠른 쿼리(benchmark = 50 ms)가 200 ms로 급증하면 이상으로 감지됩니다.
메트릭 위생
-
카디널리티 폭발 – 메트릭 라벨에 있는 raw SQL이 리터럴 값마다 시리즈를 생성합니다. 완화 방법:
- 준비된 문장(prepared statements) 사용(템플릿을 캡처하고 리터럴은 캡처하지 않음).
- 쿼리 텍스트 정규화.
- spanmetrics 커넥터에서
aggregation_cardinality_limit설정.
-
민감 데이터 – 내보내기 전에 Collector에서 민감한 속성을 마스킹하거나 삭제합니다.
-
베이스라인 워밍‑업 – 적응형 규칙은 24–48 시간의 데이터가 필요합니다; 초기에는 넓은 밴드로 시작하고 시간이 지남에 따라 좁힙니다.
From Symptoms to Root Causes
이상 탐지를 사용하더라도 증상만을 보고 있습니다. 실제 사고는 종종 여러 증상이 얽혀 있어 상관관계 파악이 필요합니다:
- 스키마 마이그레이션 후 인덱스 누락
- 오래된 통계 때문에 발생한 쿼리‑플랜 회귀
- 동시 배치 작업으로 인한 락 경합
- 소음이 많은 이웃으로 인한 리소스 압박
- 업스트림 서비스 성능 저하가 초래한 재시도 폭풍
수동으로 트리아지를 수행할 수는 있지만 시간 소모가 크고 규모에 맞추기 어렵습니다.
Enter Causely
Causely는 우리가 만든 패턴을 자동화합니다:
- Distill – 느린 쿼리와 기타 증상을 추출합니다.
- Causal model – 증상을 시스템 의존성(엔드포인트, 사용자, 업스트림 서비스)과 연결합니다.
- Root‑cause identification – 인과 관계 체인을 추적합니다(예: “인덱스가 삭제돼서 검색 쿼리가 느려졌다”).
- Actionable recommendations – AskCausely를 통해 구체적인 다음 단계(인덱스 추가, 배포 롤백, 업스트림 압박 해결)를 제공합니다.
Distill‑detect‑surface 파이프라인이 기반이며, Causely는 이를 확장해 대규모 전체 원인 분석을 가능하게 합니다.
결론
OpenTelemetry 데이터베이스 스팬을 메트릭으로 변환하고, 트래픽 가중 임팩트 점수를 적용하며, 이상 탐지를 계층화함으로써 다음을 얻을 수 있습니다:
- 전체 애플리케이션 컨텍스트와 함께 느린 쿼리에 대한 즉각적인 가시성.
- 실제 사용자 영향에 기반한 우선순위 지정.
- 비정상적인 행동의 자동 감지.
이를 Causely의 인과 추론과 결합하면 증상에서 근본 원인으로 이동할 수 있어, 더 빠르고 데이터‑드리븐한 성능 엔지니어링 및 사고 대응이 가능해집니다.
직접 시도해 보세요: Causely에 느린 쿼리에 대해 물어보고, 그것이 근본 원인과 어떻게 연결되는지 확인하세요.