IT/모니터링

Tempo vs Jaeger v2, 결국 우리가 Tempo로 옮긴 이유

gfrog 2026. 6. 8. 12:42
SMALL

들어가며

작년 말부터 트레이싱 백엔드를 갈아엎자는 얘기가 팀 내부에서 계속 나왔다. 직접적인 계기는 Jaeger v1이 2025년 12월 31일부로 EOL을 맞은 것이었다. 어차피 손을 대야 한다면 Jaeger v2로 갈지, Tempo로 옮길지 둘 중 하나를 골라야 했다. 결론부터 말하면 우리는 Tempo로 갔다. 그런데 솔직히 말해서 이 결정이 모든 팀에 정답은 아니다.

두 솔루션이 지금 어디쯤 와 있는지

Jaeger v2는 2024년 11월에 정식 릴리즈됐는데, 기존 코드베이스를 거의 다 버리고 OpenTelemetry Collector 위에 다시 쌓아 올린 구조다. 그러니까 Jaeger v2는 사실 "Jaeger 구성요소가 추가된 OTel Collector distribution"에 가깝다. 최근 2.19까지 나왔고 활발히 업데이트되고 있다.

Tempo는 2.10이 올해 1월에 풀렸다. TraceQL에 span:childCount 같은 intrinsic이 추가됐고 = nil 연산자도 지원하기 시작했다. 그 전 2.9에서는 probabilistic sampling hint(with(sample=0.1))가 들어와서 카디널리티 높은 트레이스를 조회할 때 쿼리 속도를 의도적으로 깎아 쓸 수 있게 됐다. 이게 우리한테는 꽤 컸다.

운영 부담을 먼저 비교했다

처음에는 기능 비교부터 했는데, 한참 보다 보니 그게 핵심이 아니라는 걸 깨달았다. 우리 팀은 SRE 인원이 셋이다. 트레이싱 백엔드 하나에 매주 8시간씩 쓸 여유가 없다.

Jaeger v2는 Collector 기반이라 파이프라인 설정이 OTel 컨벤션을 그대로 따른다. 이미 OTel Collector를 메트릭과 로그 쪽에서 쓰고 있다면 학습 비용이 사실상 0이다. 우리도 그렇긴 했다. 다만 스토리지 백엔드를 Cassandra나 Elasticsearch에 묶어두면 Cassandra 클러스터 운영 부담이 그대로 따라온다. 우리는 작년에 Cassandra 노드 하나가 갑자기 GC 폭주하면서 트레이스가 한 시간 가까이 안 들어온 적이 있다. 그 트라우마가 생각보다 컸다.

Tempo는 S3(또는 GCS)만 있으면 끝난다. 컴팩터, 인제스터, 쿼리어, 디스트리뷰터로 나뉘는 마이크로서비스 구조는 처음 볼 때 좀 부담스럽지만, 결국 stateless 컴포넌트가 대부분이라 오토스케일이 자연스럽다. 그리고 2.8 릴리즈에서 컴팩터 메모리가 50% 이상 줄었다는 발표가 있었는데, 우리 환경(하루 약 80GB 트레이스)에서 측정해보니 실제로 30~40% 정도 떨어졌다. 발표 수치만큼은 아니어도 체감되는 수준이었다.

TraceQL이 결정적이었다

기능 차이 중에서 우리 팀 의사결정에 가장 크게 작용한 건 TraceQL이다.

Jaeger UI의 검색은 여전히 service, operation, tag 필터 위주다. v2에서도 백엔드 쿼리 모델은 크게 안 바뀌었다. 반면 TraceQL은 SQL 같은 느낌으로 span 단위 조건을 조합할 수 있다. 예를 들어 "DB 호출 span 중에 duration이 500ms 넘는 게 2개 이상 들어있는 트레이스만" 같은 질문을 한 줄로 쓴다.

{ resource.service.name = "checkout" } 
  >> { span.db.system = "postgresql" && duration > 500ms } 
  | count() > 1

이런 쿼리는 실제 장애 분석할 때 꽤 자주 쓴다. Jaeger UI에서 같은 걸 찾으려면 사람이 눈으로 트레이스를 까봐야 한다. 트레이스가 만 개 넘어가면 그게 불가능해진다.

여기에 2.10의 span:childCount가 더해지니까, "fan-out 폭발한 트레이스만" 같은 쿼리도 가능해졌다. 우리는 GraphQL 리졸버 N+1 문제를 잡을 때 이걸 자주 쓴다.

{ name = "GraphQL.Query" && span:childCount > 50 }

그래도 Jaeger가 나았던 부분

이렇게 쓰니까 Tempo가 일방적으로 나은 것 같지만 그렇지는 않다.

UI 자체는 Jaeger UI가 더 손에 익다. Grafana의 트레이스 뷰는 익숙해지면 괜찮은데, 처음 보는 개발자한테 "여기 가서 이 트레이스 한 번 봐줘"라고 했을 때 진입 장벽이 좀 있다. Jaeger UI는 그냥 들어가서 검색하면 끝난다.

그리고 어댑티브 샘플링은 Jaeger가 아직 더 성숙하다. Jaeger는 클라이언트로 샘플링 결정을 푸시해주는 메커니즘이 잘 다듬어져 있는 반면, Tempo 자체는 샘플링을 담당하지 않는다. OTel Collector의 tail sampling processor를 따로 붙여서 처리해야 한다. 이게 설정이 까다롭다.

또 하나, 만약 트레이스를 메인 쿼리 인터페이스 없이 그냥 저장만 해두려는 팀이라면 Jaeger의 단순함이 더 매력적일 수 있다. Tempo의 메트릭 제너레이터, vParquet5 같은 기능들은 안 쓰면 그냥 복잡도만 늘어난다.

우리는 왜 결국 Tempo였나

세 가지가 컸다.

첫째, 이미 Grafana 스택을 쓰고 있다. Loki, Mimir, Grafana 대시보드까지 다 깔려있는 상황에서 트레이스만 Jaeger UI를 따로 쓰는 건 컨텍스트 스위칭 비용이 너무 크다. 로그에서 트레이스로 점프하는 워크플로우가 한 화면에서 끝나는 게 생각보다 큰 차이다.

둘째, 스토리지를 S3로 통일하고 싶었다. Mimir도 S3를 쓰고 Loki도 S3를 쓰는데 Jaeger만 Cassandra/ES를 들고 있는 건 운영자 입장에서 비효율이다.

셋째, TraceQL의 쿼리 표현력이 우리 팀 디버깅 패턴에 맞았다. 이건 결국 취향이긴 한데, 한번 익숙해지면 돌아가기 어렵다.

다만 다시 강조하자면, Cassandra/ES를 이미 잘 운영하고 있고 어댑티브 샘플링이 필수인 환경이라면 Jaeger v2가 더 맞을 수도 있다. Jaeger v2가 OTel Collector 기반으로 바뀌면서 확장성도 많이 좋아진 상태다. EOL이라고 v1을 급하게 버릴 필요는 없지만, 어쨌든 어딘가로는 옮겨야 한다는 것만 분명하다.

마이그레이션 자체는 한 달 좀 안 걸렸다. 듀얼 라이팅으로 한 2주, 검증 일주일, 컷오버 며칠. 혹시 비슷한 고민 중이신 분 있으면 댓글로 환경 공유해주시면 더 구체적으로 얘기 나눠볼 수 있을 것 같다.

BIG