Tracing
트레이싱
분산 시스템에서 요청 경로 추적. Jaeger, Zipkin.
트레이싱
분산 시스템에서 요청 경로 추적. Jaeger, Zipkin.
분산 트레이싱(Distributed Tracing)은 마이크로서비스 환경에서 하나의 요청이 여러 서비스를 거치는 경로를 추적하는 기술입니다. 2010년 Google의 Dapper 논문에서 시작되어, 현재는 OpenTelemetry가 표준으로 자리잡고 있습니다.
핵심 개념으로 Trace(전체 요청 경로), Span(개별 작업 단위), Context(서비스 간 전달되는 식별 정보)가 있습니다. 사용자 요청이 들어오면 고유한 Trace ID가 생성되고, 이 ID가 모든 서비스 호출에 전파되어 전체 경로를 연결합니다.
트레이싱의 핵심은 Context Propagation입니다. HTTP 헤더(traceparent, b3 등)나 gRPC 메타데이터로 Trace ID와 Span ID가 서비스 간에 전달됩니다. 각 서비스는 자신의 Span을 생성하고, 부모 Span ID로 연결하여 호출 트리를 구성합니다.
실무에서는 느린 API 응답, 간헐적 타임아웃, 에러 전파 경로를 분석할 때 필수적입니다. Jaeger, Zipkin, Grafana Tempo 같은 백엔드에 trace를 수집하고, 시각화된 워터폴 차트로 병목 지점을 찾습니다. 평균적으로 장애 원인 파악 시간이 80% 이상 단축됩니다.
// OpenTelemetry를 사용한 Node.js 트레이싱 설정
// tracing.js
const { NodeSDK } = require('@opentelemetry/sdk-node');
const { getNodeAutoInstrumentations } = require('@opentelemetry/auto-instrumentations-node');
const { OTLPTraceExporter } = require('@opentelemetry/exporter-trace-otlp-grpc');
const { Resource } = require('@opentelemetry/resources');
const { SemanticResourceAttributes } = require('@opentelemetry/semantic-conventions');
const sdk = new NodeSDK({
resource: new Resource({
[SemanticResourceAttributes.SERVICE_NAME]: 'user-service',
[SemanticResourceAttributes.SERVICE_VERSION]: '1.0.0',
[SemanticResourceAttributes.DEPLOYMENT_ENVIRONMENT]: process.env.NODE_ENV,
}),
traceExporter: new OTLPTraceExporter({
url: process.env.OTEL_EXPORTER_OTLP_ENDPOINT || 'http://tempo:4317',
}),
instrumentations: [
getNodeAutoInstrumentations({
'@opentelemetry/instrumentation-fs': { enabled: false },
'@opentelemetry/instrumentation-http': { enabled: true },
'@opentelemetry/instrumentation-express': { enabled: true },
}),
],
});
sdk.start();
// 애플리케이션 코드에서 커스텀 span 생성
// userController.js
const { trace, SpanStatusCode } = require('@opentelemetry/api');
const tracer = trace.getTracer('user-service');
async function createUser(req, res) {
// 커스텀 span 시작
const span = tracer.startSpan('createUser', {
attributes: {
'user.email': req.body.email,
},
});
try {
// 중첩 span으로 세부 작업 추적
const validationSpan = tracer.startSpan('validateInput');
await validateUserInput(req.body);
validationSpan.end();
const dbSpan = tracer.startSpan('saveToDatabase');
const user = await db.users.create(req.body);
dbSpan.setAttribute('db.rows_affected', 1);
dbSpan.end();
// 외부 서비스 호출 (자동으로 context 전파됨)
const notificationSpan = tracer.startSpan('sendWelcomeEmail');
await notificationService.sendWelcomeEmail(user.email);
notificationSpan.end();
span.setStatus({ code: SpanStatusCode.OK });
res.json({ success: true, user });
} catch (error) {
span.setStatus({
code: SpanStatusCode.ERROR,
message: error.message,
});
span.recordException(error);
res.status(500).json({ error: error.message });
} finally {
span.end();
}
}
// package.json 실행 스크립트
// "start": "node --require ./tracing.js app.js"
시니어: "주문 API가 간헐적으로 5초 넘게 걸려요. 로그만 봐서는 어디가 문제인지 모르겠어요."
주니어: "Trace ID로 Jaeger에서 조회해볼게요. 아, 결제 서비스에서 3.2초, DB 쿼리에서 1.5초 걸리네요."
시니어: "결제 서비스 쪽 span 펼쳐보세요. 외부 PG사 API 호출이 병목인 것 같아요."
면접관: "마이크로서비스 환경에서 성능 문제를 어떻게 디버깅하셨나요?"
지원자: "OpenTelemetry로 분산 트레이싱을 구현했습니다. 모든 서비스에 자동 계측을 적용하고, 중요 비즈니스 로직에는 커스텀 span을 추가했습니다. Grafana에서 trace와 로그를 연결해 특정 요청의 전체 경로와 각 구간 소요 시간을 한눈에 파악할 수 있었습니다."
리뷰어: "외부 API 호출하는데 span이 없네요. 이러면 이 구간 시간을 측정할 수 없어요."
개발자: "axios 요청에 tracer.startSpan 추가하겠습니다. 아, HTTP instrumentation이 자동으로 해주지 않나요?"
리뷰어: "자동 계측은 있지만, 비즈니스 의미를 담은 커스텀 attribute 추가하면 검색이 편해요."