시작하며
안녕하세요. 개발자 Stark입니다.
제가 최근 MSA 프로젝트를 운영하다 보니 서버 간 트랜잭션을 한눈에 볼 수 있도록 도와주는 분산추적 도구가 정말 중요하다는 생각을 하게 되었습니다. 예전에 제가 사이드 프로젝트를 할 때 Zipkin을 활용하여 분산추적을 구현해 봤었는데 최근 제가 트렌드를 알아봤더니 Jaeger를 사용하는 것이 조금 더 클라우드 네이티브 환경에 잘 맞는다는 것을 알게 되었습니다. 그래서 이번 기회에 실무 프로젝트에도 적용해 볼 겸 공식 가이드도 계속 살펴보고 AI 검색(ChatGPT Deep Research)의 도움을 받아 내용을 정리해봤습니다. 참고로 내용은 제가 구축하려는 방향성인 Jaeger(v2) + Elasticsearch를 기준으로 설명되었습니다. 잘못된 정보가 있을 수도 있지만 기본적인 개념을 잡는데 많은 도움이 되었으면 좋겠습니다.
1. Jaeger의 기본 개념과 아키텍처 (v2.6 최신 버전)
Jaeger는 Uber에서 시작되어 CNCF에 기부된 오픈 소스 분산 추적 플랫폼입니다. 마이크로서비스 환경에서 각 서비스 간의 트랜잭션 흐름(Trace)을 추적하고 스팬(Span) 단위로 성능 병목이나 에러 원인을 파악할 수 있도록 설계되었습니다. 2024년 말에 릴리스 된 Jaeger v2(예: 2.6 버전)는 기존 1.x와 아키텍처적으로 큰 변화가 있었습니다.
핵심은 Jaeger가 OpenTelemetry Collector 프레임워크를 기반으로 재구성되었다는 점입니다. 이는 Jaeger가 이제 OpenTelemetry의 데이터를 네이티브 하게 지원하고 수집·처리하며, OTel Collector의 유연한 파이프라인 구조(수신기-프로세서-내보내기)를 Jaeger 백엔드에 통합했다는 뜻입니다. 결과적으로 Jaeger v2는 OTLP 같은 표준 프로토콜을 변환 없이 바로 수용하여 성능을 높이고, 배치 처리 및 후단 샘플링(tail-based sampling) 등의 새로운 기능을 활용할 수 있게 되었습니다.
또한 Jaeger v2부터는 이전처럼 에이전트/콜렉터/쿼리 등을 각각 실행하는 다중 바이너리 구조 대신, 단일 바이너리(jaeger)로 제공된다고 합니다. 이 단일 실행 파일은 YAML 구성에 따라 에이전트, 콜렉터, 쿼리(UI) 등의 역할을 한 번에 또는 선택적으로 수행하도록 설정할 수 있습니다. 컨테이너 이미지도 40MB 미만으로 경량화되어 배포 효율이 향상되었습니다.
Jaeger의 기본 개념으로서, 애플리케이션에서 생성된 Span들은 Trace ID를 통해 '하나의 분산 Trace'로 연결되며, Jaeger는 이 Trace들을 수집하고 저장한 뒤 UI로 시각화합니다. Jaeger는 OpenTracing 및 OpenTelemetry 표준을 준수하여 W3C Trace Context 등의 컨텍스트 전파를 지원하고, 다계층 호출을 DAG 형태로 표현합니다. v2.6 기준 Jaeger는 여전히 추적(트레이스) 데이터에 특화된 백엔드로서, OpenTelemetry SDK가 수집한 데이터를 받아 저장/조회/시각화하는 역할을 합니다. (OpenTelemetry 프로젝트 자체는 멀티신호 데이터 수집 프레임워크이고, Jaeger는 그중 트레이싱 백엔드로 자리합니다.)
정리하면, Jaeger는 분산 시스템에서 '전체 요청 경로를 추적'해주는 플랫폼이며, 최신 버전(v2.6)은 OpenTelemetry와 긴밀히 통합된 유연한 아키텍처로 진화했습니다. 이로써 Jaeger는 향후 OTel과의 호환성을 유지하면서도 Jaeger 고유의 기능(예: 원격 샘플링, 적응형 샘플링, Jaeger UI 등)을 제공하는 확장 가능하고 표준화된 추적 인프라가 되었습니다.
Jaeger Collector v2는 어떻게 달라졌나?
항목 | Jaeger v1 | Jaeger v2 (최신, 2.x) |
내부 구현 구조 | Jaeger 자체 Collector 코드 (Go) | OpenTelemetry Collector 기반 |
구성 방식 | Jaeger 전용 CLI 플래그 | OTel Collector YAML 구성 파일 기반 |
지원하는 샘플링 방식 | Probabilistic, RateLimiting 등 | + Tail-based, Adaptive 등 OTel 기능 포함 |
플러그인/익스포터 확장성 | 제한적 | 매우 유연 (OTLP, Kafka, Prometheus, etc.) |
2. 구성 요소별 역할과 상호작용
Jaeger Collector
애플리케이션(혹은 에이전트/OTel 콜렉터)으로부터 스팬(span) 데이터를 수신하는 중앙 수집 컴포넌트입니다. 수신 프로토콜은 Jaeger SDK의 Thrift/UDP, gRPC 또는 OTLP(gRPC/HTTP) 등이 될 수 있습니다. 콜렉터는 받은 Trace들을 검증 및 전처리한 후 지정된 저장소(백엔드)에 기록합니다. Jaeger v2에서는 이 콜렉터가 OTel Collector 파이프라인으로 구현되어 있어 다양한 프로세서(예: 배치, 큐잉) 적용이나 여러 익스포터 출력도 가능합니다. 제가 찾아본 구성에서는 Jaeger Collector가 Elasticsearch를 스팬 저장소로 사용할 수 있으며, 필요시 Kafka를 통해 비동기 처리도 할 수 있었습니다. (Kafka를 중간 버퍼로 두는 경우, 추가로 Ingester 컴포넌트를 두어 Kafka에서 데이터를 소비(Consume)해 ES에 적재합니다.)
Jaeger UI/Query
추적 조회 및 UI 서비스 역할을 담당합니다. Jaeger UI는 웹 인터페이스로 제공되며, 사용자가 서비스별 트레이스를 검색하거나 특정 Trace ID로 상세 Span 타임라인을 볼 수 있습니다. 내부적으로 UI는 Query 서비스를 통해 Elasticsearch에 저장된 Trace 데이터를 조회합니다. Query 서비스는 저장소에서 spans를 검색/집계하고, Jaeger UI에 결과를 반환합니다. Jaeger v2에서는 이 Query 기능이 Collector 프로세스의 “Query Extension” 형태로 구현되어, Collector와 동일한 프로세스 내에서 UI를 제공할 수도 있습니다. UI는 서비스 의존성 그래프, 스팬 상세정보(tag, log 등), 성능 분포 등을 시각화하여 문제 분석에 활용됩니다.
Elasticsearch
Jaeger가 수집한 추적 데이터를 영구 저장하는 분산형 검색/스토리지 시스템입니다. Jaeger는 저장소로 Elasticsearch(ES)를 지원하며, 스팬 데이터와 서비스/오퍼레이션 메타데이터를 ES의 인덱스로 관리합니다. ES를 사용하면 강력한 검색 기능(기간, 서비스명, 태그 등으로 필터링)이 가능하여 Jaeger UI에서 풍부한 쿼리를 지원합니다. 예를 들어 'service=frontend AND error=true' 같은 조건으로 에러가 발생한 특정 서비스의 트레이스를 검색할 수 있습니다. 반면 ES를 사용하려면 데이터 인덱싱/샤딩 비용과 운영 오버헤드가 있으므로, 고가용성을 위해 ES 클러스터 및 모니터링(Kibana 등)이 함께 필요합니다.
Kibana
Elasticsearch의 시각화 대시보드 도구로, ES에 저장된 데이터 확인 및 관리에 사용됩니다. 엄밀히 말하면 Jaeger 전용 구성요소는 아니지만, ES 백엔드를 쓸 경우 운영자가 Kibana로 인덱스 상태를 모니터링하거나 트레이스 원시 데이터 질의를 해볼 수 있습니다. 예컨대 Kibana에서 Jaeger span 인덱스를 인덱스 패턴으로 추가하면, 특정 traceID나 태그 필드로 ES 내 데이터를 직접 조회해 볼 수 있습니다. 또한 Index Lifecycle Management(ILM) 등 ES 설정을 Kibana를 통해 손쉽게 관리할 수 있어, Jaeger+ES 환경에서 Kibana는 보조적인 관리 UI로 활용됩니다.
2-1. Elasticsearch의 Rollover (인덱스 관리)
Rollover는 Elasticsearch의 인덱스 관리 기법으로, 인덱스를 일정 조건(나이, 문서 수, 크기 등) 이상이 되면 새로운 인덱스로 롤오버시키는 방식입니다. Jaeger에서는 기본적으로 일자별 인덱스(매일 새로운 인덱스 생성)를 사용하지만, Rollover를 활성화할 경우 읽기/쓰기 앨리어스(alias)를 통해 동적으로 인덱스를 분리합니다.
구성 단계는 다음과 같습니다.
(1) 초기화(init): Jaeger 제공 도구(jaeger-es-rollover)로 ES에 초기 읽기/쓰기 alias와 템플릿을 생성
(2) 주기적 롤오버: 조건이 충족되면 alias를 새로운 인덱스로 넘기는 작업을 CronJob 등으로 주기 실행
(3) (선택) lookback: 읽기 alias에서 일정 기간 이전의 오래된 인덱스를 제외하여 검색 범위를 제한 등의 절차가 필요합니다.
Rollover을 사용하면 일별 인덱스 방식보다 데이터 균형 및 자원 활용 최적화에 유리하지만, 그만큼 운영 복잡도가 올라갑니다. Jaeger 구성 시 --es.use-aliases=true 옵션으로 Rollover를 활성화하며, 주기 실행 작업은 Kubernetes CronJob으로 배포해 일정 주기로 jaeger-es-rollover:latest rollover 명령을 호출하는 방식으로 구현할 수 있습니다.
2-2. Elasticsearch Cleaner(인덱스 정리 도구)
Elasticsearch Cleaner는 오래된 Trace 데이터를 삭제하여 저장소 용량을 관리하는 컴포넌트입니다. Jaeger는 기본적으로 jaeger-es-index-cleaner 유틸리티를 제공하며, 지정한 보존 기간보다 오래된 인덱스를 주기적으로 삭제합니다. 예를 들어 아래 명령에서 14는 보존 일수를 의미하며, 14일 초과된 인덱스를 제거합니다.
docker run --rm jaegertracing/jaeger-es-index-cleaner:latest 14 http://elasticsearch:9200
Rollover를 사용하는 경우, cleaner를 실행할 때 -e ROLLOVER=true 옵션을 주어 롤오버 된 인덱스들을 기준으로 삭제할 수 있습니다. 이 또한 Kubernetes CronJob으로 일정 주기마다 실행시켜 자동화하며, 데이터 보존 기간(예: 2주, 1달 등)에 맞게 파라미터를 조정합니다.
Cleaner를 쓸 때 주의할 점은, 읽기 alias에서 제외되지 않은 인덱스를 바로 삭제하면 UI 에러가 날 수 있으므로, 앞서 언급한 lookback 단계를 통해 안전하게 alias에서 제외한 후 삭제하는 것이 좋습니다. 또한 Elasticsearch 자체의 ILM 정책을 사용할 경우 cleaner를 따로 쓰지 않고 ILM의 삭제 단계로 대체할 수도 있습니다.
정리해 보면 이들 구성요소는 아래와 같이 상호 작용합니다.
Spring Boot 애플리케이션에서 OpenTelemetry로 생성된 스팬들은 Jaeger Collector에 전송되고, Collector는 이를 받아 ES에 저장합니다. 사용자가 Jaeger UI를 통해 특정 서비스나 트레이스를 조회하면, Query 서비스(UI)가 ES에서 관련 데이터를 검색하여 UI에 표시합니다. 한편 Rollover/Cleaner 프로세스는 백그라운드에서 ES 인덱스를 관리하여, Trace 데이터가 효율적으로 축적되고 오래된 데이터는 정리되도록 합니다. 필요에 따라 운영자는 Kibana로 ES 인덱스를 모니터링하거나, Jaeger 메트릭(예: 스팬 수집량)을 Prometheus 등으로 수집해 전체 추적 시스템의 건강 상태를 관찰할 수도 있습니다.
3. OpenTelemetry SDK를 사용하는 Spring Boot에서의 Trace 수집 및 Jaeger 전달 흐름
Spring Boot 애플리케이션에 OpenTelemetry SDK를 적용하면, 각 요청 및 내부 연산에 대한 스팬(Span)이 자동 또는 수동으로 생성되어 Trace를 구성합니다. 이번 목차에서는 애플리케이션 코드에서 Jaeger Collector까지 Trace 데이터가 전파되는 전체 흐름을 단계별로 설명해 보겠습니다.
1. 애플리케이션에서 Trace/Span 생성
Spring Boot에서는 일반적으로 Spring Cloud Sleuth(Micrometer Tracing)나 OpenTelemetry Java Agent를 통해 주요 구간을 자동 계측합니다. 예를 들어 HTTP 요청을 수신하면 새 Trace와 Root Span이 시작되고, 이후 DB 쿼리나 REST API 호출 등마다 자식 Span이 생성됩니다. 이 Span들은 Trace Context(W3C Traceparent 헤더 등)를 통해 서비스 경계를 넘어 전파되며, 각 서비스의 OTel SDK는 부모 컨텍스트를 이어받아 새로운 Span을 생성합니다. 이러한 방식으로 분산 시스템 전체에 걸쳐 TraceID로 연결된 Span들의 트리/그래프가 만들어집니다.
2. OpenTelemetry SDK의 Exporter 동작
애플리케이션 내 OTel SDK는 일정 시간 또는 Span 배치(batch) 단위로 누적된 Span 데이터를 외부로 전송합니다. 이를 담당하는 모듈이 Exporter이며, 설정에 따라 OTLP 또는 Jaeger 프로토콜 등을 사용합니다. 권장되는 방식은 OTLP gRPC/HTTP Exporter를 사용하는 것으로, OpenTelemetry 공식 가이드도 애플리케이션에서 수집기(Collector)로 OTLP를 보내는 구성을 모범 사례로 제시합니다. 예컨대 Spring Boot의 환경설정에서 OTLP Exporter를 활성화하고 OTEL_EXPORTER_OTLP_ENDPOINT=http://jaeger-collector:4318 와 같이 Jaeger Collector의 OTLP 수신 endpoint를 지정하면, SDK가 백그라운드 스레드로 해당 주소에 Span들을 전송합니다. (Jaeger v2.6의 Collector는 기본적으로 OTLP gRPC 4317, HTTP 4318 포트를 지원합니다.)
3. 데이터 송신 경로
OTel SDK가 보낸 Span 데이터는 네트워크를 통해 Jaeger 수집 엔드포인트로 전송됩니다. 여기에는 몇 가지 시나리오가 있습니다.
- 에이전트(sidecar/daemonset) 경유: Jaeger Agent를 세팅한 경우, Exporter가 localhost:6831/6832(UDP) 등의 주소로 데이터를 보내고, Agent가 이를 받아 Jaeger Collector로 재전달합니다. 과거 Jaeger Java SDK는 이 방식을 사용했으며, UDP 전송 특성상 손실 가능성을 Agent가 보완해 주었습니다. OTel SDK도 Jaeger-Thrift Exporter를 사용하면 UDP로 Agent에 보낼 수 있습니다.
- 직접 Collector 전송: Spring Boot에서 OpenTelemetry를 사용할 땐 에이전트를 생략하고 애플리케이션에서 바로 Jaeger Collector로 보내는 구성이 흔합니다. 이때는 Exporter가 Jaeger Collector의 gRPC/HTTP 주소를 향해 데이터를 보내며, TCP 연결을 사용하므로 전송 신뢰성이 높습니다. 다만 애플리케이션이 Jaeger Collector의 서비스 주소를 알아야 하므로, 서비스 디스커버리나 고정 환경 변수를 통해 Jaeger Collector의 주소를 주입합니다.
- OTel Collector 에이전트 모드: Jaeger Agent 대신 OpenTelemetry Collector를 사이에 둘 수도 있습니다. 예를 들어 애플리케이션 Pod에 사이드카로 가벼운 OTel Collector를 실행하여, 앱은 localhost의 Collector로 OTLP를 보내고, 이 Collector가 중앙 Jaeger 또는 Elasticsearch로 재전송하는 방식입니다. 이는 Jaeger Agent와 유사한 로컬 프록시 역할을 하지만, OTLP를 사용하고 데이터 가공(필터링 등)도 가능하다는 장점이 있습니다.
4. Jaeger Collector 처리 및 저장
Collector는 애플리케이션(또는 에이전트/OTel Collector)으로부터 받은 Span들을 수집 파이프라인에 투입합니다. v2 Jaeger Collector 내부에는 OTel Collector와 마찬가지로 수신기(Receiver), 프로세서(Processor), 내보내기(Exporter) 단계가 있습니다. OTLP나 Jaeger.thrift 등 여러 Receiver로 입력받은 Span들은 우선 배치 프로세서 등을 통해 모아지고, 필요하면 샘플링이나 태그 마스킹 등의 추가 Processor를 거친 후, Elasticsearch Exporter를 통해 ES 클러스터에 저장됩니다. (엘라스틱 서치를 사용한다고 가정한 상황입니다) 이때 ES Exporter는 Span 객체를 JSON 문서로 변환하여 ES API로 색인 저장을 수행합니다. 성공적으로 저장되면 Collector는 해당 Span에 대한 ack를 반환하거나(OTLP gRPC의 경우) UDP라면 best-effort로 끝납니다. Collector는 또한 서비스 발견 정보를 별도 index(jaeger-service-*)에 기록하여 UI의 서비스 목록을 구성합니다.
5. Jaeger UI를 통한 Trace 조회
최종적으로 사용자는 Jaeger UI (혹은 Grafana 등 UI에서 Jaeger 데이터를 연동한 경우 Grafana)를 통해 Trace 데이터를 조회합니다. Jaeger UI에 서비스 이름, 시간 범위, 태그 등의 필터를 입력하면 Query 서비스가 Elasticsearch에 해당 조건의 인덱스 쿼리를 수행합니다. ES는 분산 색인을 통해 조건에 부합하는 Trace의 스팬들을 검색하고, 결과를 반환합니다. Jaeger UI는 이 결과를 받아 Trace별 트랜잭션 타임라인으로 재구성하여 화면에 보여줍니다. 예를 들어 한 Trace를 클릭하면 그 Trace의 모든 Span들이 계층 구조로 시간순 나열되고, 각 Span의 duration, 서비스명, operation, 태그(예: http.status_code), 로그 이벤트 등이 세부 패널에 표시됩니다. 또한 UI는 서비스 간 종속성 그래프를 그리기 위해 주기적으로 ES에 집계 쿼리를 날려 서비스 호출 관계를 도식화합니다 (v2.6에선 Collector에 내장된 SPM(Service Performance Monitoring) 기능이 이를 담당합니다).
요약하면, Spring Boot - OpenTelemetry SDK - Jaeger로 이어지는 경로에서, 애플리케이션은 비동기로 Trace 데이터를 내보내고, Jaeger Collector가 이를 받아 ES에 보내서 저장한 뒤 UI에서 ES에 저장된 Trace 데이터를 Query해서 표현합니다. 개발자는 애플리케이션 로깅에는 영향을 주지 않으면서도, 이러한 추적 데이터를 통해 시스템 전반의 분산 트랜잭션 가시성을 얻을 수 있습니다. 모든 구성은 Kubernetes 서비스와 ConfigMap 등을 통해 연결되며, 예를 들어 deployment.yaml에서 애플리케이션에 OTEL_EXPORTER_OTLP_ENDPOINT=http://jaeger-collector:4318 환경변수를 주고, Jaeger Collector 서비스의 클러스터 IP로 통신하도록 설정합니다. 결과적으로, 코드 레벨의 Trace 생성부터 최종 UI 시각화까지의 사이클이 자동화되어, 운영자는 엔드투엔드 분산 추적을 활용한 모니터링/디버깅을 할 수 있습니다.
4. 애플리케이션(OpenTelemetry SDK) 내부에서 동작하는 샘플러
샘플링(Sampling)은 모든 트레이스를 수집하지 않고 일부만 선택적으로 수집함으로써 오버헤드를 줄이는 기법입니다. 분산 추적에서 샘플링을 적절히 적용하면 저장소 비용을 절감하고 노이즈를 줄일 수 있지만, 과도하면 중요한 정보를 놓칠 위험이 있습니다. Jaeger/OpenTelemetry에서 사용되는 주요 샘플링 전략의 이론적 배경과 실제 적용법은 다음과 같습니다. 참고로 하단의 모든 샘플링은 Head-based 샘플링 방식입니다.
AlwaysOn 샘플러
모든 트레이스를 샘플링하는 정책입니다. 샘플링 결정이 항상 “예”로 나와 100% 수집되므로, 가장 완전한 진단 정보를 제공합니다. 기본 OpenTelemetry SDK의 기본값이 이 AlwaysOn이며 (정확히는 ParentBased+AlwaysOn 조합), 개발/테스트 환경이나 트래픽이 적은 시스템에서 주로 사용합니다. 예를 들어 AlwaysOn이면 매 요청마다 새로운 Trace가 시작되고 Exporter로 전달됩니다. AlwaysOn은 구현이 가장 간단하고 통계적으로 정확도도 문제가 없지만, 대규모 트래픽 환경에서는 데이터량이 감당이 안 될 수 있다는 단점이 있습니다.
AlwaysOff 샘플러
모든 트레이스 무시(0% 수집)하는 정책입니다. 운영 중 임시로 추적을 끄거나 성능 테스트 시 추적 부하를 완전히 배제하고 싶을 때 사용합니다. OTel 설정으로는 OTEL_TRACES_SAMPLER=always_off를 주면 됩니다. 일반적인 프로덕션 환경에서는 잘 사용되지 않지만, 특정 상황에서 샘플링 일시 정지 용도로 쓸 수 있습니다.
TraceIdRatioBased (Probabilistic: 확률적 샘플링)
Trace ID 해시값을 기반으로 일정 비율의 트레이스를 샘플링하는 방법입니다. 예를 들어 비율을 0.1(10%)로 설정하면, 약 10% 확률로 트레이스가 선택되어 수집되고 나머지 90%는 폐기됩니다. 이 샘플러는 TraceID를 이용해 결정하므로 분산 시스템 전체에서 동일한 결정을 내릴 수 있습니다. 즉 어떤 서비스에서 TraceID로 “샘플링=true” 결정이 나면, 그 하위 호출들은 ParentBased 샘플러를 통해 자동 승계됩니다. 이론적 장점은 트래픽 양에 비례해 균일한 표본을 얻기 쉽고, 구현이 단순하며, 통계적으로 span 수 등을 보정 계산이 가능하다는 것입니다.
ParentBased 샘플러
부모 Span의 샘플링 여부를 따르는 복합 로직입니다. 새로운 Trace의 루트에서는 별도의 샘플러(e.g. Ratio 또는 AlwaysOn)가 결정하고, 그 하위에서는 부모가 유효(샘플링됨)인 경우 무조건 따라가도록 하는 방식입니다. 이는 한 분산 Trace 내 일부 Span만 수집되고 다른 Span은 버려지는 모순을 막기 위한 것으로, Trace 단위의 완결성을 보장합니다. 실제 대부분의 SDK 기본값이 ParentBased(부모) + AlwaysOn(루트) 조합인데, 이 뜻은 “루트 Span은 AlwaysOn으로 샘플링 결정하고, 자식은 부모에 따름”이라는 의미입니다. 만약 Ratio 기반과 결합하면 “루트에서 확률 적용, 자식은 따름”이 됩니다. 적용: OTel에서 ParentBased는 내부적으로 기본 적용되며, 수동 설정 시 ParentBasedSampler(base=TraceIdRatioBased(0.5)) 이런 식으로 구성합니다.
4-1. OpenTelemetry Collector에서 동작하는 샘플러 (Jaeger V2 포함)
Tail-based Sampling (후단 샘플링, Jaeger v2만 지원)
트레이스가 완료된 후, 즉 모든 Span 정보를 수집한 뒤에 나중에 샘플링 여부를 결정하는 기법입니다. Head-based (사전) 샘플링과 달리, 오류 여부나 지연 시간 등을 판단 기준으로 중요한 트레이스만 선별 저장할 수 있다는 이점이 있습니다. 예를 들어 “HTTP 5xx 오류가 포함된 Trace는 모두 샘플링하고, 정상 트레이스는 1%만 샘플링” 같은 세분화 정책이 가능하므로, 중요 정보를 놓치지 않으면서도 데이터량을 크게 줄일 수 있습니다.
Tail sampling 구현은 보통 Collector(백엔드)에서 이뤄집니다. 애플리케이션에서는 일단 모든 Span을 수집기로 보내야 하며, Collector가 TraceID별로 Span들을 모았다가 조건 만족 시 해당 Trace의 Span 전부를 저장하고, 아니면 폐기합니다. Jaeger v1까지는 후단 샘플링 기능이 없었으나, Jaeger v2에서는 OTel Collector의 Tail Sampling 프로세서를 그대로 활용하여 이 기능을 지원합니다. OTel Collector 설정에서 tailsamplingprocessor에 정책을 작성하여 (예: error=true 면 keep, 아니면 비율 5% 등) 적용할 수 있고, Jaeger v2도 동일한 YAML 구성으로 Collector에 Tail sampling 규칙을 넣으면 됩니다.
주의점: Tail sampling은 메모리 버퍼가 필요하고, Trace 완성을 판단하기 위해 지연 시간이 추가됩니다. 트레이스가 완성될 때까지 Collector가 Span들을 쌓아두어야 하므로, 아주 높은 QPS 환경에서는 메모리 부담이 크고, Trace 길이가 길거나 지속 시간이 긴 경우 샘플링 결정이 느려질 수 있습니다. 따라서 Tail Sampling은 주로 오류율이 낮지만 트래픽이 높은 서비스에서, 중요 이벤트만 골라 저장하려는 목적으로 활용하며, 시스템 성능과의 트레이드오프를 면밀히 고려해야 합니다. 참고로 Jaeger v2는 OpenTelemetry Collector 기반이기 때문에 Tail-based Sampling은 Jaeger v2에서도 동작합니다.
샘플링 종류 | 구현 위치 | Jaeger v1 | Jaeger v2 | OTel Collector |
Head-based | SDK, Jaeger Collector | ✅ | ✅ | ✅ |
Tail-based | OTel Collector 기반에서만 가능 | ❌ | ✅ | ✅ |
4-2. Jaeger Collector에서 동작하는 샘플링
하단의 모든 샘플링은 Head-based 샘플링 방식입니다.
RateLimiting 샘플러 (Jaeger v1, v2 지원)
1초에 N건까지만 Trace를 받아들이는 속도 제어형 샘플링입니다. Jaeger에 원래 있던 방식으로, 초당 2트레이스, 5트레이스 등 절대 속도 상한을 지정합니다. 트래픽이 매우 적을 땐 확률보다 유용할 수 있고, 스파이크를 완화하는 데 도움 됩니다. 그러나 분산 환경에서 정확히 N TPS로 제어하는 것은 서비스별로 따로 적용하면 전체 의미가 퇴색되므로, 보통 단일 서비스의 로컬 샘플러로 씁니다. Jaeger 원격 샘플링 설정 파일에서 type: ratelimiting, param: 5 식으로 서비스별 적용 가능하며, OTel SDK에는 자바의 경우 아직 RateLimitingSampler는 공식 제공되지 않고 (Processor단에서 구현하거나 Collector 측에서 수행), OTel Collector의 tail-sampling 프로세서에서 rate-limiting 적용은 가능합니다.
Remote Sampling (원격 샘플링, Jaeger v1, v2 지원)
Remote sampling은 각 서비스의 Jaeger SDK/Agent가 Collector로부터 샘플링 전략을 주기적으로 내려받아 적용하도록 한 것입니다. 운영자는 Jaeger Collector의 sampling_strategies.json 파일이나 REST API로 서비스별 확률 또는 rate를 지정할 수 있고, 에이전트들은 이를 폴링하여 로컬에 반영합니다. 이렇게 하면 다수의 마이크로서비스에서 중앙집중적으로 샘플링 정책 관리가 가능해집니다.
Adaptive Sampling (적응형 샘플링, Jaeger v2만 지원)
Adaptive sampling은 Remote sampling의 한 형태로, Jaeger Collector가 실시간으로 수집량을 관찰하여 각 서비스/엔드포인트별 샘플링 확률을 동적으로 조정하는 기능입니다. 예컨대 어떤 서비스의 특정 엔드포인트 트래픽이 분당 1000건으로 치솟으면 해당 엔드포인트의 샘플링 비율을 자동으로 낮춰주는 식입니다. 이것은 설정한 타겟 TPS 수준을 유지하도록 확률을 증감시키는 알고리즘으로 동작하며, 저유량 서비스는 100% 수집, 고유량 서비스는 낮은 비율로 조절하여 데이터의 대표성 확보와 수집량 제어를 동시에 달성합니다. Adaptive sampling 설정은 Jaeger Collector 옵션으로 켜둘 수 있고, 초기 sampling 설정과 목표치를 정의하면 Collector가 주기적으로 계산합니다. 다만 OTel SDK 자체에는 이 기능이 없고 Jaeger 호환 환경에서만 동작합니다. OpenTelemetry로 전환된 환경에서는 Tail sampling + Metric 알람 등으로 대체하는 추세입니다.
5. 실제 Sampling 적용 측면 분석하기
실제 적용 측면에서, Spring Boot + OTel SDK 환경의 기본 샘플링은 AlwaysOn(전체 수집)입니다. 이는 초기에는 완전한 추적으로 디버깅에 유리하지만, 프로덕션 환경에서는 앞서 언급한 방식들로 샘플링 전략을 조정해야 할 수 있습니다. 일반적으로는 다음과 같은 절차를 권장합니다.
1. 전구간 수집 (초기)
시스템 안정화 전이나 트래픽이 낮은 동안에는 AlwaysOn으로 설정해 모든 트레이스 데이터를 수집/모니터링합니다. 이때 성능 오버헤드와 저장소 사용량을 관찰합니다.
2. 헤드 샘플링 도입
부하가 커지면 우선 TraceIdRatioBased 샘플링으로 전환하여, 예를 들어 10~20%만 수집하도록 합니다. OTel SDK 설정만으로 간단히 적용 가능하며, 대부분의 경우 문제없는 성능을 보입니다. 이때 ParentBased와 함께 설정하여 Trace의 일관성을 유지합니다. Jaeger Collector에서 remote sampling을 사용 중이라면, Collector 설정 파일에 각 서비스의 비율을 기재하고 Jaeger 에이전트를 통해 적용합니다.
3. 고급 샘플링 고려
단순 확률로는 중요 트랜잭션을 놓칠 우려가 있다면, Tail Sampling을 Collector 단계에서 사용합니다. 이를 위해 Jaeger v2 Collector (jaeger.yaml 설정 파일)에 tailsamplingprocessor를 추가하고, 규칙(예: error=trueTrace는 모두 저장)을 작성합니다. 혹은 OpenTelemetry Collector를 애플리케이션과 Jaeger 사이에 넣어 Tail Sampling을 수행해도 됩니다. Tail Sampling은 리소스가 허용하는 선에서 신중히 도입하되, 드롭할 기준(성공한 짧은 트랜잭션 등)을 명확히 정해야 합니다.
4. 샘플링 튜닝 및 모니터링
일단 샘플링을 적용하면, 샘플 비율에 따른 추적지표 보정이 필요합니다. 예를 들어 10% 샘플링이면 관측된 에러수치를 10배로 추산하는 식입니다. 또한 샘플링으로 인해 일부 메트릭이 왜곡되지 않는지 검증해야 합니다. Jaeger UI나 Grafana 등을 통해 샘플링으로 어떤 Trace가 드롭되고 있는지 모니터링하고, 정책을 보완합니다. (Adaptive Sampling을 켰다면 Jaeger가 자동으로 확률을 조정하지만, 로그를 통해 조정 현황을 살펴야 합니다.)
정리하면, 샘플링 전략의 선택은 데이터 품질 vs. 비용의 균형점을 찾는 과정입니다. AlwaysOn은 완벽한 데이터 제공 대신 비용 고저를 감수하고, 확률적(head, Probabilistic) 샘플링(은 단순/경량이지만 중요한 이벤트 누락 가능성이 있으며, 후단(Tail) 샘플링은 중요한 Trace에 집중 가능하지만 구현 복잡도와 지연이 추가됩니다. 일반적인 권장 순서는 우선 헤드 샘플링으로 대부분 요구를 충족하고, 그래도 부족하면 후단 샘플링 등 고급 기법을 도입하는 것입니다. Jaeger의 원격/적응형 샘플링은 OTel 환경에서도 계승되어 사용할 수 있으므로 (Jaeger Collector v2에서 지원) 대규모 다수 서비스 환경에서는 이를 활용하여 중앙 관리형 샘플링 정책을 운영하는 것도 좋습니다.
6. Elasticsearch 인덱스 롤오버 및 정리 전략 (권장 방식)
Jaeger를 Elasticsearch와 함께 사용할 때, 인덱스 관리는 성능 및 비용 측면에서 매우 중요합니다. 기본적으로 Jaeger Collector는 일별 인덱스(index-per-day)를 생성하여 데이터를 저장합니다. 예를 들어 jaeger-span-2023-12-01, jaeger-span-2023-12-02와 같이 날짜별 인덱스를 만들고, 새로운 날이 되면 새 인덱스로 기록을 시작합니다. 이러한 방식은 설정이 간단하지만, 데이터량이 불균등한 경우 비효율을 초래할 수 있습니다. (예: 주말에는 인덱스가 거의 비어 있어도 샤드가 할당되며, 특정 하루는 폭증하여 그 인덱스 하나만 너무 커지는 등.) 또한 보존 기간이 지나면 오래된 날짜의 인덱스를 삭제해야 하는데, Jaeger 자체로는 자동 삭제를 하지 않으므로 수동 정리가 필요합니다.
Rollover 전략은 이러한 문제를 완화하기 위한 엘라스틱서치의 권장 인덱스 관리 기법입니다. Rollover를 사용하면 날짜와 무관하게, 현재 쓰기 인덱스의 크기/나이 등 조건에 따라 수시로 새 인덱스로 교체합니다. Jaeger에서 Rollover를 활성화하려면 --es.use-aliases=true 옵션을 주고, 초기 세팅과 주기적 작업을 수행해야 합니다. 구체적인 권장 방식은 다음과 같습니다.
1. 초기화 (Initializer)
Jaeger 팀이 제공하는 Docker 이미지인 jaegertracing/jaeger-es-rollover를 이용해, 처음 한번 ES에 필요한 세팅을 만듭니다. init 커맨드를 실행하면 (예: jaeger-es-rollover:latest init http://elasticsearch:9200), jaeger-span-write와 jaeger-span-read라는 앨리어스(alias)와 초기 물리적 인덱스(예: jaeger-span-000001 등)가 생성됩니다. 또한 mapping 템플릿이 설정되어 추후 생성될 인덱스에 일관된 스키마가 적용됩니다. Archive용 인덱스를 쓸 경우 -e ARCHIVE=true 옵션으로 별도 alias도 생성합니다. 이 초기화 단계는 필수이며, 실행하지 않고 Jaeger Collector를 es.use-aliases=true로 켜면 Collector가 찾는 alias가 없어 에러가 발생합니다.
2. 롤오버 조건 실행
운영 중에는 주기적으로 Rollover API를 호출하여, 조건 만족 시 write alias를 새로운 인덱스로 롤오버해야 합니다. 예를 들어 하루 단위로 굴리고 싶다면 CONDITIONS='{"max_age": "1d"}'로, 혹은 50GB 이상 커지면 굴리고 싶다면 CONDITIONS='{"max_size": "50gb"}' 식으로 조건을 지정합니다. CronJob으로 0시마다 실행하거나, 혹은 6시간마다 실행하면서 max_age를 1일로 두어 24시간 경과 시점에 교체되도록 할 수 있습니다. Jaeger의 rollover 스크립트는 조건이 충족될 경우 write alias를 새 인덱스로 이동하고, 이전 인덱스를 read alias에 남겨 과거 데이터는 조회 가능하게 유지합니다. Rollover 주기를 너무 잦게 하면 오버헤드가 있으니, 일반적으로 하루이틀에 한 번 또는 20~50GB당 한 번 정도가 적절합니다. (K8s에선 하단의 명령을 CronJob으로 등록)
// 예시
docker run --rm -e CONDITIONS='{"max_age": "2d"}' jaegertracing/jaeger-es-rollover:latest rollover http://ES:9200
3. lookback alias 관리 (옵션)
새 인덱스로 롤오버한 후, read alias에서 너무 오래된 인덱스를 빼줌으로써 검색 범위를 제한할 수 있습니다. Jaeger의 기본 daily 인덱스 모드에서는 --es.max-span-age 파라미터로 UI에서 조회 가능한 최대 기간을 정했는데, Rollover 환경에선 해당 역할을 lookback 명령이 합니다. 예컨대 7일로 할 경우 -e UNIT=days -e UNIT_COUNT=7 옵션으로 jaeger-es-rollover:latest lookback를 실행하면, read alias에서 7일 초과된 인덱스를 unalias 처리합니다. 이렇게 하면 Jaeger UI에서 자동으로 7일보다 이전 데이터는 조회되지 않습니다 (물리적으로 삭제된 것은 아니지만, read alias에 없으므로 검색에서 빠짐). 이 단계는 꼭 필요하지는 않지만 성능을 위해 권장됩니다. 생략하는 경우, 아주 오래된 인덱스까지 alias에 남아있으면 조회 쿼리 시 불필요한 범위를 찾느라 느려질 수 있습니다.
4. 인덱스 삭제(Index Cleaner)
마지막으로, 보존 주기가 지난 인덱스를 실제로 삭제합니다. Rollover를 써도 오래된 인덱스가 계속 쌓이므로, cleaner가 필요합니다. jaeger-es-index-cleaner 이미지를 Rollover 모드로 실행하면 (-e ROLLOVER=true), 지정한 일수보다 오래된 순번의 인덱스를 제거합니다. 이 역시 CronJob으로 하루에 한 번 정도 돌립니다. 일반적으로 'lookback 일수 < 보존 일수'로 설정하여, 먼저 alias에서 제외 -> 나중에 삭제 순으로 운영합니다. 예를 들어 lookback 7일, 삭제 14일로 두면, 8일 지난 데이터부터 UI에 안 보이고 15일째 실제 삭제되는 흐름입니다. (보존 정책은 조직 요구사항에 맞게 설정하면 됩니다.)
5. Elasticsearch ILM(Index Lifecycle Management) 활용
수동 rollover+cleaner 대신, Elasticsearch의 ILM 기능을 사용할 수도 있습니다. Jaeger 1.22+ 버전부터 ILM 지원이 추가되었고, Jaeger v2에서도 사용 가능합니다.
권장되는 방식은
(1) ES에 jaeger-ilm-policy라는 ILM 정책을 생성 (예: hot 단계 1일 or 50GB 후 rollover, warm 단계 7일 유지, delete 단계 14일 후 삭제 등)
(2) Jaeger ES 템플릿/초기화를 ILM 모드로 실행 (ES_USE_ILM=true로 jaeger-es-rollover init 실행)하여 해당 정책을 적용
(3) Jaeger Collector를 --es.use-ilm=true 모드로 실행하는 것입니다.
이렇게 하면 Elasticsearch 클러스터가 자체적으로 인덱스 롤오버와 삭제를 수행하므로, 별도 CronJob 없이도 데이터 관리가 이뤄집니다. 단, ILM 적용 시 Jaeger 쪽 설정과 ES 정책이 어긋나지 않도록 유의해야 합니다. 예컨대 ILM 정책명을 Jaeger 설정과 맞추고 (jaeger-ilm-policy), ILM 용 템플릿이 제대로 생성됐는지 확인해야 합니다. ILM 사용 시 Jaeger 로그에 정책을 찾지 못하면 에러를 낼 수 있으므로 (policy doesn't exist 오류), 초기화 순서를 지켜야 합니다.
6-1. Elasticsearch 인덱스 롤오버 및 정리 전략 (주의점)
1. 일관성 보장
Rollover/ILM을 사용하는 경우, Jaeger Collector와 ES의 설정이 일치해야 합니다. Collector를 use-aliases=true로 켰는데 alias가 없거나, ILM=true로 켰는데 정책이 없으면 정상동작 안 합니다. 따라서 변경 시에는 초기화를 다시 하거나 사전 준비를 해야 합니다.
2. 운영 자동화
위 단계들은 수동으로도 할 수 있지만 사람 실수를 줄이기 위해 자동화가 필요합니다. Kubernetes CronJob으로 rollover/lookback/cleaner를 돌릴 때, 실행 주기와 타이밍을 체계적으로 관리해야 합니다. 예컨대 매일 00:00에 rollover, 00:10에 lookback, 00:30에 cleaner 수행 식으로 스케줄링하여 순서를 보장합니다.
3. 성능과 비용 균형
Rollover는 인덱스 크기를 일정하게 유지해 검색 성능을 안정시키지만, 너무 작은 단위로 롤오버 하면 오히려 인덱스가 늘어나 관리비용이 증가합니다. 조건을 서비스 패턴에 맞춰 조정하고, ES 모니터링을 통해 샤드 수나 인덱스 크기를 지속 평가해야 합니다. 예를 들어 하루 100GB씩 쌓인다면 50GB 단위 rollover로 하루 2개, 한 달 60개 인덱스 -> 보존 2달이면 120개 정도 유지, 적절. 반면 하루 1GB 미만이면 굳이 rollover 않거나 7일에 한 번 등으로 넉넉히 묶는 편이 낫습니다.
4. Kibana 및 모니터링 활용
Kibana Index Management 화면이나 _cat/indices API로 현재 alias와 인덱스 상태를 주기적으로 점검합니다. Rollover/삭제 작업이 제대로 이뤄졌는지, alias에 active 인덱스가 하나만 있는지 등을 확인하여 문제 (예: rollover 안 되어 단일 인덱스가 비대해짐, alias 빠뜨림 등)를 조기에 발견할 수 있습니다.
5. 백업 고려
데이터 보존 기간이 짧다면 (예: 7일) 상관없으나, 중요한 추적을 장기간 보존하려면 삭제 전에 스냅샷 백업을 검토해야 합니다. Jaeger Archive 기능을 별도 Cassandra 등으로 구성할 수도 있지만 복잡하므로, 필요시 ES의 snapshot 기능으로 중요한 인덱스를 저장 후 cleaner로 지우는 방안을 사용할 수 있습니다.
결론적으로, Elasticsearch 기반 Jaeger의 지속 운용에는 인덱스 롤오버와 정리 전략이 필수입니다. 권장 접근은 가능하면 Elasticsearch ILM을 활용하여 간소화하고, ILM 사용이 어려울 경우 위의 Rollover+Cleaner 조합을 스크립트/크론으로 자동화하는 것입니다. 이를 통해 성능 저하 없이 데이터 보존 요구를 충족하고, 디스크 사용량을 예측 가능하게 관리할 수 있습니다.
출처
Docs (2.6)
Docs (2.6)See also: Welcome to Jaeger’s documentation! Below, you’ll find information for beginners and experienced Jaeger users. If you cannot find what you are looking for, or have an issue not covered here, we’d love to hear from you. About Jaeger
www.jaegertracing.io
Jaeger v2 released: OpenTelemetry in the core!
Project post by the Jaeger maintainers Jaeger, the popular open-source distributed tracing platform, has had a successful 9 year history as being one of the first graduated projects in the Cloud…
www.cncf.io
Distributed Tracing Tool Jaeger Releases Version 2 with OpenTelemetry at the Core
Version 2 of the Jaeger project, a leading open-source distributed tracing platform, has been released. This release contains a significant architectural transformation, as it brings Jaeger and its components into the OpenTelemetry framework.
www.infoq.com
https://www.jaegertracing.io/docs/2.6/architecture/
Architecture
ArchitectureSee also: Jaeger v2 is designed to be a versatile and flexible tracing platform. It can be deployed as a single binary that can be configured to perform different roles within the Jaeger architecture. Roles collector: Receives incoming trace da
www.jaegertracing.io
https://last9.io/blog/grafana-tempo-vs-jaeger/#:~:text=,in%20on%20specific%20request%20patterns
Grafana Tempo vs Jaeger: Key Features, Differences, and When to Use Each | Last9
Grafana Tempo vs Jaeger: Understand how they differ in storage, querying, and setup—so you can choose the right tracing tool for your stack.
last9.io
A Guide to Deploying Jaeger on Kubernetes in Production
Deploying Jaeger Tracing on containers with K8s in production involves multiple components with different options and tools.
medium.com
ElasticSearch
While initializing with ILM support, make sure that an ILM policy named jaeger-ilm-policy is created in Elasticsearch beforehand (see the previous step), otherwise the following error message will be shown: “ILM policy jaeger-ilm-policy doesn’t exist i
www.jaegertracing.io
Tail Sampling with OpenTelemetry: Why it’s useful, how to do it, and what to consider
Tail sampling is useful for identifying issues in your distributed system while saving on observability costs. In this post, you’ll learn how to implement tail sampling using the OpenTelemetry Collector. I will also share some general and OpenTelemetry-s
opentelemetry.io
https://uptrace.dev/opentelemetry/sampling
OpenTelemetry Sampling: head-based and tail-based
Sampling reduces the cost and verbosity of tracing by reducing the number of created spans.
uptrace.dev
Sampling
Sampling Sampling is essential when handling large volumes of traces. Sampling helps to control both the overhead on the applications producing traces, and the storage and processing costs for trace data. Ideally, sampling aims to discard less interesting
www.jaegertracing.io
https://medium.com/jaegertracing/adaptive-sampling-in-jaeger-50f336f4334
Adaptive Sampling in Jaeger
Introduction to the new adaptive sampling engine released in Jaeger v1.27
medium.com
https://www.jaegertracing.io/docs/2.0/storage/elasticsearch/#:~:text=To%20enable%20ILM%20support%3A
ElasticSearch
While initializing with ILM support, make sure that an ILM policy named jaeger-ilm-policy is created in Elasticsearch beforehand (see the previous step), otherwise the following error message will be shown: “ILM policy jaeger-ilm-policy doesn’t exist i
www.jaegertracing.io
'MSA' 카테고리의 다른 글
[Spring] MSA 서킷 브레이커 적용기 (2) | 2025.02.15 |
---|---|
MSA 서버 간 통신: SNS의 MessageAttributes로 완벽한 Zeropayload 전략 구현하기 (1) | 2023.12.01 |
MSA 환경에서 SNS 메시지 재발행을 위한 스프링 배치 및 스케쥴러 구현 (1) | 2023.11.28 |
Spring MSA 프로젝트에서 단일 책임 원칙을 지키기 위한 리팩토링 (3) | 2023.11.24 |
Spring MSA: Sampling으로 원하는 http요청만 Zipkin으로 추적하기 (0) | 2023.11.23 |