OpenTelemetry(OTEL) 메트릭, 혹시 Counter.add(1)만 호출하면 마법처럼 데이터가 쌓인다고 생각하셨나요? 🤔
사실 우리가 대시보드에서 보는 하나의 숫자 뒤에는, 보이지 않는 4개의 핵심 단계가 숨어있습니다.
이 전체 흐름을 이해하지 못하면, "왜 이 메트릭이 이렇게 집계됐지?" 혹은 "데이터가 왜 분리되어 보이지?" 와 같은 문제에 부딪혔을 때 해결의 실마리를 찾기 어렵습니다.
오늘은 개발자가 코드를 작성하는 첫 순간부터 데이터가 최종 저장소에 기록되기까지, OpenTelemetry 메트릭 데이터가 여행하는 4가지 단계를 낱낱이 파헤쳐 보겠습니다. 이 글을 끝까지 읽으시면 OTEL 메트릭의 전문가로 거듭나실 수 있을 겁니다! ✨

1단계: 어떤 도구로 측정할까? - Instrument Model 🛠️
모든 측정의 시작은 '어떤 도구를 사용할 것인가'를 정하는 것에서부터 출발합니다. Instrument Model은 바로 이 데이터를 생성하는 도구(API) 그 자체를 의미합니다.
단순히 증가만 하는 값인지, 아니면 오르내리는 값인지 등 데이터의 성격에 따라 적절한 도구를 선택해야 합니다.
- 설명: 6가지 핵심 계측기(Counter, UpDownCounter, Histogram 등)의 API 명세이자, 데이터 수집의 가장 첫 번째 관문입니다. 개발자는 이 모델을 통해 '어떤 종류의' 메트릭을 만들지 정의합니다.
- 핵심 질문:
- "이 데이터는 오직 증가만 하는가?" ➡️ Counter를 선택!
- "현재의 상태 값(예: CPU 사용량)을 나타내는가?" ➡️ Observable Gauge를 선택!
이 단계는 아직 실제 데이터가 발생하기 전, 설계도를 그리는 단계와 같습니다. 실제 측정값인 '이벤트 모델'이나, 집계된 데이터 파이프라인인 '메트릭 스트림'과는 명확히 구분됩니다. 단순히 어떤 도구를 쓸지 '선언'하는 단계일 뿐입니다.
// 예시: Go 언어로 "http.requests" 라는 이름의 Counter '도구'를 생성
meter := provider.GetMeter("my-app")
httpRequestsCounter, _ := meter.Int64Counter("http.requests")
2단계: 데이터의 탄생, 최초의 측정값 - Event Model 👶
도구를 선택했다면 이제 실제로 측정을 할 차례입니다. Event Model은 집계(Aggregation)되기 전의 원시 측정(Raw Measurement) 이벤트 하나하나를 의미합니다.
- 설명: OTEL 메트릭 모델에 입력되는 가장 순수하고 기본적인 데이터입니다. 우리가 코드에서 API를 호출하는 바로 그 순간에 발생합니다.
- 예시:
- httpRequestsCounter.Add(ctx, 1)를 호출하는 바로 그 행위
- histogram.Record(ctx, 52.4)로 응답 시간을 기록하는 단일 측정값
이것은 아직 아무런 가공도 거치지 않은 '날 것'의 데이터입니다. 특정 시간, 특정 속성으로 값이 '한 번' 측정되었다는 사실 그 자체를 나타냅니다. 이 이벤트들이 모여서 다음 단계인 '메트릭 스트림'을 형성하게 됩니다.
3단계: 데이터의 고속도로를 만들다 - Metric Stream 🛣️
수많은 원시 측정값(Event)이 발생하면, OTEL SDK는 이들을 무작정 섞지 않습니다. 고유한 특성을 가진 데이터끼리 묶어 별도의 '논리적 파이프라인'을 만드는데, 이것이 바로 Metric Stream입니다. OTEL 내부에서 데이터를 식별하고 처리하는 가장 중요한 핵심 단위이죠.
- 설명: 리소스(Resource) + 계측기(Instrument) + 속성(Attributes)의 고유한 조합으로 식별되는 논리적 데이터 흐름입니다. SDK는 이 스트림 단위로 데이터를 집계하고 처리합니다.
- 핵심: 속성(Attributes)이 다르면, 같은 계측기라도 완전히 다른 스트림으로 취급됩니다.
이 개념이 왜 중요할까요? 아래 예시를 봅시다.
// 같은 http.requests Counter를 사용하지만...
// Instrument: "http.requests"
// Event 1
httpRequestsCounter.Add(ctx, 1, attribute.String("method", "GET"), attribute.Int("status", 200))
// Event 2
httpRequestsCounter.Add(ctx, 1, attribute.String("method", "POST"), attribute.Int("status", 500))
// Event 3
httpRequestsCounter.Add(ctx, 1, attribute.String("method", "GET"), attribute.Int("status", 200))
위 코드에서 Event 1과 Event 3은 {method="GET", status="200"}이라는 동일한 속성을 가집니다. 따라서 이 둘은 하나의 메트릭 스트림에 속하게 됩니다.
반면, Event 2는 {method="POST", status="500"}이라는 다른 속성을 가지므로, 완전히 별개의 메트릭 스트림으로 처리됩니다.
- 스트림 1: http.requests + {method="GET", status="200"}
- 스트림 2: http.requests + {method="POST", status="500"}
이처럼 메트릭 스트림은 데이터를 의미 있는 단위로 분류하여 집계하기 위한 중간 파이프라인이며, 최종 산출물인 '시계열 모델'과는 다릅니다. 시계열은 이 파이프라인을 거쳐 나온 '결과물'입니다.
4단계: 최종 보고서, 백엔드로의 전송 - Timeseries Model 📊
모든 집계와 처리가 끝나면, 드디어 우리가 Prometheus나 Datadog 같은 백엔드 시스템에서 볼 수 있는 최종 데이터 형식이 만들어집니다. 이것이 바로 Timeseries Model입니다.
- 설명: OTEL SDK가 메트릭 스트림별로 집계, 시간성(Temporality) 처리 등을 모두 마친 후, 외부로 내보내는 최종 산출물입니다.
- 구성: (메트릭 이름 + 속성 + 데이터 포인트[시간+값])의 조합으로 이루어집니다.
위의 메트릭 스트림 예시가 최종적으로 어떤 시계열 데이터가 되는지 살펴볼까요?
Raw Data (Timeseries Payload)
{
"resourceMetrics": [
{
"scopeMetrics": [
{
"metrics": [
{
"name": "http.requests",
"sum": {
"dataPoints": [
{
"attributes": [
{ "key": "method", "value": { "stringValue": "GET" } },
{ "key": "status", "value": { "intValue": "200" } }
],
"startTimeUnixNano": "1762489800000000000",
"timeUnixNano": "1762489860000000000",
"asInt": "2" // Event 1과 3이 합산된 결과
},
{
"attributes": [
{ "key": "method", "value": { "stringValue": "POST" } },
{ "key": "status", "value": { "intValue": "500" } }
],
"startTimeUnixNano": "1762489800000000000",
"timeUnixNano": "1762489860000000000",
"asInt": "1" // Event 2의 결과
}
],
"aggregationTemporality": "CUMULATIVE",
"isMonotonic": true
}
}
]
}
]
}
]
}
Prometheus 같은 시스템에서는 이 데이터가 다음과 같은 형식으로 변환되어 표시됩니다.
http_requests_total{method="GET", status="200"} 2
http_requests_total{method="POST", status="500"} 1
이것이 바로 우리가 대시보드에서 마주하는 데이터의 최종 형태입니다!
🗺️ 한눈에 보는 전체 흐름
자, 그럼 이 모든 과정을 처음부터 끝까지 정리해볼까요?
- Instrument Model (도구 선택 🛠️): 개발자가 "요청 수를 셀 거야!"라며 Counter라는 도구를 선택합니다.
- Event Model (원시 측정값 발생 👶): 사용자의 요청이 들어올 때마다 counter.add(1)이 호출되며, 속성({method="GET"})과 함께 원시 측정값이 발생합니다.
- Metric Stream (논리적 파이프라인 처리 🛣️): SDK가 이 측정값을 속성별로 구분합니다. {method="GET"} 요청은 GET 스트림으로, {method="POST"} 요청은 POST 스트림으로 보내 각각의 논리적 파이프라인에서 집계(Sum)를 시작합니다.
- Timeseries Model (최종 산출물 전송 📊): 일정 시간 동안 집계된 결과가 http_requests_total{method="GET"} = 120과 같은 시계열 데이터(최종 산출물)가 되어 모니터링 백엔드로 전송됩니다.
이제 OpenTelemetry 메트릭이 어떻게 동작하는지 완벽하게 이해되셨나요? 이 4단계의 흐름을 기억하신다면, 앞으로 어떤 메트릭 관련 문제를 마주하더라도 자신 있게 분석하고 해결하실 수 있을 겁니다!
'클라우드 > opentelemetry' 카테고리의 다른 글
| 🤯 OTel Metrics, 아직도 헷갈리시나요? MeterProvider, Meter, Instrument 완벽 정리! (0) | 2025.11.10 |
|---|---|
| OpenTelemetry Collector, 아직도 run만 쓰세요? 고수들이 쓰는 5가지 필수 명령어 🚀 (1) | 2025.11.10 |
| 개발자 필독! 🤯 MSA 지옥에서 날 구해준 OpenTelemetry 완벽 가이드 (0) | 2025.11.09 |
| 당신의 서버 로그, 개인정보 유출의 시한폭탄일 수 있습니다 💣 | OpenTelemetry 민감정보 안전하게 처리하는 법 (0) | 2025.11.08 |
| 🤯 "서버 터졌대!" 하이브리드 환경, 모니터링 때문에 머리 아프셨죠? 정답 알려드립니다 (0) | 2025.11.06 |