1. 소개
서비스 지향 아키텍처(SOA), 마이크로서비스, 클라우드 솔루션이 보편화된 현대에서는 하나의 사용자 요청이 결과를 반환하기 전까지 수십 개의 서로 다른 서비스들을 거칠 수 있어요. 이 경로의 각 노드는 자신의 로그를 남기는데, 특정 지점의 오류나 지연이 이 요청과 실제로 연관되어 있는지 어떻게 알 수 있을까요?
분산 추적은 물류에서의 배송 추적 시스템과 같아요: 패키지가 어떤 지점을 거쳤고, 어디서 지연됐고, 어디서 문제가 생겼는지 볼 수 있죠. 프로그래밍 세계에선 하나의 요청이 여러 서비스들을 거치는 "여정"을 추적하고, 각 구간에 얼마의 시간이 소비됐는지, 어디서 오류나 지연이 발생했는지 파악할 수 있게 해줍니다.
왜 추적이 필요한가?
- 오류 로컬라이제이션: 문제가 생기면 어느 서비스의 어느 단계에서, 심지어 어떤 메서드가 호출되었는지 바로 알 수 있어요.
- 병목 탐지: 추적을 통해 서비스들이 어디에서 "느려지는지"와 어떤 단계에서 시간이 가장 많이 소모되는지 알 수 있습니다.
- 사용자 시나리오 분석: 실제로 클라이언트가 서비스를 어떻게 사용하는지 이해하고, 최적화가 필요한 부분에 우선 투자할 수 있어요.
어떻게 동작하나?
각 요청은 고유한 트레이스 식별자(TraceId)를 부여받아 프론트엔드에서 백엔드, API에서 DB까지 스택 전반을 따라 "이동"합니다. 각 단계에서 해당 작업을 기록하는 "스팬"(span)이 생성되어 실행 시간과 추가 이벤트를 기록합니다. 결국 연산들의 트리(그래프)와 각 연산의 지속시간, 연관 관계를 얻게 됩니다.
분산 추적의 주요 개념과 용어
| 용어 | 설명 |
|---|---|
| 트레이스 (Trace) | 하나의 요청이 여러 서비스에 걸쳐 이동한 논리적 경로. TraceId로 고유하게 식별됩니다. |
| 스팬 (Span) | 트레이스 내의 연산 또는 하위 연산. 각 스팬은 고유한 식별자를 가집니다. |
| TraceId | 전체 트레이스(요청)의 고유 식별자. |
| SpanId | 현재 스팬(연산)의 고유 식별자. |
| Parent SpanId | 부모 스팬의 Id (해당 스팬이 다른 연산의 일부일 경우). |
| Attributes | 스팬에 붙는 사용자 메타데이터 집합: 메서드 이름, 파라미터, 상태 등. |
| Events/Logs | 스팬과 관련된 중요한 이벤트들(예: 오류, 완료 등). |
| Context Propagation | 서비스와 스레드 간에 트레이스 정보를 전달하는 것. |
2. OpenTelemetry: 관찰성을 위한 오픈 표준
OpenTelemetry에 대해 간단히
OpenTelemetry는 다양한 애플리케이션에서 텔레메트리(추적, 메트릭, 로그)를 수집하기 위한 오픈 표준이자 도구 모음입니다. Cloud Native Computing Foundation(CNCF)이 지원하며, 클라우드 및 분산 애플리케이션 분야에서 사실상 표준이 되었어요.
OpenTelemetry(줄여서 OTel)는 다음을 할 수 있습니다:
- 추적, 메트릭, 로그를 자동으로 수집하고 전송;
- C#, Java, Python 등 주요 프로그래밍 언어 지원;
- 데이터를 Jaeger, Zipkin, Azure Monitor, Grafana 등 관찰 시스템으로 전송;
- 플러그인으로 확장되고 어떤 스택에도 맞게 구성 가능.
왜 OpenTelemetry인가?
- 벤더 독립성: OTel은 특정 회사 제품이 아닌 표준입니다.
- 호환성: 주요 트레이싱 포맷을 지원하고 APM 시스템과 쉽게 통합됩니다.
- 자동화: HTTP 요청 같은 일반적인 작업의 추적을 최소한의 설정으로 자동으로 처리할 수 있어요.
- 확장성: 작은 시스템부터 아주 큰 시스템까지 잘 동작합니다.
OpenTelemetry 아키텍처: 쉽게 설명
레이어를 헷갈리지 않게 하기 위해 OTel에서 추적 데이터의 전형적인 흐름을 시각화해봅시다.
flowchart LR
subgraph 애플리케이션
A[OpenTelemetry SDK]
end
A -->|트레이스, 메트릭, 로그| B[OpenTelemetry Collector]
B -->|Export| C[Jaeger/Zipkin/Grafana/Azure/Application Insights 등]
- OpenTelemetry SDK: 애플리케이션 내부에 직접 임베드됩니다(예: .NET용 NuGet 패키지로).
- OTel Collector: 별도의 중앙집중식 서비스(보통 Docker 컨테이너로 배포)로, 모든 애플리케이션에서 오는 트레이스 데이터를 받아 설정된 목적지로 내보냅니다.
- 관찰 프론트엔드: 예쁘게 그래프와 트리를 보고 필터링하고 트레이스를 분석할 수 있는 시스템입니다.
3. C#/.NET에서 추적 빠르게 시작하기
학습용 애플리케이션에 기본 분산 추적을 추가해봅시다. 주요 단계는 꽤 간단해요.
NuGet 패키지 설치
dotnet add package OpenTelemetry
dotnet add package OpenTelemetry.Exporter.Console
OpenTelemetry — SDK의 기본 구현;
OpenTelemetry.Exporter.Console — 트레이스를 콘솔로 내보내는 exporter(원하면 Jaeger, Zipkin 등으로 교체 가능).
최소한의 추적 설정
콘솔 애플리케이션의 경우 Program.cs 파일에서:
using OpenTelemetry;
using OpenTelemetry.Trace;
class Program
{
static void Main(string[] args)
{
// 추적 설정
using var tracerProvider = Sdk.CreateTracerProviderBuilder()
.AddSource("DemoApp") // 소스 이름 지정
.AddConsoleExporter() // 트레이스를 콘솔로 내보냄
.Build();
var source = new System.Diagnostics.ActivitySource("DemoApp");
// 새 트레이스 시작(루트 스팬)
using (var activity = source.StartActivity("메인 작업"))
{
DoWork();
}
}
static void DoWork()
{
// 애플리케이션 로직...
System.Threading.Thread.Sleep(300);
}
}
무슨 일이 일어나나?
- 트레이서 프로바이더를 만들고, 소스("DemoApp")를 등록한 뒤 콘솔 exporter(AddConsoleExporter())를 추가합니다.
- 프로그램 루트에서 ActivitySource를 통해 새 "활동"(span)을 시작합니다.
- 그 안에서 우리가 추적하고 싶은 실제 작업을 수행합니다.
결과 예시:
Activity.Id: 0b69e3e97ca5f14d
Activity.Operation: 메인 작업
Duration: 00:00:00.3001082
...
HTTP와 DB 자동 추적
OpenTelemetry는 인기 있는 라이브러리들을 자동으로 계측(instrumentation)할 수 있어요. 예를 들어 HttpClient를 통한 호출과 DB(ADO.NET) 쿼리는 코드 변경 없이 수집할 수 있습니다.
using var tracerProvider = Sdk.CreateTracerProviderBuilder()
.AddHttpClientInstrumentation() // HTTP 요청
.AddSqlClientInstrumentation() // SQL 요청(사용 시)
.AddConsoleExporter()
.Build();
이제 HttpClient를 통한 모든 호출과 SQL 관련 작업이 자동으로 트레이스에 나타납니다.
4. 트레이스 시각화
콘솔 출력은 시작에 불과해요. 실제로는 시각화가 가능한 프론트엔드를 사용하는 게 훨씬 유리합니다.
Jaeger
Jaeger는 트레이스 시각화를 위한 가장 인기 있는 오픈소스 시스템 중 하나입니다.
- Jaeger를 배포합니다(예: Docker로).
- AddConsoleExporter() 대신 다음을 사용합니다:
.AddJaegerExporter(options => { options.AgentHost = "localhost"; options.AgentPort = 6831; }) - 이제 모든 트레이스가 Jaeger UI에 표시되며, TraceId로 필터하거나 상세한 시간 다이어그램을 볼 수 있습니다.
전체 가이드: OpenTelemetry Jaeger Exporter for .NET
5. 유용한 팁
접근 방식 비교
OTel을 사용하지 않으면 TraceId를 로그에 수동으로 붙이고 전달하는 등 많은 수작업을 해야 해서 실수하기 쉽습니다. OTel은 체인을 자동으로 구성하고 클릭 한 번으로 시각화해줍니다.
| 방법 | 노력 | 신뢰성 | 확장성 | 호환성 |
|---|---|---|---|---|
| TraceId 수동 로깅 | 높음 | 낮음 | 지속적인 유지 필요 | 자신의 애플리케이션에 한정 |
| OpenTelemetry | 도입 후 최소 | 보장됨 | 광범위(언어·스택 불문) | 대부분의 APM 및 로그 시스템과 호환 |
아키텍처 예시
[웹 클라이언트] --> [API 서비스] --> [인증 서비스] --> [데이터베이스 서비스]
사용자가 버튼을 클릭하면 요청은 이 서비스들을 통과합니다. OTel을 사용하면 각 서비스에서 TraceId를 "잇는 실"을 당겨 전체를 하나의 트레이스로 자동으로 결합할 수 있어요.
- 각 서비스에서 OTel SDK는 HTTP 헤더에서 TraceId를 자동으로 인식하고 체인을 계속 이어갑니다.
- 결과적으로 하나의 트레이스를 열면 어디에 밀리거나 오류가 났는지 밀리초 단위로 확인할 수 있습니다.
실무 및 면접에서의 활용
- 마이크로서비스와 분산 애플리케이션(핀테크, 마켓플레이스, SaaS 등).
- DevOps/SRE: 문제를 빠르게 로컬라이즈.
- SLA와 응답성 개선.
- 프로파일링 및 최적화.
- 면접에서 성숙한 엔지니어링 실무를 보여주는 사례로 활용 가능.
6. 흔한 실수와 구현상의 주의점
TraceContext 전달 누락. 서비스 간에 TraceId가 전달되지 않으면(예: 필요한 HTTP 헤더를 넘기지 않음) 트레이스가 끊어져서 각 조각들이 독립된 "포인트"로 나타나며, 관찰성의 이점이 사라집니다.
비계측 코드. 라이브러리나 프레임워크가 자동 계측을 지원하지 않으면 일부 연산이 트레이스에서 빠질 수 있어요(이럴 때는 ActivitySource와 StartActivity()로 수동 스팬을 만들면 됩니다).
데이터 과다 수집. 모든 요청을 전부 수집하면 비용이 큽니다. 운영 환경에선 보통 일부 요청만(샘플링 비율) 또는 오류·느린 요청처럼 트리거 기반으로 수집합니다.
성능 영향. 비동기 수출(export)을 사용하면 OTel 도입으로 인한 퍼포먼스 영향은 최소화되지만, 트래픽이 매우 많을 경우 오버헤드를 모니터링하고 샘플링을 조절하세요.
GO TO FULL VERSION