실전 운영과 성능 고려사항
프로덕션 OTel이 어려운 이유
개발 환경에서는 ConsoleExporter에 스팬 몇 개를 찍어보는 것으로 충분하다. 프로덕션은 다르다. 트래픽이 1000 RPS라면, 요청당 스팬 20개를 계측할 때 초당 2만 개의 스팬이 생긴다. 하루 17억 개다.
세 가지 압박이 동시에 온다.
볼륨: 대부분의 트레이스는 "정상, 느리지 않음"이다. 이것들을 전부 저장하면 비용이 폭증한다.
오버헤드: 계측 코드가 실제 요청 레이턴시에 영향을 준다. 잘못 설정된 동기 Exporter는 요청마다 수십 ms를 더한다.
카디널리티: user.id나 request.uuid처럼 고유값이 많은 속성을 메트릭 레이블로 쓰면, Prometheus 메모리가 수십 GB로 불어난다.
이 챕터는 이 세 가지 문제를 다루는 실전 패턴을 정리한다.
샘플링 전략: Head vs Tail
가장 근본적인 볼륨 제어 수단은 샘플링이다.
Head 샘플링
트레이스가 시작하는 순간 보존 여부를 결정한다. SDK의 TraceIdRatioBased 샘플러가 대표적이다.
from opentelemetry.sdk.trace.sampling import TraceIdRatioBased
sampler = TraceIdRatioBased(rate=0.1) # 10%만 보존장점: 결정이 즉시 이루어져 Collector 부담이 없다. 단점: 에러 트레이스도 90%가 버려진다. 중요한 트레이스를 놓칠 확률이 높다.
Tail 샘플링
트레이스의 모든 스팬이 도착한 뒤 결정한다. Collector의 tail_sampling processor가 담당한다. 에러·느린 요청은 100% 보존하고, 정상 요청만 낮은 비율로 샘플링하는 정책이 가능하다.
processors:
tail_sampling:
decision_wait: 10s # 스팬이 모이길 기다리는 시간
num_traces: 100000 # 메모리에 유지할 최대 트레이스 수
expected_new_traces_per_sec: 10
policies:
- name: always-sample-errors
type: status_code
status_code: { status_codes: [ERROR] }
- name: always-sample-slow
type: latency
latency: { threshold_ms: 500 }
- name: sample-rest
type: probabilistic
probabilistic: { sampling_percentage: 5 }단점: 트레이스 전체가 동일한 Collector 인스턴스에 도달해야 한다. 게이트웨이를 여러 인스턴스로 수평 확장할 때 loadbalancingexporter로 trace_id 기준 라우팅이 필요하다.
Memory Limiter와 Batch 튜닝
Memory Limiter 용량 계산
프로덕션 Collector의 메모리 한도는 예상 사용량에 30% 여유를 더해 설정한다.
| 항목 | 크기 |
|---|---|
| 기본 오버헤드 | 200 MiB |
| OTLP receiver (2개) | 100 MiB |
| Batch processor 버퍼 | 50 MiB |
| Export 큐 (5000개) | 500 MiB |
| Tail sampling 버퍼 | 500 MiB |
| 소계 | 1350 MiB |
| limit_mib (30% 여유) | 1800 MiB |
processors:
memory_limiter:
check_interval: 1s
limit_mib: 1800
spike_limit_mib: 400 # 순간 급증 허용 폭spike_limit_mib는 갑작스러운 트래픽 급증 때 순간적으로 초과할 수 있는 양이다. limit_mib - spike_limit_mib = 1400 MiB가 실제 soft_limit이다. soft limit을 넘으면 신규 데이터를 거부(503 반환)하고, limit_mib까지 올라가면 강제 drop한다.
Batch Processor 튜닝
processors:
batch:
timeout: 5s # 기본 200ms → 5초로 증가 시 처리량 향상
send_batch_size: 4096 # 기본 1024 → 4096
send_batch_max_size: 8192처리량보다 레이턴시가 중요한 트레이싱 파이프라인은 짧게(200ms / 1024), 볼륨이 큰 로그·메트릭은 길게(5s / 4096) 설정한다.
카디널리티 관리
메트릭에서 카디널리티 폭발은 Prometheus 메모리를 파괴한다. OTel에서 자주 발생하는 패턴과 해결책이다.
문제 패턴: http.url 전체를 레이블로 사용하면 요청마다 다른 시계열이 생긴다.
# 나쁜 예 — /orders/123, /orders/456, /orders/789... 무한 증가
http_server_duration{http.url="/orders/123"} ...해결: attributes processor로 고유값 속성을 삭제하거나, filter processor로 URL을 패턴별로 정규화한다.
processors:
attributes:
actions:
- key: http.url
action: delete # 메트릭에서는 제거
- key: http.route # /orders/{id} 형태의 경로 패턴만 유지
action: insert
from_attribute: http.target일반 원칙: 트레이스 속성은 고유해도 괜찮다. 트레이스는 개별 스팬을 저장하지 시계열로 집계하지 않는다. 메트릭 레이블은 제한된 값만 허용한다. 레이블 조합의 수 = 시계열 수임을 기억하라.
SDK 오버헤드 최소화
계측 방식별 레이턴시 오버헤드 기준(참고치, 환경마다 다름):
| 계측 방식 | 추가 레이턴시 |
|---|---|
| 자동 계측 (Zero-code) | 1~5 ms |
| 수동 계측 (OTel API 직접) | 0.1~0.5 ms |
| BatchSpanProcessor (비동기) | ~0 ms (백그라운드) |
| SimpleSpanProcessor (동기) | 전체 네트워크 RTT |
프로덕션에서 SimpleSpanProcessor는 절대 금지다. 요청 처리 중에 백엔드 응답을 기다린다.
자동 계측은 편리하지만 과도한 스팬을 만들 수 있다. filter processor로 헬스체크, 내부 probe, 정적 파일 요청 등의 노이즈를 Collector에서 제거한다.
processors:
filter/drop-healthcheck:
traces:
span:
- 'attributes["http.route"] == "/healthz"'
- 'attributes["http.route"] == "/readyz"'
- 'attributes["http.route"] == "/metrics"'Collector 자체 모니터링
Collector는 자기 자신의 메트릭을 Prometheus 형식으로 /metrics 엔드포인트에서 내보낸다. 주요 지표:
| 메트릭 | 의미 | 경보 기준 |
|---|---|---|
otelcol_processor_refused_spans | memory_limiter가 거부한 스팬 수 | > 0 → 메모리 한도 증가 검토 |
otelcol_exporter_send_failed_spans | 내보내기 실패 수 | > 0 → 백엔드 연결 확인 |
otelcol_receiver_accepted_spans | 수신 스팬 수 (처리량 baseline) | 급락 시 앱 계측 확인 |
otelcol_processor_batch_timeout_trigger_send | 타임아웃으로 배치 전송된 횟수 | 높으면 batch_size 증가 고려 |
process_runtime_heap_alloc_bytes | Collector 힙 메모리 | limit의 80% 초과 경보 |
zpages(/debug/tracez, /debug/pipelinez)는 Collector 내부를 실시간으로 볼 수 있는 디버그 UI다. 개발 환경에서 파이프라인 문제를 진단할 때 유용하다.
비용 관리 실전 체크리스트
OTel 관측성 비용의 대부분은 저장소(Grafana Cloud, Datadog, New Relic)에서 발생한다. 데이터를 줄이는 순서:
- filter processor — 헬스체크, 메트릭 수집 경로, 내부 probe의 트레이스·로그를 Collector에서 drop.
- tail sampling — 정상·성공 트레이스를 5~10% 샘플링. 에러·느린 트레이스는 100% 보존.
- attributes processor — 저장 비용이 큰 고카디널리티 속성(URL 전체, 사용자 ID) 제거.
- 메트릭 집계 수준 조정 — 히스토그램 버킷을 꼭 필요한 범위로만 설정.
- 로그 레벨 필터 — DEBUG 로그는 Collector에서 drop, WARN 이상만 전송.
실제 팀 경험치: 이 다섯 단계를 적용하면 데이터 볼륨을 40~70% 줄일 수 있다.
Kubernetes 운영 체크리스트
| 항목 | 권장 설정 |
|---|---|
| DaemonSet agent 리소스 | requests: 200m CPU / 400Mi, limits: 1 CPU / 1Gi |
| Gateway Deployment 레플리카 | 최소 2개, HPA CPU 60% 기준 |
| tail_sampling 사용 시 | loadbalancingexporter로 trace_id 기준 라우팅 필수 |
| health_check | Kubernetes liveness probe / :13133 |
| 설정 관리 | ConfigMap으로 관리, 변경 시 롤링 업데이트 |
| 시크릿 | exporter endpoint·auth는 환경변수 또는 k8s Secret 참조 |
| 업그레이드 | otelcol-contrib 릴리스 노트 확인 (processor API 변경 잦음) |
OTel Operator (Kubernetes)
opentelemetry-operator를 쓰면 OpenTelemetryCollector CRD로 Collector를 선언적으로 관리하고, Instrumentation CRD로 자동 계측을 Pod에 주입할 수 있다.
apiVersion: opentelemetry.io/v1alpha1
kind: Instrumentation
metadata:
name: my-instrumentation
spec:
exporter:
endpoint: http://otel-collector:4317
propagators:
- tracecontext
- baggage
sampler:
type: parentbased_traceidratio
argument: "0.1"
python:
image: ghcr.io/open-telemetry/opentelemetry-operator/autoinstrumentation-python:latestinstrumentation.opentelemetry.io/inject-python: "true" 어노테이션을 Pod에 추가하면 init container가 SDK를 자동 주입한다. 코드 변경 없이 계측이 가능하지만, 자동 계측이 생성하는 스팬 양을 반드시 확인하고 filter로 제어해야 한다.
한 줄 정리
프로덕션 OTel 운영의 핵심은 tail sampling으로 에러 트레이스를 100% 보존하면서 정상 트레이스를 5~10%로 줄이고, memory_limiter로 OOM을 막고, 고카디널리티 속성을 메트릭에서 제거하는 것이다. Collector 자체 메트릭을 Prometheus로 수집해 refused/failed 경보를 반드시 걸어야 한다.
References
- https://opentelemetry.io/docs/collector/configuration/
- https://oneuptime.com/blog/post/2026-01-25-memory-limiter-opentelemetry/view
- https://oneuptime.com/blog/post/2026-02-06-batch-processor-opentelemetry-collector/view
- https://oneuptime.com/blog/post/2026-02-06-reduce-opentelemetry-performance-overhead-production/view
- https://oneuptime.com/blog/post/2026-02-06-cut-observability-costs-opentelemetry-filtering-sampling/view
- https://oneuptime.com/blog/post/2026-02-06-handle-high-cardinality-metrics-opentelemetry/view
- https://www.michal-drozd.com/en/blog/otel-tail-sampling/
- https://last9.io/guides/opentelemetry/deploying-opentelemetry-at-scale-production-patterns-that-work/
- https://betterstack.com/community/guides/observability/opentelemetry-sampling/
- https://medium.com/@alokrahuldevocs/day-164-opentelemetry-collector-processors-the-control-layer-of-your-observability-pipeline-066cceeb82d8