관측성 도입 초기에 자동 계측은 매우 매력적이다. agent를 붙이거나 라이브러리를 추가하면 HTTP, 데이터베이스, 메시지, 외부 호출에 span이 생기고, 서비스별로 수작업 wrapper를 작성하는 시간을 줄일 수 있다. 하지만 조직의 서비스와 언어가 늘어난 뒤에는 “span이 생겼다"만으로 운영 품질을 판단할 수 없다. 같은 결제 요청이 Java 서비스에서는 POST /payments로, Node 서비스에서는 URL 원문으로, proxy에서는 다른 resource로 기록되면 dashboard·SLO·incident query는 서로 다른 사실을 말하게 된다.
9월 25일 OpenTelemetry가 공개한 계측 생태계 정리는 API·SDK·OTLP·semantic convention·instrumentation·Collector가 서로 다른 역할을 한다는 점을 다시 분명히 한다. 특히 계측은 실행을 관측 데이터로 바꾸는 경계이며, convention은 서로 다른 라이브러리와 언어가 같은 현상을 같은 방식으로 표현하게 하는 계약이다. 따라서 오늘의 추세는 “더 많은 자동 계측” 자체보다 계측을 어떤 데이터 계약으로 승인·변경·운영할 것인가에 있다.
이 글은 Prometheus와 OTel metric identity, Metric Cardinality Limit, OTTL Lambda의 변환 계약, 분산 트레이싱 도입 플레이북의 다음 단계다. 앞선 글이 metric 정체성·카디널리티·Collector 변환·trace 도입을 다뤘다면, 여기서는 데이터가 생성되는 첫 경계, 즉 instrumentation을 운영 단위로 다룬다.
이 글에서 얻는 것
- 자동 계측, 수동 계측, Collector 변환이 같은 문제가 아니라 서로 다른 책임 경계임을 구분합니다.
- coverage 수치 대신 span name·attribute·parentage·오류 의미·resource identity를 품질 기준으로 설계합니다.
- instrumentation upgrade를 dependency patch가 아닌 schema·비용·경보 회귀로 검증하는 방법을 배웁니다.
- 플랫폼 표준과 서비스 팀의 업무 맥락을 어디에서 나눠 소유할지 정할 수 있습니다.
핵심 개념/이슈
1) 계측 생태계에서 “자동"은 의미의 자동 보장이 아니다
OpenTelemetry의 API와 SDK는 application이 telemetry를 만들고 처리하는 공통 표면이다. OTLP는 데이터를 전달하는 protocol이며, Collector는 받아서 변환·샘플링·라우팅·export하는 pipeline이다. instrumentation은 HTTP client, ORM, Kafka consumer, web framework처럼 실제 코드가 움직이는 지점에서 이 표면을 호출해 trace·metric·log를 만든다. semantic convention은 이 데이터에서 http.request.method, database system, service name 같은 필드를 어떤 이름과 의미로 기록할지 정한다.
자동 계측은 framework hook을 이용해 많은 경계를 빠르게 포착한다. 그러나 업무 의미까지 알 수는 없다. POST /orders/{id}/cancel이 취소 요청인지, 멱등 재시도인지, 결제 이후 보상인지 알려면 서비스가 제공하는 domain context가 필요하다. 자동 계측이 request URL 전체를 attribute에 넣으면 observability는 늘어나지만 path parameter가 cardinality와 개인정보 문제를 만들 수도 있다.
| 계층 | 잘 맡는 일 | 혼자 결정하면 안 되는 일 |
|---|---|---|
| 자동 instrumentation | framework·driver 호출, 기본 trace parentage, 표준 protocol 속성 | 업무 성공/실패 의미, tenant·주문 같은 domain attribute |
| 수동 instrumentation | business event, 중요한 상태 전이, 사용자 정의 latency 구간 | vendor별 schema를 임의로 복제하는 일 |
| SDK | sampling, processor, resource, export lifecycle | 조직의 dashboard naming 정책 |
| Collector | 공통 redaction, routing, enrichment, 비용 제어 | 원본에서 이미 사라진 업무 사실 복구 |
| semantic convention | 공통 속성 이름과 의미 | 모든 조직에 맞는 SLO·보존 정책 |
핵심은 자동 계측을 배제하는 것이 아니다. 가장 가치 있는 시작점은 자동 계측으로 protocol 경계를 넓게 덮고, 수동 계측으로 업무 결정을 좁게 보강하며, Collector로 공통 정책을 일관되게 적용하는 조합이다. 다만 각 계층이 무엇을 책임지는지 섞지 않아야 incident에서 누락 원인을 찾을 수 있다.
2) coverage가 높아도 trace가 진단 가능하다는 뜻은 아니다
“서비스의 90%에 agent를 설치했다"는 coverage는 배포 진행률이지 telemetry 품질 지표가 아니다. on-call이 필요한 것은 checkout p99가 높은 이유와 영향 사용자를 추적할 수 있는 데이터다. 다음 다섯 가지가 빠지면 span 개수가 많아도 답을 얻기 어렵다.
- 안정된 이름:
GET /users/{id}처럼 템플릿화된 operation name이 있어야 동일 endpoint를 묶을 수 있다. raw URL은 cardinality와 PII를 키운다. - 올바른 parentage: inbound request, async queue, outbound call이 같은 trace 또는 명시적 link로 연결되어야 지연 전파를 복원할 수 있다.
- 오류 의미: timeout, caller cancel, validation failure, dependency 5xx를 모두 error로 합치면 재시도와 page 기준이 흐려진다.
- resource identity: service name, version, deployment environment, region이 일관돼야 새 배포와 특정 replica의 영향을 필터할 수 있다.
- 데이터 예산: attribute set과 sampling 규칙이 제한돼야 최고 부하와 incident 때도 exporter·backend가 붕괴하지 않는다.
이를 signal contract로 작게 표현할 수 있다.
signal: http.server.request
owner: checkout-platform
required:
resource: [service.name, service.version, deployment.environment]
attributes: [http.request.method, http.route, server.address]
semantics:
error: "5xx와 dependency failure; 4xx validation은 별도 business outcome"
privacy:
deny: [authorization, raw_url_query, customer_email]
budget:
route_cardinality: "1,000 active routes 이하"
trace_sample: "normal 5%, 5xx 100%, p99 초과 100%"
evidence:
fixtures: [success, validation, timeout, async_handoff]
모든 span에 긴 YAML을 붙이라는 뜻은 아니다. 핵심 경로 3~5개와 shared library부터 이 정도의 contract를 만들면, agent update나 framework migration이 어떤 dashboard를 깨뜨릴지 review에서 보인다.
3) 계측 upgrade는 코드 호환성과 telemetry 호환성이 함께 바뀐다
instrumentation library는 framework 버전, SDK, semantic convention과 함께 변한다. 어떤 update는 새 속성을 추가하고, 어떤 update는 이전 attribute를 deprecated 하거나 span name과 error 상태를 바꾼다. application test가 통과해도 alert query가 예전 label을 찾거나, Collector transform이 새 schema를 이해하지 못하거나, trace volume이 두 배가 될 수 있다.
따라서 다음 두 종류의 회귀를 분리한다.
| 회귀 | 예시 | 검증 방법 |
|---|---|---|
| 실행 회귀 | agent가 startup을 늦추거나 async context를 잃음 | startup, CPU/RSS, request p95/p99, error rate 비교 |
| 데이터 회귀 | http.route가 raw path가 되거나 parent span이 끊김 | golden trace fixture, required field, attribute distribution 비교 |
| 비용 회귀 | 새 span·attribute가 늘어 ingest bytes 급증 | signal별 spans/request, bytes/request, cardinality overflow 비교 |
| 운영 회귀 | alert·dashboard·runbook query가 빈 결과를 냄 | canary data source에서 대표 query와 alert simulation 실행 |
작은 서비스라도 canary를 먼저 적용할 가치가 있다. 예를 들어 production traffic의 5%에 새 instrumentation을 붙이고 24시간 동안 required-field missing rate 0.1% 미만, trace parent missing 0.5% 미만, spans/request와 ingest bytes가 baseline 대비 +20% 이내, p99·CPU가 예산 안인지를 비교한다. 수치는 고정된 산업 표준이 아니라 시작점이다. 인증 우회, raw token 노출, 핵심 trace 단절 같은 privacy·correctness 결함은 비율과 상관없이 즉시 중단 조건으로 둔다.
4) 표준화는 중앙집중식 속성 사전만 만드는 일이 아니다
플랫폼 팀이 모든 custom attribute를 승인하는 구조는 느리고, 팀마다 마음대로 이름을 짓게 두면 쿼리를 합칠 수 없다. 실무에서는 공통과 도메인을 나누는 방식이 더 잘 작동한다.
- 플랫폼이 소유할 것: resource naming, environment taxonomy, 표준 protocol convention, PII denylist, sampler/exporter 기본값, cardinality guardrail, version registry.
- 서비스가 소유할 것: 업무 상태 전이, business outcome, domain ID의 hash/allow 여부, SLO에서 실제로 필요한 dimension, 주석과 owner.
- 공동 review할 것: 새 high-cardinality attribute, tenant/user/customer 수준 data, Collector transform, alert를 만드는 metric, retention을 늘리는 signal.
이 분리는 애매한 책임을 없앤다. 예를 들어 플랫폼은 service.name을 강제할 수 있지만, payment_attempt.status가 authorized와 captured를 어떻게 구분해야 하는지는 결제 도메인 owner가 정해야 한다. Collector가 PII를 지우는 것은 최후의 방어선이지 source code에서 넣지 않아야 할 값을 넣어도 된다는 허가가 아니다.
실무 적용
1) inventory에서 시작하되 “설치됨"을 완료로 세지 않는다
첫 주에는 서비스마다 다음 다섯 열만 가진 inventory를 만든다: 계측 방식(auto/manual), SDK와 instrumentation version, 생성 signal, 핵심 consumer, owner. 그 다음 top 3 critical journey에 대해 실제 trace를 20개씩 뽑아 signal contract와 대조한다. 정상·validation·timeout·dependency failure·async handoff를 모두 넣어야 happy path coverage 착시를 피할 수 있다.
이때 installed와 validated를 분리한다. agent가 설치됐어도 service.version이 없거나, database span이 parent에서 끊겼거나, route가 raw URL이면 validated가 아니다. inventory의 완료율을 “agent 설치율"이 아니라 “핵심 journey의 required signal contract 통과율"로 보고하면, 보이는 span 수보다 운영 가능성에 가까운 우선순위를 만들 수 있다.
2) 새 계측은 shadow pipeline과 대표 query로 검증한다
가능하면 새 agent/라이브러리의 data를 기존 backend index와 분리된 shadow pipeline으로 내보낸다. 같은 request ID 또는 synthetic fixture로 old/new의 trace tree, required attributes, error classification, spans/request, bytes/request를 비교한다. production copy가 어려우면 staging traffic replay와 injected failure fixture를 함께 사용한다.
검증 결과는 단순한 screenshot이 아니라 query·버전·시간 창·표본 수를 남긴 evidence bundle로 기록한다. 예를 들어 “checkout timeout fixture 200건에서 parentage mismatch 0, http.route missing 0, raw query attribute 0, spans/request 중앙값 +8%, p99 +1.5%“처럼 남겨야 다음 upgrade에서 비교 기준이 된다. OTTL Lambda의 변환 계약처럼 source 계측과 Collector 규칙을 따로 바꾸지 않는 것도 원인 분리에 중요하다.
3) 계측 변경의 rollout gate를 서비스 release와 맞춘다
instrumentation은 운영 데이터의 schema를 바꾸므로 Friday 오후에 전 fleet을 업데이트하는 dependency housekeeping으로 끝내면 안 된다. 아래 순서가 현실적인 기본값이다.
- version pin과 release note에서 바뀌는 instrumentation·convention·known issue를 확인한다.
- staging fixture에서 signal contract와 privacy guardrail을 검사한다.
- production 1~5% canary에서 execution·data·cost·query 회귀를 함께 관찰한다.
- 25% → 100%로 늘리되, 각 단계에 최소 1회 traffic peak 또는 24시간 관찰 창을 둔다.
- 문제가 나면 code deploy를 되돌리는 것뿐 아니라 Collector rule, dashboard alias, backend index를 어떤 순서로 되돌릴지 실행한다.
특히 semantic convention의 rename은 새 field를 추가해 dual-read하는 기간을 둔 뒤 dashboard와 alert를 옮기고, 마지막에 옛 field를 제거하는 편이 안전하다. 한 배포에 instrumentation upgrade, Collector transform, alert 재작성, retention 변경을 묶으면 빠르게 끝나는 대신 관측 데이터가 달라졌을 때 원인을 알 수 없다.
트레이드오프/주의점
- 자동 계측의 넓은 coverage는 CPU·메모리·ingest 비용을 동반한다. 핵심 endpoint부터 시작하고 signal당 bytes/request와 cardinality를 같이 본다.
- 수동 span은 업무 맥락을 주지만 과도하면 코드가 관측성 vendor에 묶일 수 있다. 표준 API와 작은 domain event 경계에 집중한다.
- 공통 convention은 비교를 돕지만 모든 팀의 의미를 대체하지 않는다. protocol 표준과 business taxonomy를 같은 속성 사전에 억지로 넣지 않는다.
- Collector 변환은 빠른 보정 수단이지 영구적 schema adapter가 아니다. source 수정 owner와 제거 날짜가 없는 transform은 데이터 부채가 된다.
- 완전한 trace는 목표가 아니라 위험 기반 선택이다. low-value health check까지 100% sampling하기보다 5xx, p99 초과, 결제·인증 같은 실패 비용 높은 경계의 완전성을 우선한다.
체크리스트 또는 연습
계측 도입·업데이트 체크리스트
- 핵심 서비스·journey별 instrumentation 방식, version, signal, consumer, owner가 inventory에 있다.
- 각 핵심 journey는 stable name, required resource/attribute, parentage, error semantics, PII denylist, data budget을 가진다.
- success뿐 아니라 timeout·cancel·dependency failure·async handoff fixture를 검증했다.
- 새 instrumentation은 canary에서 trace completeness, required field, cardinality, spans/bytes per request, CPU/RSS, p99을 이전 버전과 비교했다.
- dashboard·alert·Collector rule의 schema migration과 rollback owner가 계측 배포에 포함되어 있다.
- custom high-cardinality 또는 customer-linked attribute는 서비스 owner와 플랫폼의 공동 승인을 거쳤다.
연습: ORM 자동 계측을 승인해 보기
한 주문 서비스에 ORM 자동 계측을 넣는다고 가정하자. 먼저 db.system, operation name, error status, trace parentage 중 반드시 있어야 할 필드를 정한다. 다음으로 full SQL, bind value, customer ID가 왜 denylist인지 적는다. 정상 주문·DB timeout·connection pool 대기 세 fixture에서 old/new trace를 비교하고, spans/request +25%, ingest bytes +40%, route cardinality 2배 중 어느 조건에서 rollout을 멈출지 결정한다. 마지막으로 application 팀과 platform 팀 중 누가 metric query와 새 attribute의 유지 책임을 지는지 명시해 본다.
마무리
계측 생태계가 커질수록 자동 instrumentation은 편의 기능을 넘어 운영 데이터의 공급망이 된다. 좋은 기준은 agent가 몇 개 설치됐는지가 아니라, 서로 다른 언어와 라이브러리에서 나온 signal이 같은 의미·비용·보안·소유권 계약을 지키는가다. 이 계약을 fixture, canary, version registry, rollback evidence로 운영할 때 자동 계측의 속도가 실제 진단력으로 이어진다.
💬 댓글