OpenTelemetry 메트릭의 종류(Counter, Gauge, Histogram), 카디널리티 관리, Exemplars를 통한 메트릭-트레이스 연결, Prometheus 호환을 학습합니다.
**메트릭(Metrics)**은 시간에 따른 수치 측정값입니다. CPU 사용률, 요청 처리량, 응답 지연 시간 같은 지표를 일정 주기로 수집하여 시스템의 건강 상태를 파악합니다. 트레이스가 개별 요청의 상세 경로를 보여준다면, 메트릭은 전체 시스템의 통계적 상태를 보여줍니다.
메트릭의 핵심 장점은 고정된 저장 비용입니다. 요청 수가 아무리 많아도 메트릭은 집계된 숫자 하나로 표현되므로, 트레이스나 로그에 비해 저장과 쿼리 비용이 훨씬 낮습니다.
Counter는 오직 증가만 하는 누적 카운터입니다. 요청 수, 처리된 바이트 수, 에러 발생 횟수처럼 "총 누적량"을 측정할 때 사용합니다.
from opentelemetry import metrics
meter = metrics.get_meter("order-service", "1.0.0")
# Counter 생성
request_counter = meter.create_counter(
name="http.server.request.count",
description="Total number of HTTP requests received",
unit="requests",
)
# 요청이 들어올 때마다 증가
def handle_request(method, path, status_code):
request_counter.add(1, {
"http.request.method": method,
"url.path": path,
"http.response.status_code": status_code,
})Counter의 값은 절대로 감소하지 않습니다. "현재 초당 요청 수"를 알고 싶다면 Prometheus의 rate() 함수처럼 시간 구간 대비 증가율을 계산합니다.
UpDownCounter는 증가와 감소가 모두 가능한 카운터입니다. 현재 활성 연결 수, 큐에 대기 중인 작업 수처럼 "현재 진행 중인 양"을 측정합니다.
active_connections = meter.create_up_down_counter(
name="http.server.active_requests",
description="Number of active HTTP requests being processed",
unit="requests",
)
def handle_request():
active_connections.add(1) # 요청 시작
try:
process()
finally:
active_connections.add(-1) # 요청 완료Gauge는 특정 시점의 순간 값을 기록합니다. CPU 사용률, 메모리 사용량, 온도처럼 "지금 현재의 상태"를 나타냅니다.
# 비동기 Gauge -- 콜백으로 값 수집
def get_cpu_usage(options):
import psutil
cpu_percent = psutil.cpu_percent()
options.observe(cpu_percent, {"host.name": "web-01"})
cpu_gauge = meter.create_observable_gauge(
name="system.cpu.utilization",
callbacks=[get_cpu_usage],
description="Current CPU utilization percentage",
unit="%",
)
# 비동기 방식으로 메트릭 수집 주기마다 콜백이 호출됨OpenTelemetry에서 Gauge는 기본적으로 비동기(Observable) 방식입니다. SDK가 메트릭을 내보내는 시점에 콜백 함수를 호출하여 현재 값을 수집합니다. 이는 Prometheus의 Gauge와 다른 접근 방식이므로 주의가 필요합니다.
Histogram은 값의 분포를 측정합니다. 응답 지연 시간, 요청 크기, 처리 시간의 분포를 버킷(bucket)으로 나누어 기록합니다. 평균뿐 아니라 p50, p90, p99 같은 백분위수(percentile)를 계산할 수 있어 성능 분석에 필수적입니다.
import time
request_duration = meter.create_histogram(
name="http.server.request.duration",
description="Duration of HTTP server requests",
unit="s",
)
def handle_request(method, path):
start = time.time()
try:
result = process_request()
finally:
duration = time.time() - start
request_duration.record(duration, {
"http.request.method": method,
"url.path": path,
})기본 버킷 경계값은 다음과 같습니다.
[0, 5, 10, 25, 50, 75, 100, 250, 500, 750, 1000, 2500, 5000, 7500, 10000]HTTP 응답 시간처럼 밀리초 단위의 측정에는 커스텀 버킷이 더 적합합니다.
from opentelemetry.sdk.metrics.view import View, ExplicitBucketHistogramAggregation
# 커스텀 버킷 설정
latency_view = View(
instrument_name="http.server.request.duration",
aggregation=ExplicitBucketHistogramAggregation(
boundaries=[0.005, 0.01, 0.025, 0.05, 0.1, 0.25, 0.5, 1.0, 2.5, 5.0, 10.0]
),
)| 종류 | 방향 | 동기/비동기 | 대표 용도 |
|---|---|---|---|
| Counter | 단조 증가 | 동기 | 요청 수, 에러 수, 전송 바이트 |
| UpDownCounter | 양방향 | 동기 | 활성 연결, 큐 크기, 스레드 수 |
| Histogram | N/A (분포) | 동기 | 응답 시간, 요청 크기 |
| Gauge | 양방향 | 비동기 | CPU 사용률, 메모리 사용량 |
| ObservableCounter | 단조 증가 | 비동기 | 시스템 카운터(네트워크 패킷 등) |
**카디널리티(Cardinality)**는 속성 값의 고유한 조합 수를 의미합니다. 메트릭 속성의 카디널리티가 높으면 저장해야 할 시계열(time series)의 수가 폭발적으로 증가합니다.
# 나쁜 예 -- 카디널리티 폭발
request_counter.add(1, {
"http.request.method": "GET", # 5가지 정도
"url.path": "/users/12345", # 수백만 가지 (사용자 ID 포함)
"user.id": "usr-98765", # 수백만 가지
"http.response.status_code": 200, # 수십 가지
})
# 시계열 수: 5 x 1,000,000 x 1,000,000 x 20 = 폭발
# 좋은 예 -- 카디널리티 관리
request_counter.add(1, {
"http.request.method": "GET", # 5가지
"url.template": "/users/:id", # 수십 가지 (경로 패턴)
"http.response.status_code": 200, # 수십 가지
})
# 시계열 수: 5 x 30 x 20 = 3,000 (관리 가능)카디널리티 폭발은 Prometheus 같은 메트릭 백엔드의 OOM(Out of Memory)을 유발하는 가장 흔한 원인입니다. 메트릭 속성에 사용자 ID, 요청 ID, IP 주소 같은 고유 값을 절대 포함하지 마세요. 이런 상세 정보는 트레이스 속성으로 기록하는 것이 적합합니다.
/users/123 → /users/:id)from opentelemetry.sdk.metrics.view import View
# 특정 메트릭에서 고카디널리티 속성 제거
cardinality_view = View(
instrument_name="http.server.request.duration",
attribute_keys=["http.request.method", "http.response.status_code"],
# url.path 같은 고카디널리티 속성은 자동 제거
)Exemplars는 메트릭 데이터 포인트에 연결된 트레이스의 참조입니다. "p99 지연 시간이 2초를 넘었다"는 메트릭 알림을 받았을 때, 해당 시점의 느린 트레이스를 바로 조회할 수 있게 해주는 핵심 연결 고리입니다.
from opentelemetry.sdk.metrics import MeterProvider
from opentelemetry.sdk.metrics.export import PeriodicExportingMetricReader
from opentelemetry.exporter.otlp.proto.grpc.metric_exporter import OTLPMetricExporter
from opentelemetry.sdk.metrics.exemplar import AlwaysOnExemplarFilter
# Exemplar 필터 활성화
provider = MeterProvider(
metric_readers=[
PeriodicExportingMetricReader(
OTLPMetricExporter(endpoint="http://collector:4317"),
export_interval_millis=10000,
)
],
exemplar_filter=AlwaysOnExemplarFilter(),
)Exemplars는 Grafana에서 특히 강력합니다. Prometheus 메트릭 그래프에서 Exemplar 점을 클릭하면 Tempo의 해당 트레이스로 바로 이동할 수 있습니다. 이 연결은 메트릭 이상 탐지에서 근본 원인 분석(RCA)까지의 시간을 크게 단축합니다.
메트릭은 일정 주기로 집계되어 내보내집니다. 기본 수집 주기는 60초이며, 환경에 따라 조정할 수 있습니다.
from opentelemetry.sdk.metrics.export import PeriodicExportingMetricReader
reader = PeriodicExportingMetricReader(
exporter=OTLPMetricExporter(endpoint="http://collector:4317"),
export_interval_millis=15000, # 15초마다 내보내기
export_timeout_millis=10000, # 내보내기 타임아웃 10초
)OpenTelemetry 메트릭은 두 가지 집계 임시성을 지원합니다.
| 임시성 | 설명 | 호환 백엔드 |
|---|---|---|
| Cumulative | 프로세스 시작부터의 누적값 | Prometheus (기본) |
| Delta | 마지막 내보내기 이후의 변화량 | Datadog, Dynatrace |
Prometheus와 연동할 때는 Cumulative, 상용 APM과 연동할 때는 Delta가 일반적입니다. OTLP Exporter에서 설정할 수 있습니다.
OpenTelemetry 메트릭은 Prometheus와의 호환성을 깊이 고려하여 설계되었습니다.
receivers:
otlp:
protocols:
grpc:
endpoint: "0.0.0.0:4317"
exporters:
prometheus:
endpoint: "0.0.0.0:8889"
namespace: "otel"
const_labels:
environment: "production"
service:
pipelines:
metrics:
receivers: [otlp]
processors: [batch]
exporters: [prometheus]scrape_configs:
- job_name: "otel-collector"
scrape_interval: 15s
static_configs:
- targets: ["collector:8889"]OTel 메트릭 이름은 Prometheus 형식으로 자동 변환됩니다.
| OTel 이름 | Prometheus 이름 |
|---|---|
http.server.request.duration (Histogram, 단위: s) | otel_http_server_request_duration_seconds |
http.server.request.count (Counter) | otel_http_server_request_count_total |
system.cpu.utilization (Gauge, 단위: %) | otel_system_cpu_utilization_percent |
네임스페이스 접두사(otel_), 단위 접미사(_seconds, _total), 점을 밑줄로 변환하는 규칙이 자동 적용됩니다.
트레이스와 메트릭을 동시에 설정하는 전체 초기화 코드입니다.
from opentelemetry import trace, metrics
from opentelemetry.sdk.trace import TracerProvider
from opentelemetry.sdk.trace.export import BatchSpanProcessor
from opentelemetry.sdk.metrics import MeterProvider
from opentelemetry.sdk.metrics.export import PeriodicExportingMetricReader
from opentelemetry.sdk.metrics.view import View, ExplicitBucketHistogramAggregation
from opentelemetry.exporter.otlp.proto.grpc.trace_exporter import OTLPSpanExporter
from opentelemetry.exporter.otlp.proto.grpc.metric_exporter import OTLPMetricExporter
from opentelemetry.sdk.resources import Resource
def setup_telemetry():
resource = Resource.create({
"service.name": "payment-service",
"service.version": "2.0.0",
"deployment.environment": "production",
})
# Traces
trace_provider = TracerProvider(resource=resource)
trace_provider.add_span_processor(
BatchSpanProcessor(OTLPSpanExporter(endpoint="http://collector:4317"))
)
trace.set_tracer_provider(trace_provider)
# Metrics
latency_view = View(
instrument_name="http.server.request.duration",
aggregation=ExplicitBucketHistogramAggregation(
boundaries=[0.005, 0.01, 0.025, 0.05, 0.1, 0.25, 0.5, 1.0, 2.5, 5.0]
),
)
metric_reader = PeriodicExportingMetricReader(
OTLPMetricExporter(endpoint="http://collector:4317"),
export_interval_millis=15000,
)
metric_provider = MeterProvider(
resource=resource,
metric_readers=[metric_reader],
views=[latency_view],
)
metrics.set_meter_provider(metric_provider)
return trace.get_tracer("payment-service"), metrics.get_meter("payment-service")이번 장에서는 OpenTelemetry 메트릭의 네 가지 종류(Counter, UpDownCounter, Histogram, Gauge)와 각각의 용도를 학습했습니다. 카디널리티 관리의 중요성과 View를 통한 제어 방법을 알아보았고, Exemplars를 통해 메트릭에서 트레이스로 연결하는 방법도 살펴보았습니다. Prometheus와의 호환성도 확인하여 기존 Prometheus 기반 인프라와의 통합 방법을 파악했습니다.
다음 장에서는 3대 신호의 마지막인 로그(Logs)를 다룹니다. OTel 로그 데이터 모델, 로그와 트레이스의 상관관계, 기존 로거(Python logging, Go slog)와의 브릿지 방법을 학습합니다.
이 글이 도움이 되셨나요?
관련 주제 더 보기
OpenTelemetry 로그 데이터 모델, 로그-트레이스 상관관계, 기존 로거 브릿지(Python logging, Go slog), 구조화 로그와 로그 레벨 전략을 학습합니다.
스팬의 내부 구조와 종류, 부모-자식 관계, 샘플링 전략(Head/Tail/Rate)을 학습하고 Python으로 분산 추적을 직접 구현합니다.
자동 계측과 수동 계측의 차이를 이해하고, Python/Node.js/Go 각 언어별 SDK 활용법과 커스텀 스팬/메트릭 생성을 실습합니다.