네 Signal이 같은 성숙도에 있지는 않습니다

장애가 났다고 무조건 로그 검색창부터 열 이유는 없습니다. 서비스 전체에서 오류가 얼마나 퍼졌는지 모른다면 메트릭이 먼저입니다. 특정 요청이 여러 서비스를 지나며 실패했다면 트레이스가 더 빠릅니다. 예외가 발생할 때 애플리케이션이 어떤 상태였는지 봐야 한다면 로그가 필요하고, CPU나 메모리를 실제로 소비한 함수를 찾으려면 프로파일이 답에 가깝습니다.

따라서 첫 번째로 볼 Signal은 "우리 도구가 무엇을 잘 보여주는가"가 아니라 "지금 증상이 어떤 형태의 실패인가"로 정합니다. 이 글은 무엇을 사용자 실패로 측정할지 다시 정의하지 않습니다. 그 문제는 사용자 여정과 SLI 설계에서 먼저 결정합니다. 여기서는 실패가 감지된 다음, 로그·메트릭·트레이스·프로파일 중 어디에서 조사를 시작하고 어느 조건에서 다음 Signal로 넘어갈지를 다룹니다.

네 Signal이 지금 같은 성숙도에 있지는 않습니다. OpenTelemetry에서 Traces는 API·SDK·OTLP가 모두 Stable입니다. Metrics는 Signal과 API·데이터 모델·OTLP가 Stable이지만 전체 SDK와 언어별 구현 상태가 섞여 있습니다. Logs도 Signal·Bridge API·SDK·OTLP는 Stable이고 언어별 Appender와 구현 범위는 다릅니다.[1]

Profiles는 다릅니다. 2026년 3월 Public Alpha에 들어갔고 Collector·eBPF Profiling Agent 연계는 진전됐지만 Concepts 문서는 여전히 Alpha로 표시하고 OTLP Profiles는 Development 상태입니다. OpenTelemetry의 Alpha 경고에 따르면 Critical Production Workload의 필수 의존성으로 전제해서는 안 됩니다.[2] 프로파일링 기법 자체가 새롭다는 뜻은 아니며 규격과 생태계의 상호운용 지원이 아직 성숙 과정에 있다는 뜻입니다.

이 차이는 Signal 선택에 영향을 줍니다. 프로파일이 가장 잘 답하는 질문이 있더라도 현재 Runtime과 Backend가 필요한 Profile Type과 Correlation을 지원하지 않는다면, 기존 언어 Profiler나 다른 진단 수단을 함께 봐야 합니다.

각 Signal이 잘 답하는 질문은 서로 겹치지 않습니다

OpenTelemetry도 네 Signal의 질문을 비슷하게 나눕니다. Metrics는 시스템 수준의 상태, Logs는 개별 사건, Traces는 분산 요청 경로, Profiles는 자원을 소비한 코드를 보여 줍니다.[1] Metrics는 많은 측정값을 시간 구간별 통계로 집계합니다. Traces는 개별 요청의 생명주기와 Context를 잇습니다.

아래 표의 오른쪽 열이 특히 중요합니다. 어떤 Signal이 답하지 못하는 질문을 그 Signal로 계속 파고들면 조사 시간이 길어지고, 우연한 상관관계를 원인으로 오인하기 쉬워집니다.

Signal
잘 답하는 질문
잘 답하지 못하는 질문
Metrics언제부터 이상했는가. 얼마나 많은 요청·인스턴스·사용자가 영향을 받는가. 오류율·지연 분포·Queue Depth·CPU·메모리가 어떻게 변했는가특정 요청이 왜 실패했는가. 어느 함수가 CPU를 소비했는가. 실패 당시 정확한 업무 상태는 무엇이었는가
Logs어떤 사건이 발생했는가. 어떤 예외·입력 분류·상태 전이·재시도 결과가 기록됐는가문제가 전체의 몇 퍼센트인가. 여러 서비스의 인과 경로는 무엇인가. 기록되지 않은 사건이 일어나지 않았다는 증명이 되는가
Traces한 요청이나 작업이 어떤 서비스·DB·외부 API를 거쳤는가. 어느 Span에서 실패하거나 시간이 늘어났는가. 비동기 작업과 원 요청은 어떻게 연결되는가어떤 함수가 CPU·Heap을 소비했는가. 문제가 전체 트래픽에서 얼마나 흔한가. 계측되지 않은 내부 상태에서 무슨 일이 있었는가
Profiles어떤 함수와 호출 스택이 CPU, Heap, Allocation, Lock·I/O 대기를 소비했는가사용자 영향이 얼마나 넓은가. 요청이 어떤 외부 서비스를 지나갔는가. 현재 환경이 필요한 Profile Type을 실제로 지원하는가
네 Signal이 잘 답하는 질문과 잘 답하지 못하는 질문 — OpenTelemetry 규격의 Signal 구분을 장애 조사 질문으로 옮긴 편집 판단 (2026-08)
서비스 작동 방식

