분산 트레이싱, 지금 우리 서비스에 다 붙여도 될까?
결론부터 말하면 "다 붙인다"는 선택은 대부분 과하다. 분산 트레이싱은 장애를 없애는 도구가 아니라 장애가 났을 때 어디서 끊겼는지를 빨리 좁혀주는 도구이고, 그 대가로 지연·CPU·메모리·네트워크 비용을 항상 지불한다. 판교에서 몇 개 서비스에 붙여본 경험으로는 "핵심 경로 우선 + 샘플링 + 배칭"으로 시작하는 편이 실측 수치와도 맞아떨어졌다.
이 글의 핵심
- OpenTelemetry를 켠 Go 서비스 실측에서 CPU +35%, 메모리 +5~8MB, p99 지연 10ms→15ms, 네트워크 약 4MB/s가 관찰됐다[1].
- 다른 재현 벤치마크에서는 JSON 엔드포인트 중앙값 지연이 0.62ms→0.74ms(약 19%↑), update 엔드포인트는 OTel 약 16.5%↑, 특정 APM 도구는 약 42%↑까지 나왔다[2].
- "전면 계측"보다 "핵심 경로 + 샘플링 + 배칭"으로 시작해 장애 빈도가 높은 경로부터 촘촘히 확장하는 편이 오버헤드 대비 이득이 컸다.
- 트레이스는 로그·메트릭과 함께 써야 완성된다. 메트릭은 빈도, 로그는 사건 내용, 트레이스는 끊긴 위치를 답한다[13].
- 재시도·서킷 브레이커 같은 회복 패턴과 트레이스를 같이 설계하면, 타임아웃 대기 대신 즉시 실패한 경로가 트레이스에 남아 원인 파악이 빨라진다[13].
이 판단은 하나의 질문에서 갈린다. "이 요청 경로에서 부분 실패를 몇 초 안에 찾아내야 하는가, 아니면 지금은 장애 빈도 자체가 낮아 오버헤드가 아깝나?" 이 축을 먼저 세우고 나머지를 결정하는 순서가 실무에서 덜 후회했다.
트레이싱을 켜면 지연·CPU가 실제로 얼마나 늘어나나?
늘어난다는 방향은 분명하지만 정도는 조건에 따라 크게 갈린다. Docker/Linux 환경의 Go 서비스 실측에서는 OpenTelemetry를 켰을 때 CPU 사용량이 약 35% 늘고, 메모리는 5~8MB 추가되며, p99 지연은 10ms에서 15ms로 올라갔다. 같은 조건에서 외부로 나가는 네트워크 트래픽도 약 4MB/s가 새로 발생했다[1]. 2코어 인스턴스를 쓰던 서비스라면 이 정도 CPU 증가는 체감상 2코어를 거의 2.7코어처럼 쓰는 셈이라, 오토스케일링 임계값을 다시 잡아야 하는 경우도 있었다.
다른 재현 벤치마크는 엔드포인트 단위로 더 세밀한 그림을 보여준다. JSON 응답 엔드포인트는 중앙값 기준 0.62ms에서 0.74ms로 약 19% 증가했고, 상태를 변경하는 update 엔드포인트는 OpenTelemetry에서 약 16.5%, 비교 대상이었던 Elastic APM에서는 약 42%까지 증가했다[2]. 여기서 중요한 건 "트레이싱 도구가 다르면 오버헤드도 다르다"는 점과, "상태 변경 요청일수록 계측 비용이 더 크게 보인다"는 경향이다. 다만 이 수치는 특정 벤치마크 환경의 결과이므로, 우리 서비스의 프레임워크·인스턴스 사양·트래픽 패턴에 그대로 대입하기보다는 "같은 방향의 증가가 있을 수 있다"는 참고선으로 쓰는 게 안전하다.
전면 계측과 핵심 경로 계측, 어디서부터 시작해야 하나?
핵심 경로부터 시작하는 편이 오버헤드 대비 얻는 정보가 많았다. 모든 엔드포인트에 span을 다 붙이면 위에서 본 CPU·지연 증가가 서비스 전체에 균일하게 걸리는데, 정작 장애가 자주 나는 경로는 결제·인증·외부 API 연동처럼 일부에 몰려 있는 경우가 많다. 실사용 흐름에서 자주 보이는 순서는 이렇다.
- 장애 빈도·영향도가 큰 경로(결제, 로그인, 외부 연동)에 먼저 span을 촘촘히 붙인다.
- 나머지 경로는 헤더 기반 샘플링(예: 1~10%)으로 낮춰 오버헤드를 줄인다.
- 배치 export와 비동기 전송을 기본값으로 두어 요청 스레드가 트레이스 전송을 기다리지 않게 한다.
- 장애가 새로 관찰되면 그 경로의 샘플링률을 올리고, 안정화되면 다시 낮춘다.
이 방식은 "전면 tracing"보다 "핵심 경로만 추적 + 샘플링 + 배칭"으로 단계적으로 확장하는 구성이 최근 실측 경향과도 맞는다는 점과 연결된다[1][6][8]. 다만 샘플링을 너무 낮게 잡으면 정작 드물게 발생하는 장애의 트레이스가 표본에서 빠질 수 있으니, 에러율이 높은 요청은 항상 100% 샘플링하는 규칙(에러 우선 샘플링)을 같이 두는 편이 실무에서 덜 답답했다.
재시도·서킷 브레이커와 트레이스는 어떻게 맞물리나?
트레이스만 있으면 "어디서 느려졌는지"는 보이지만 "왜 계속 느려지는지"까지 막아주진 않는다. 그래서 회복 패턴을 같이 설계해야 한다. 재시도는 실패가 일시적이고 요청이 멱등일 때는 효과적이지만, 상태를 바꾸는 요청이나 다운스트림이 지속적으로 느릴 때는 재시도가 오히려 부하를 키워 상황을 악화시킬 수 있다[9]. 실무에서는 "이 요청이 멱등인가"를 먼저 확인하고 나서 재시도 정책을 넣는 순서를 지켰다.
서킷 브레이커는 실패가 누적되면 즉시 거절로 전환해 연쇄 장애를 막는 역할을 한다. 이때 트레이스에는 타임아웃을 끝까지 기다린 긴 span 대신, 즉시 실패로 짧게 끊긴 경로가 남는다. 덕분에 "타임아웃 30초를 다 채우고 실패한 요청 100개"를 뒤지는 대신, "서킷이 열린 시점부터 즉시 실패한 요청들"을 트레이스에서 바로 구분할 수 있었다[13]. 타임아웃·재시도·서킷 브레이커를 폭발을 막는 안전장치로 함께 두고, 그 결과를 트레이스로 확인하는 조합이 관측 삼각형(로그·메트릭·트레이스)의 취지에도 맞는다[13].
애플리케이션 계측과 수집기 성능, 왜 따로 봐야 하나?
계측 코드가 가볍다고 해서 수집 파이프라인까지 가볍다는 보장은 없기 때문이다. OpenTelemetry 공식 문서는 API의 동적 메모리 사용량과 CPU 사용량을 별도로 측정해 추세를 추적하도록 안내하고 있고, Collector 쪽도 별도의 벤치마크 자료를 공식적으로 제공한다[6][8]. 즉 "애플리케이션에서 span을 만드는 비용"과 "Collector가 그 span을 받아서 배치·필터링·export하는 비용"은 서로 다른 병목 지점이라는 뜻이다.
실무에서는 애플리케이션 쪽 오버헤드(위에서 본 CPU·지연 증가)만 보고 안심했다가, 트래픽이 몰리는 시간대에 Collector 인스턴스의 큐가 쌓이면서 export 지연이 늘어나는 경우를 본 적이 있다. 이럴 때는 애플리케이션 계측 설정(샘플링률, 배치 크기)과 Collector 리소스(CPU·메모리 할당, 파이프라인 수)를 각각 따로 모니터링하고, 필요하면 Collector를 별도로 스케일아웃하는 판단이 필요했다. OpenTelemetry Collector 벤치마크 문서는 이런 수집 계층 성능 검증의 출발점으로 참고할 만하다.
이 선택에서 자주 놓치는 함정은 무엇인가?
가장 흔한 함정은 "오버헤드는 작으니 일단 전부 켜자"는 접근이다. 위 수치들은 특정 Go 서비스, Docker/리눅스 호스트, 특정 벤치마크 도구라는 조건이 붙은 값이라, 우리 서비스에 그대로 적용되지 않을 수 있다[1][2]. 프레임워크가 다르거나 요청당 span 수가 많은 서비스라면 CPU·지연 증가폭이 더 크게 나타날 수도, 더 작게 나타날 수도 있다. 그래서 "붙이기 전에 스테이징 환경에서 같은 트래픽 패턴으로 p99·CPU·메모리를 먼저 재보는 것"이 안전하다.
두 번째 함정은 회복 패턴 없이 트레이스만 넣는 경우다. 트레이스는 관찰 신호일 뿐 자동으로 장애를 막아주지 않는다. 서킷 브레이커나 타임아웃 정책이 없는 상태에서 트레이스만 늘리면, "어디서 끊겼는지는 보이는데 계속 끊긴다"는 상황이 반복된다. 마이크로서비스 회복 패턴 문서에서 다루는 것처럼 서킷 브레이커·타임아웃과 트레이스를 같이 설계해야 관측과 회복이 맞물린다. 마지막 함정은 샘플링을 낮춘 뒤 에러 우선 샘플링 규칙을 빼먹는 것으로, 이 경우 드문 장애의 트레이스가 표본에서 통째로 빠질 수 있다.
그래서 실무에서는 언제 무엇을 고르나?
트래픽이 적고 장애가 드문 초기 서비스라면 트레이싱 전면 도입보다 로그·메트릭을 먼저 촘촘히 하고, 결제·인증처럼 영향도 큰 경로 한두 곳에만 트레이싱을 붙이는 편이 오버헤드 대비 합리적이었다. 반대로 마이크로서비스 수가 늘고 요청 경로가 3~4홉을 넘어가는 시점에는, CPU·지연 증가를 감수하더라도 핵심 경로 트레이싱을 넣는 쪽이 장애 원인 파악 시간을 줄여줬다.
- 초기/소규모: 로그+메트릭 우선, 트레이싱은 결제·인증 등 핵심 1~2경로만
- 중간 규모, 홉 수 증가: 핵심 경로 트레이싱 + 저비율 샘플링 + 에러 우선 100% 샘플링
- 장애 빈발 경로: 서킷 브레이커·타임아웃과 함께 촘촘한 트레이싱 적용
- 트래픽 급증 대비: 애플리케이션 오버헤드와 Collector 처리량을 분리해서 모니터링
2026년 기준으로도 이 판단 축 자체는 크게 달라지지 않을 것으로 본다. 도구별 정확한 오버헤드 수치는 버전이 바뀔 때마다 달라질 수 있으니, 새 버전을 도입할 때는 항상 자체 환경에서 다시 재보는 습관이 필요하다.
핵심 정리
- 분산 트레이싱은 장애를 없애는 도구가 아니라 부분 실패 위치를 빨리 좁히는 도구이며, CPU·지연·메모리·네트워크 오버헤드를 항상 동반한다.
- 실측 조건에서 CPU +35%, 메모리 +5~8MB, p99 10ms→15ms, 네트워크 약 4MB/s 증가가 관찰됐고, 엔드포인트별로는 지연이 16~42%까지 늘어난 사례도 있다[1][2].
- "전면 계측"보다 "핵심 경로 + 샘플링 + 배칭"으로 시작해 장애 빈발 경로부터 확장하는 방식이 오버헤드 대비 실익이 컸다.
- 재시도·서킷 브레이커 같은 회복 패턴과 트레이스를 함께 설계해야 "어디서 끊겼는지"와 "왜 계속 끊기는지"를 같이 다룰 수 있다.
- 애플리케이션 계측 오버헤드와 Collector/수집 파이프라인 성능은 서로 다른 병목이므로 따로 모니터링해야 한다.