관측에서 복구와 개선까지 이어지는 운영서비스 지표와 경보를 기준으로 대응하고 변경 이력과 사후 보고를 다음 개선에 연결합니다.

실패 유형이 첫 Signal을 정합니다

아래 표의 First Signal은 모든 환경에서 고정된 정답이 아닙니다. 현재 알고 있는 증상만으로 조사 범위를 가장 빨리 줄이는 기본 출발점입니다.

이 매트릭스를 Metrics → Logs → Traces → Profiles 같은 단일 계단으로 바꾸면 다시 문제가 생깁니다. 특정 사용자 요청 한 건이 실패했다면 전체 Metrics부터 훑는 것보다 해당 Trace가 빠릅니다. 반대로 "오늘 아침부터 서비스가 대체로 느리다"는 제보만 있다면 임의의 Trace 한 건을 여는 것보다 지연 분포와 영향 Service를 먼저 보는 편이 낫습니다.

같은 User-visible Error라도 하나의 사건인지 집단적 변화인지에 따라 첫 Signal이 갈립니다. "결제 요청 ABC가 실패했다"처럼 구체적인 요청 식별자가 있다면 Trace에서 어느 Span이 최초 오류를 냈는지 보는 편이 빠릅니다. 실패 Span을 찾은 뒤 같은 trace_id와 span_id를 가진 로그로 이동하면 당시 예외와 업무 상태가 드러납니다. 반면 "결제 오류가 평소보다 많다"는 증상은 집계 문제입니다. 먼저 Metrics에서 오류율이 언제부터 올랐는지, 특정 Route·지역·배포 버전·Instance에 몰렸는지 범위를 좁힌 뒤 대표적인 실패 요청을 Trace로 고릅니다.

실패 유형
지금 보이는 증상
First Signal
Next Signal
전환 조건과 주의점
User-visible Error특정 요청·사용자 여정이 실패TraceLogs → Metrics요청 ID가 없고 오류율 급등만 보이면 Metrics로 범위부터 잡는다
Latency서비스 전반 또는 일부 요청이 느림MetricsTraces → Profiles 또는 Logs느린 요청의 Trace ID가 이미 있다면 Trace부터 시작한다. 한 Process 내부 시간이 길면 Profile로 이동한다
Resource SaturationCPU·메모리·Thread·Connection·Queue가 상한에 접근MetricsProfiles → Logs 또는 Traces자원 소비 코드가 필요하면 Profile, 포화의 결과로 실패한 요청은 Trace·Logs로 확인한다
Intermittent Failure일부 요청만 불규칙하게 실패Trace 또는 구조화된 LogsLogs ↔ Traces → Metrics실패 요청이 Trace에 남지 않았다면 Correlation ID가 있는 Logs에서 시작한다
External Dependency외부 API·DB·Queue·DNS 등 원격 의존성 의심TraceDependency Metrics → LogsTrace가 원격 경계를 보여주지 못한다면 Client 계측과 Context 전파부터 점검한다
Distributed Request Failure여러 서비스에 걸친 한 요청이 중간에서 실패Trace실패 Span의 Logs → Metrics마지막 오류를 낸 서비스와 최초 원인 서비스를 구분한다
Code Hotspot특정 코드가 느리거나 CPU 비용이 크다고 의심ProfileTrace → Metrics사용자가 겪는 경로와 이으려면 Trace, 전체 영향 크기를 보려면 Metrics가 필요하다
Memory / CPU anomalyCPU 급등, Memory 증가, OOM, GC 압박MetricsProfiles → Logs → TracesProfile Type 지원을 확인한다. OOM·Restart·GC 사건은 Logs, 영향 요청은 Traces로 잇는다
Background JobQueue 적체, 처리 지연, 중복·누락·실패 작업MetricsTraces / Span Links → LogsJob ID가 주어졌다면 Logs나 Trace부터 시작해도 된다. Producer와 Consumer Context를 잇지 않으면 원 요청을 찾기 어렵다
Unknown symptom"서비스가 이상하다" 외에 단서가 거의 없음MetricsTrace 또는 Logs → 필요 시 ProfilesMetrics는 원인을 확정하는 도구가 아니라 다음 질문을 고르는 지도다
실패 유형 → First Signal → Next Signal — 현재 아는 증상만으로 조사 범위를 가장 빨리 줄이는 출발점 (2026-08)

지연과 자원 포화는 범위·경로·코드 순서로 좁힙니다

서비스가 전반적으로 느려졌다면 Metrics가 네 가지를 갈라 줍니다. 전체 요청이 느린지 특정 Route만 느린지, 평균만 변했는지 상위 지연 구간이 두꺼워졌는지, 특정 Service·Instance·배포 버전에 몰렸는지, CPU·메모리·Queue·Connection 같은 자원 변화와 같은 시점인지입니다.

범위를 좁힌 뒤에는 Trace에서 느린 요청의 시간이 어느 Span에 머물렀는지 봅니다. 외부 API 호출이 길었다면 Dependency 문제로 전환하고, DB Span이 길다면 Query와 Connection 상태를 봅니다. 긴 시간이 한 Process 내부 Span에 남아 있다면 그때 Profile이 유용해집니다. On-CPU Profile은 CPU를 소비한 함수, Off-CPU Profile은 Lock이나 I/O를 기다린 위치, Heap·Allocation Profile은 메모리를 점유하거나 반복 할당한 코드 경로를 찾는 데 씁니다. 지원되는 Profile Type은 Runtime과 Profiler마다 다릅니다.[2]

Trace는 "어느 구간이 느렸는가"를 보여 주지만 그 구간 안의 어떤 함수가 CPU를 썼는지까지 자동으로 답하지는 않습니다. Profile은 그 반대입니다. 함수 수준의 소비는 보여 주지만 그 코드가 전체 사용자 요청에서 얼마나 큰 문제였는지는 Metrics와 Trace 없이 판단하기 어렵습니다.

자원 포화도 마찬가지입니다. CPU 95%, Connection Pool 100%, Queue Depth 급등은 중요한 단서지만 그 자체가 최초 원인을 증명하지는 않습니다. 느려진 외부 호출을 기다리는 동안 Worker와 Connection이 묶였을 수도 있고, 재시도 폭증이 CPU와 Queue를 함께 밀어 올렸을 수도 있습니다. 그래서 Resource Saturation에서는 Metrics로 먼저 포화된 자원과 시점을 찾고, 다음 Signal은 질문에 따라 갈립니다. 누가 CPU와 메모리를 소비했는지는 Profiles, 어떤 요청이 자원을 오래 점유했는지는 Traces, OOM·Restart·GC·Connection Timeout이 실제로 발생했는지는 Logs, 영향이 전체 요청 중 얼마나 되는지는 Metrics가 답합니다. 자원 차트 하나를 보고 인스턴스 증설부터 하는 것보다, 포화가 원인인지 결과인지 구분해야 같은 장애가 되풀이되지 않습니다.

간헐 실패와 분산 요청은 한 건을 보존해야 풀립니다

간헐 장애는 전체 Metrics에서 희석되기 쉽습니다. 특정 사용자, 특정 Tenant, 특정 입력 조합, 일부 Instance에서만 실패한다면 정상 요청과 실패 요청을 비교할 식별자가 필요합니다. 실패한 요청이 Trace로 남아 있다면 경로와 Span Attribute를 정상 요청과 비교합니다. Trace가 없다면 구조화된 Logs에서 요청 ID, Job ID, 배포 버전, Instance, 결과 코드 같은 안정된 Field로 사건을 찾습니다. 여기서 구조화된 로그는 JSON으로 출력한 로그가 아니라 Field 이름·자료형·의미가 일관된 로그를 뜻합니다.[1] 한 건의 차이를 찾은 뒤에야 Metrics로 돌아가 같은 조건의 발생률을 계산합니다. 원인을 찾았다고 생각했지만 실제로는 극소수 요청의 우연한 특성만 본 것인지 확인하기 위해서입니다.

분산 시스템에서 최종 오류를 기록한 서비스가 최초 원인인 것은 아닙니다. 상류 서비스가 하류 API 응답을 기다리다 Timeout을 냈거나, 하류 서비스의 재시도가 Queue와 Connection을 고갈시킨 결과일 수도 있습니다. Trace는 요청이 서비스·DB·외부 API를 통과한 경로와 각 Span의 시간을 잇습니다. OpenTelemetry는 W3C Trace Context의 traceparent를 기본 전파 방식으로 쓰며 Trace ID와 Parent Span 정보를 서비스 경계 너머로 전달합니다.[3] 비동기 작업은 Span Link나 Producer·Consumer Span으로 원 요청과 후속 처리를 인과적으로 잇습니다. 다만 외부 서비스에 내부 Context를 무조건 전달해서는 안 됩니다. 신뢰하지 않는 외부 경계에서는 들어오는 Trace Header를 검증하거나 정리하고, 나가는 Context가 내부 구조를 불필요하게 드러내지 않는지 봅니다. Baggage에는 사용자 자격정보, API Key, 개인정보를 넣지 않습니다.

백그라운드 작업은 HTTP 응답이 끝난 뒤 시작되거나, Producer가 Job을 만든 지 오래 지난 후 Consumer가 처리하기도 합니다. 그래서 단일 요청의 Parent-Child 관계만으로는 부족합니다. Queue 전체가 밀리는 증상이라면 먼저 Metrics를 봅니다. Enqueue·Completion·Failure Rate, Queue Depth, Oldest Job Age를 확인합니다. Retry·Dead-letter 증가, Consumer 수와 처리량, 특정 Job Type·Tenant·버전 편중을 봅니다. 특정 Job 한 건을 추적할 때는 Producer Span과 Consumer Span, Span Link, Job ID가 필요합니다. Logs에는 상태 전이와 재시도 사유를 남기되 Trace와 이을 Context가 있어야 합니다. OpenTelemetry는 Producer Span을 비동기 Job 생성, Consumer Span을 이후의 실제 처리로 구분합니다.[1]

Signal을 모으는 것보다 사이를 이동하는 능력이 중요합니다

네 Signal이 모두 있어도 서로 다른 이름과 식별자를 쓰면 장애 중에는 사실상 네 개의 고립된 데이터 저장소가 됩니다. 필요한 것은 하나의 화면보다 Correlation Contract입니다.

OpenTelemetry의 모든 Signal은 Resource와 이어질 수 있으며, 같은 Resource Identity는 동일 Service·Process·Instance에서 나온 Signal을 오가는 기준점이 됩니다. Metrics와 Traces는 Exemplar로 집계값의 대표 측정과 Trace ID·Span ID를 잇습니다. Logs는 활성 Trace Context를 Log Record에 주입합니다.[1] Profiles도 Request Context와 Resource Context 양쪽의 연결을 지향하지만 현재 Alpha이므로 실제 End-to-end 지원은 조합별로 확인해야 합니다.[2]

Metric Label에 매 요청의 Trace ID를 넣는 방식으로 Correlation을 구현해서는 안 됩니다. Metrics는 고유 Attribute 조합마다 별도 집계 상태를 유지하기 때문에 User ID, Raw URL, Trace ID처럼 값의 종류가 계속 늘어나는 Attribute는 Cardinality와 메모리 비용을 크게 키웁니다. 개별 Trace와 이을 때는 Metric Dimension을 늘리는 대신 Exemplar 같은 연결 구조를 씁니다.

모든 Signal을 항상 수집해야 하는 것도 아닙니다. 관측 가능성의 목표는 네 종류의 데이터를 빠짐없이 사거나 저장하는 것이 아니라, 반복해서 답해야 하는 운영 질문을 합리적인 비용과 위험으로 푸는 것입니다. 단일 Process에서 실행되고 외부 호출이 적은 단순한 작업은 기본 Metrics와 구조화된 Logs만으로 충분합니다. 반대로 여러 서비스와 Queue를 통과하는 중요한 사용자 여정은 Traces가 없으면 원인 Service를 찾는 데 시간이 오래 걸립니다. CPU·메모리 비용이 핵심인 처리 서비스는 Profiles의 가치가 크지만 지원 상태와 수집 Overhead, 데이터 보존, Symbolization, Backend 호환성을 먼저 검증해야 합니다. 새 Signal의 도입 여부는 여섯 질문으로 정합니다. 어떤 장애 질문을 현재 데이터로 답하지 못하는가. 그 질문은 얼마나 자주 발생하며 사용자 피해가 얼마나 큰가. 새 Signal이 실제로 조사 범위를 줄이는가. 기존 Signal과 이을 Context를 만들 수 있는가. 수집량·Cardinality·보존 비용·개인정보 위험을 통제하는가. 언어·Runtime·Agent·Collector·Backend가 필요한 기능을 실제로 지원하는가입니다. 네 번째 Signal을 추가하는 것보다 기존 두 Signal 사이에 trace_id, Resource, Deployment Version을 일관되게 심는 일이 먼저인 경우가 많습니다.

이동 경로
필요한 Context
무엇이 가능해지는가
Metrics → TracesExemplar, 공통 Resource, 시간 구간지연·오류 Histogram의 대표 측정값에서 실제 요청 Trace로 이동
Traces → Logstrace_id, span_id, 공통 Timestamp실패 Span에서 같은 실행 Context의 예외·상태 로그로 이동
Signal 사이의 Resource 이동service.name, service.instance.id, 환경·버전·Region같은 Service·Instance·Deployment가 만든 다른 Signal 조회
Traces → ProfilesTrace·Span Context 또는 동일 Resource·시간 구간느린 Span과 당시 실행된 코드 스택 연결
Producer → Background JobSpan Link, Job·Message ID, Producer·Consumer Context원 요청과 나중에 실행된 비동기 작업 연결
Signal 사이를 이동할 때 필요한 Context — OpenTelemetry Resource·Exemplar·Trace Context 규격 대조 (2026-08)

장애 중에는 다음 질문이 막힐 때 Signal을 바꿉니다

실전에서는 모든 탭을 순서대로 여는 대신, 현재 Signal이 다음 질문에 더 이상 답하지 못할 때 이동합니다. 순서는 다섯 단계입니다.

먼저 증상 형태를 분류합니다. 한 요청인가, 집단적 변화인가, 자원 이상인가, 코드 비용인가. 다음으로 First Signal에서 시간과 범위를 줄입니다. Service, Route, Instance, Job Type, Dependency, Deployment Version까지 좁힙니다. 그다음 구체적인 식별자를 확보합니다. Trace ID, Span ID, Job ID, Resource Identity, 대표 Exemplar를 찾습니다. 그리고 다음 질문에 맞는 Signal로 이동합니다. 사건의 세부 상태는 Logs, 요청 경로는 Traces, 코드 자원 소비는 Profiles, 발생률과 영향 범위는 Metrics로 확인합니다. 마지막으로 완화 행동에 필요한 증거를 남깁니다. 원인 추정만 적지 말고 어떤 Signal과 식별자로 확인했는지, 영향을 받은 범위와 배포 버전은 무엇인지 적습니다.

장애 분석 속도를 정하는 것은 Signal의 개수가 아닙니다. 메트릭은 범위를 보여 주고, 트레이스는 경로를 잇고, 로그는 사건을 설명하고, 프로파일은 코드의 자원 소비를 드러냅니다. 하지만 이 순서는 고정된 계단이 아닙니다. 첫 Signal은 언제나 지금 보고 있는 실패의 모양에서 시작합니다.