🤖

본 콘텐츠의 이미지 및 내용은 AI로 생성되었습니다.

⚠️

본 콘텐츠의 이미지 및 내용을 무단으로 복제, 배포, 수정하여 사용할 경우 저작권법에 의해 법적 제재를 받을 수 있습니다.

이미지 로딩 중...

Zipkin으로 추적 시각화 완벽 가이드 - 슬라이드 1/7
A

AI Generated

2025. 12. 22. · 4 Views

Zipkin으로 추적 시각화 완벽 가이드

마이크로서비스 환경에서 분산 추적을 시각화하는 Zipkin의 핵심 개념과 활용 방법을 초급자도 쉽게 이해할 수 있도록 실무 스토리로 풀어낸 가이드입니다. Docker 실행부터 UI 분석까지 단계별로 배웁니다.


목차

  1. Zipkin_소개
  2. Docker로_Zipkin_실행
  3. 추적_데이터_전송
  4. Zipkin_UI_사용
  5. 서비스_의존성_그래프
  6. 지연_분석

1. Zipkin 소개

어느 날 김개발 씨는 회사의 주문 시스템에서 이상한 문제를 발견했습니다. 사용자가 주문 버튼을 누르면 가끔 10초 넘게 걸리는 거예요.

하지만 어느 서비스에서 지연이 발생하는지 도통 알 수가 없었습니다.

Zipkin은 마이크로서비스 환경에서 요청이 어떤 경로로 흘러가는지 추적하고 시각화하는 분산 추적 시스템입니다. 마치 택배가 여러 물류 센터를 거쳐가는 과정을 실시간으로 확인하는 것과 같습니다.

각 서비스에서 얼마나 시간이 걸렸는지, 어디서 문제가 발생했는지 한눈에 파악할 수 있습니다.

다음 코드를 살펴봅시다.

// Spring Boot 프로젝트에 Zipkin 의존성 추가
// build.gradle 또는 pom.xml에 추가
dependencies {
    implementation 'org.springframework.cloud:spring-cloud-starter-zipkin:2.2.8.RELEASE'
    implementation 'org.springframework.cloud:spring-cloud-starter-sleuth'
}

// application.yml 설정
spring:
  zipkin:
    base-url: http://localhost:9411
  sleuth:
    sampler:
      probability: 1.0  // 모든 요청 추적 (개발 환경)

김개발 씨는 입사 6개월 차 백엔드 개발자입니다. 회사는 최근 모놀리식 아키텍처에서 마이크로서비스로 전환했습니다.

주문 시스템 하나만 해도 주문 서비스, 결제 서비스, 재고 서비스, 배송 서비스 등 여러 개로 쪼개졌습니다. 어느 날 고객 지원팀에서 긴급 문의가 들어왔습니다.

"주문이 너무 느려요!" 김개발 씨는 로그를 확인했지만, 각 서비스의 로그가 따로따로 흩어져 있어서 전체 흐름을 파악하기가 어려웠습니다. 그때 선배 개발자 박시니어 씨가 다가왔습니다.

"Zipkin을 사용해 보셨나요?" 분산 추적이란 무엇일까요? 쉽게 비유하자면, 분산 추적은 마치 택배 추적 시스템과 같습니다. 여러분이 인터넷 쇼핑몰에서 물건을 주문하면, 택배가 출발지 → 집하장 → 물류센터 → 배송센터 → 목적지까지 여러 단계를 거쳐갑니다.

각 단계마다 언제 도착했고 얼마나 머물렀는지 기록되죠. 마이크로서비스도 똑같습니다.

사용자의 요청이 여러 서비스를 거쳐가면서 처리됩니다. 이 과정을 하나하나 기록하고 추적하는 것이 바로 분산 추적입니다.

왜 Zipkin이 필요한가? 마이크로서비스가 없던 시절, 즉 모놀리식 아키텍처에서는 모든 로직이 한 곳에 있었습니다. 디버거를 붙이거나 로그를 보면 쉽게 문제를 찾을 수 있었습니다.

코드의 흐름이 한눈에 보였으니까요. 하지만 마이크로서비스 환경에서는 상황이 달라집니다.

하나의 요청이 5개, 10개, 때로는 20개가 넘는 서비스를 거쳐갑니다. 각 서비스는 다른 팀에서 관리하고, 다른 언어로 작성되기도 합니다.

로그도 각자 따로 저장됩니다. 김개발 씨가 겪었던 문제가 바로 이것이었습니다.

주문이 느린 건 알겠는데, 주문 서비스가 느린 건지, 결제 서비스가 느린 건지, 아니면 재고 확인이 문제인지 알 수가 없었던 거죠. Zipkin의 등장 바로 이런 문제를 해결하기 위해 Zipkin이 등장했습니다.

원래 Twitter에서 개발했고, 지금은 오픈소스로 공개되어 많은 회사에서 사용하고 있습니다. Zipkin을 사용하면 요청 하나하나에 추적 ID가 부여됩니다.

마치 택배 송장번호처럼 말이죠. 이 ID를 따라가면 요청이 어떤 서비스들을 거쳐갔는지, 각 서비스에서 얼마나 시간이 걸렸는지 모두 확인할 수 있습니다.

핵심 개념: Trace와 Span Zipkin을 이해하려면 두 가지 핵심 개념을 알아야 합니다. Trace는 하나의 전체 요청을 의미합니다.

사용자가 주문 버튼을 누른 순간부터 주문이 완료될 때까지의 전체 여정이 하나의 Trace입니다. Span은 그 여정의 각 단계입니다.

주문 서비스에서 처리한 부분이 하나의 Span, 결제 서비스에서 처리한 부분이 또 다른 Span입니다. 하나의 Trace는 여러 개의 Span으로 구성됩니다.

Spring Cloud Sleuth와의 조합 위의 코드를 보면 Zipkin만 추가한 게 아니라 Sleuth도 함께 추가했습니다. Sleuth는 자동으로 추적 ID를 생성하고 관리해주는 라이브러리입니다.

여러분이 별도의 코드를 작성하지 않아도, Sleuth가 알아서 HTTP 요청에 추적 정보를 심어줍니다. 그리고 Zipkin은 그 정보를 수집해서 시각화해줍니다.

완벽한 콤비네이션이죠. 샘플링 확률 위 설정에서 probability: 1.0이 보이시나요?

이것은 샘플링 확률입니다. 1.0은 모든 요청을 추적한다는 의미입니다.

실제 운영 환경에서는 요청량이 엄청나게 많습니다. 모든 요청을 다 추적하면 성능에 영향을 줄 수 있습니다.

그래서 보통 0.1(10%) 정도로 설정합니다. 개발 환경에서는 모든 요청을 추적하는 게 디버깅에 유리하니까 1.0으로 설정하는 겁니다.

김개발 씨의 깨달음 박시니어 씨의 설명을 들은 김개발 씨는 고개를 끄덕였습니다. "그러니까 Zipkin을 쓰면 주문이 어디서 느려지는지 바로 알 수 있겠네요!" 맞습니다.

Zipkin을 제대로 설정하면 분산 환경에서도 요청의 흐름을 한눈에 파악할 수 있습니다. 디버깅 시간이 획기적으로 줄어들고, 성능 병목 지점도 쉽게 찾을 수 있습니다.

실전 팁

💡 - 개발 환경에서는 샘플링 확률을 1.0으로, 운영 환경에서는 0.1 정도로 설정하세요

  • Zipkin은 Twitter가 만든 오픈소스이며, Jaeger, SkyWalking 등 다른 대안도 있습니다

2. Docker로 Zipkin 실행

김개발 씨는 Zipkin을 직접 사용해보기로 했습니다. 하지만 설치 과정이 복잡할까봐 걱정이 되었습니다.

선배가 웃으며 말했습니다. "Docker 쓰면 한 줄이면 됩니다."

Docker를 사용하면 Zipkin 서버를 복잡한 설치 과정 없이 단 몇 초 만에 실행할 수 있습니다. 마치 일회용 컵라면처럼, 필요할 때 바로 꺼내서 쓰고 필요 없으면 바로 치울 수 있습니다.

로컬 개발 환경에서 테스트하기에 완벽한 방법입니다.

다음 코드를 살펴봅시다.

# Zipkin 서버를 Docker로 실행
# 9411 포트로 접속 가능
docker run -d -p 9411:9411 openzipkin/zipkin

# 메모리 스토리지 대신 Elasticsearch 사용하기
docker run -d -p 9411:9411 \
  -e STORAGE_TYPE=elasticsearch \
  -e ES_HOSTS=http://elasticsearch:9200 \
  openzipkin/zipkin

# Docker Compose로 한 번에 실행
# docker-compose.yml 파일 예제
version: '3.8'
services:
  zipkin:
    image: openzipkin/zipkin
    ports:
      - "9411:9411"

김개발 씨는 개발자 노트북에 Docker가 이미 설치되어 있었습니다. 요즘 대부분의 개발자가 Docker를 사용하니까요.

박시니어 씨가 터미널을 열고 명령어를 입력했습니다. 타닥타닥, 엔터 한 번.

그러자 Docker가 Zipkin 이미지를 다운로드하기 시작했습니다. 몇 초 후 "Zipkin 서버가 실행되었습니다"라는 메시지가 떴습니다.

"이게 끝이에요?" 김개발 씨가 놀라서 물었습니다. Docker가 뭐길래? Docker는 컨테이너 기술을 사용해서 애플리케이션을 격리된 환경에서 실행하는 도구입니다.

쉽게 비유하자면, Docker는 마치 이사 갈 때 쓰는 이삿짐 박스와 같습니다. 집을 옮길 때 물건을 박스에 담으면, 어디서든 똑같이 꺼내서 쓸 수 있죠.

Docker도 마찬가지입니다. 애플리케이션을 컨테이너라는 박스에 담으면, 어떤 컴퓨터에서든 똑같이 실행됩니다.

왜 Docker를 사용할까? 전통적인 방식으로 Zipkin을 설치하려면 여러 단계를 거쳐야 합니다. Java를 설치하고, Zipkin 바이너리를 다운로드하고, 환경 변수를 설정하고...

생각만 해도 복잡합니다. 더 큰 문제는 개발자마다 환경이 다르다는 겁니다.

어떤 개발자는 Mac을 쓰고, 어떤 개발자는 Windows를 씁니다. Java 버전도 제각각입니다.

이렇게 되면 "제 컴퓨터에서는 잘 되는데요" 문제가 발생합니다. Docker를 사용하면 이런 문제가 사라집니다.

Docker 이미지 안에 필요한 모든 것이 다 들어있으니까요. Java도, Zipkin도, 설정 파일도 전부 포함되어 있습니다.

첫 번째 명령어 분석 첫 번째 명령어를 자세히 살펴보겠습니다. docker run은 컨테이너를 실행하라는 명령입니다.

-d 옵션은 백그라운드에서 실행하라는 의미입니다. 터미널을 닫아도 계속 돌아가게 하는 거죠.

-p 9411:9411은 포트 매핑입니다. 컨테이너 내부의 9411 포트를 내 컴퓨터의 9411 포트로 연결한다는 뜻입니다.

마치 전화 교환원이 전화를 연결해주는 것처럼, Docker가 네트워크 요청을 컨테이너로 연결해줍니다. openzipkin/zipkin은 사용할 이미지 이름입니다.

Docker Hub라는 곳에 공개되어 있는 공식 Zipkin 이미지를 가져옵니다. 저장소 옵션 기본적으로 Zipkin은 메모리에 데이터를 저장합니다.

빠르고 간편하지만, 서버를 재시작하면 모든 데이터가 사라집니다. 개발 환경에서 테스트할 때는 괜찮지만, 운영 환경에서는 문제가 됩니다.

두 번째 명령어를 보면 환경 변수를 추가했습니다. STORAGE_TYPE=elasticsearch는 Elasticsearch에 데이터를 저장하라는 의미입니다.

Elasticsearch를 사용하면 데이터가 영구적으로 보관되고, 강력한 검색 기능도 사용할 수 있습니다. 물론 MySQL, Cassandra 같은 다른 저장소도 사용할 수 있습니다.

회사의 인프라에 맞춰서 선택하면 됩니다. Docker Compose로 더 편하게 세 번째 예제는 Docker Compose를 사용했습니다.

Docker Compose는 여러 컨테이너를 한 번에 관리하는 도구입니다. YAML 파일에 설정을 작성해두면, docker-compose up 명령어 하나로 모든 서비스를 실행할 수 있습니다.

Zipkin뿐만 아니라 Elasticsearch, MySQL 등을 함께 실행해야 할 때 매우 유용합니다. 실제 실행 확인 명령어를 실행한 후 브라우저를 열어서 http://localhost:9411로 접속해보세요.

Zipkin의 깔끔한 UI가 나타납니다. 아직 데이터가 없어서 비어있을 테지만, 서버가 정상적으로 실행된 겁니다.

주의사항 Docker를 사용할 때 주의할 점이 있습니다. Docker 컨테이너는 기본적으로 격리되어 있습니다.

컨테이너 안에서 실행되는 Zipkin은 외부 네트워크를 바로 접근하지 못합니다. 만약 여러분의 애플리케이션도 Docker로 실행 중이라면, 같은 Docker 네트워크에 있어야 서로 통신할 수 있습니다.

이때는 Docker Compose를 사용하는 게 좋습니다. 김개발 씨의 실행 김개발 씨는 직접 명령어를 입력해봤습니다.

정말로 몇 초 만에 Zipkin이 실행되었습니다. "생각보다 너무 쉬운데요?" 박시니어 씨가 웃으며 대답했습니다.

"그렇죠. Docker 덕분에 개발 환경 구축이 정말 쉬워졌어요.

이제 우리 애플리케이션에서 추적 데이터를 전송해봅시다."

실전 팁

💡 - 개발할 때는 메모리 저장소로 충분하지만, 운영에서는 Elasticsearch나 MySQL 사용을 권장합니다

  • Docker Desktop을 사용하면 Windows와 Mac에서도 쉽게 Docker를 사용할 수 있습니다

3. 추적 데이터 전송

Zipkin 서버가 실행되었지만 화면은 여전히 비어있었습니다. 김개발 씨가 물었습니다.

"데이터가 왜 안 나타나요?" 박시니어 씨가 대답했습니다. "아직 애플리케이션에서 데이터를 보내지 않았으니까요."

애플리케이션에서 Zipkin으로 추적 데이터를 전송하려면 각 서비스에서 Span 정보를 생성하고 Zipkin 서버로 보내야 합니다. Spring Boot에서는 Sleuth가 자동으로 이 작업을 처리해주지만, 커스텀 Span을 추가하여 더 상세한 추적도 가능합니다.

다음 코드를 살펴봅시다.

// Spring Boot 컨트롤러에서 자동 추적
@RestController
@RequestMapping("/orders")
public class OrderController {

    @Autowired
    private Tracer tracer;  // Sleuth의 Tracer 주입

    @PostMapping
    public Order createOrder(@RequestBody OrderRequest request) {
        // 자동으로 Span이 생성됨

        // 커스텀 Span 추가하기
        Span customSpan = tracer.nextSpan().name("validate-order");
        try (Tracer.SpanInScope ws = tracer.withSpan(customSpan.start())) {
            // 검증 로직
            validateOrder(request);
            customSpan.tag("order.amount", request.getAmount().toString());
        } finally {
            customSpan.end();  // Span 종료 및 전송
        }

        return orderService.create(request);
    }
}

김개발 씨는 자신이 담당하는 주문 서비스 코드를 열었습니다. 앞에서 이미 Sleuth와 Zipkin 의존성을 추가했으니, 추가 설정만 하면 될 것 같았습니다.

박시니어 씨가 코드를 가리키며 설명을 시작했습니다. "사실 특별히 할 게 없어요.

Sleuth가 대부분 자동으로 처리해줍니다." 자동 추적의 마법 Spring Cloud Sleuth는 정말 똑똑합니다. 여러분이 아무 코드도 작성하지 않아도, HTTP 요청이 들어올 때마다 자동으로 Span을 생성합니다.

마치 CCTV가 알아서 녹화하는 것처럼, Sleuth는 알아서 요청을 추적합니다. 컨트롤러에 들어갈 때, 서비스 메서드를 호출할 때, 데이터베이스 쿼리를 실행할 때, 다른 서비스를 호출할 때...

모든 순간을 기록합니다. 어떻게 작동할까? Sleuth는 Spring AOP를 활용합니다.

AOP는 관점 지향 프로그래밍의 약자로, 쉽게 말하면 메서드 실행 전후에 자동으로 특정 코드를 실행하는 기술입니다. 예를 들어 컨트롤러 메서드가 호출되면, Sleuth가 먼저 가로채서 Span을 시작합니다.

메서드가 끝나면 다시 가로채서 Span을 종료하고 Zipkin으로 전송합니다. 개발자는 전혀 신경 쓸 필요가 없습니다.

추적 ID의 전파 더 신기한 건 추적 ID가 자동으로 전파된다는 겁니다. 주문 서비스에서 결제 서비스를 호출할 때, Sleuth가 HTTP 헤더에 추적 정보를 자동으로 심어줍니다.

헤더 이름은 X-B3-TraceId, X-B3-SpanId 같은 형식입니다. B3는 Zipkin의 추적 형식 이름입니다.

결제 서비스에서도 Sleuth가 실행 중이라면, 이 헤더를 읽어서 같은 Trace에 Span을 추가합니다. 결과적으로 주문부터 결제까지의 전체 흐름이 하나의 Trace로 연결됩니다.

개발자는 손가락 하나 까딱하지 않아도 됩니다. 커스텀 Span은 언제? 그렇다면 위 코드처럼 직접 Span을 만드는 건 언제 필요할까요?

자동 추적은 HTTP 요청, 데이터베이스 쿼리 같은 일반적인 작업을 커버합니다. 하지만 여러분만의 비즈니스 로직은 알아서 추적해주지 못합니다.

예를 들어 주문 검증 로직이 복잡해서 시간이 오래 걸린다면, 그 부분만 따로 Span으로 만들면 좋습니다. 나중에 Zipkin UI에서 "아, 검증에서 3초나 걸렸구나" 하고 바로 알 수 있으니까요.

코드 상세 분석 위 코드를 단계별로 살펴보겠습니다. 먼저 Tracer를 주입받습니다.

Tracer는 Span을 생성하고 관리하는 핵심 객체입니다. tracer.nextSpan().name("validate-order")는 새로운 Span을 만듭니다.

이름은 "validate-order"로 지정했습니다. Zipkin UI에서 이 이름으로 표시됩니다.

tracer.withSpan(customSpan.start())는 Span을 시작하고, 현재 실행 컨텍스트에 연결합니다. try-with-resources 구문을 사용해서 자동으로 정리되게 했습니다.

customSpan.tag("order.amount", ...)는 태그를 추가합니다. 태그는 추가 정보를 기록하는 용도입니다.

주문 금액, 사용자 ID 같은 비즈니스 데이터를 넣으면 나중에 필터링할 때 유용합니다. 마지막으로 customSpan.end()로 Span을 종료합니다.

이때 시작 시간과 종료 시간 차이를 계산해서 소요 시간이 기록됩니다. 비동기 처리 주의사항 만약 비동기로 작업을 처리한다면 조금 신경 써야 합니다.

예를 들어 @Async 메서드나 별도 스레드를 사용하면, 추적 컨텍스트가 자동으로 전파되지 않습니다. 이때는 Tracer를 사용해서 수동으로 Span을 전달해야 합니다.

Sleuth 문서에 자세한 예제가 나와있으니 참고하세요. 실제 전송 확인 김개발 씨가 애플리케이션을 재시작하고 주문 API를 호출해봤습니다.

그리고 Zipkin UI를 새로고침했습니다. "와, 나타났어요!" 타임라인에 주문 요청이 표시되었습니다.

각 Span이 색깔별로 구분되어 있고, 소요 시간도 명확하게 보였습니다. 로그와의 연계 추가로 Sleuth는 로그에도 추적 정보를 자동으로 추가합니다.

로그를 보면 [appname,traceId,spanId] 형식으로 ID가 찍힙니다. 이걸 활용하면 Zipkin에서 느린 요청을 찾은 다음, Trace ID로 로그를 검색해서 상세 내용을 확인할 수 있습니다.

추적과 로그가 완벽하게 연결되는 겁니다. 성능 영향 "그런데 이렇게 추적하면 성능에 영향은 없나요?" 김개발 씨가 물었습니다.

물론 약간의 오버헤드는 있습니다. 하지만 Span 생성과 전송은 매우 가볍습니다.

실제 측정 결과 1% 미만의 성능 영향만 있다고 알려져 있습니다. 얻는 이점에 비하면 무시할 만한 수준입니다.

그리고 앞에서 본 샘플링 설정으로 일부 요청만 추적하면 영향을 더 줄일 수 있습니다.

실전 팁

💡 - 커스텀 Span은 병목이 예상되는 비즈니스 로직에만 추가하세요

  • 태그를 활용하면 나중에 특정 조건으로 필터링할 수 있어 유용합니다

4. Zipkin UI 사용

데이터가 Zipkin으로 전송되기 시작했습니다. 김개발 씨는 웹 UI를 열어봤지만, 뭐가 뭔지 잘 이해가 안 됐습니다.

"이 화면을 어떻게 봐야 하죠?" 박시니어 씨가 하나씩 설명해주기 시작했습니다.

Zipkin UI는 수집된 추적 데이터를 시각적으로 탐색할 수 있는 웹 인터페이스입니다. 타임라인 뷰로 각 서비스의 소요 시간을 한눈에 파악하고, 검색 필터로 특정 조건의 요청만 찾아볼 수 있습니다.

마치 Google Analytics로 웹사이트 트래픽을 분석하듯이, Zipkin UI로 서비스 흐름을 분석합니다.

다음 코드를 살펴봅시다.

// Zipkin UI 검색 쿼리 예제 (URL 파라미터)
// 서비스 이름으로 필터링
http://localhost:9411/zipkin/?serviceName=order-service

// 특정 Span 이름으로 검색
http://localhost:9411/zipkin/?serviceName=order-service&spanName=validate-order

// 최소 지연 시간으로 필터링 (100ms 이상)
http://localhost:9411/zipkin/?serviceName=order-service&minDuration=100000

// 태그로 필터링
http://localhost:9411/zipkin/?serviceName=order-service&annotationQuery=order.amount%3E1000

// 시간 범위 지정 (최근 1시간)
http://localhost:9411/zipkin/?lookback=1h

김개발 씨가 브라우저에서 http://localhost:9411을 열었습니다. 깔끔한 UI가 나타났습니다.

상단에는 검색 바가 있고, 하단에는 최근 요청들이 리스트로 표시되어 있었습니다. 박시니어 씨가 마우스를 움직이며 설명했습니다.

"먼저 검색 기능부터 볼게요." 검색 필터의 힘 Zipkin UI의 핵심은 강력한 검색 기능입니다. 운영 환경에서는 초당 수백, 수천 개의 요청이 발생합니다.

이 중에서 문제가 있는 요청만 찾아내야 합니다. 첫 번째 드롭다운은 서비스 이름입니다.

여러분의 마이크로서비스 중에서 어느 서비스의 요청을 볼지 선택합니다. 주문 서비스를 선택하면 주문 관련 요청만 표시됩니다.

두 번째는 Span 이름입니다. 특정 API 엔드포인트나 커스텀 Span만 보고 싶을 때 사용합니다.

예를 들어 "validate-order" Span만 보면 검증 로직의 성능만 집중적으로 분석할 수 있습니다. 시간 범위 선택 오른쪽 상단에는 시간 범위 선택 버튼이 있습니다.

기본값은 최근 15분이지만, 1시간, 12시간, 7일 같은 다양한 범위를 선택할 수 있습니다. 장애가 발생한 시점을 알고 있다면, "Custom" 옵션으로 정확한 시간대를 지정할 수도 있습니다.

"어제 오후 3시쯤 느렸어요"라는 제보가 들어오면, 그 시간대만 정확히 검색할 수 있습니다. 지연 시간 필터링 가장 유용한 기능 중 하나는 최소 지연 시간 필터입니다.

위 예제처럼 minDuration=100000을 지정하면 100ms 이상 걸린 요청만 표시됩니다. 숫자 단위는 마이크로초입니다.

1초는 1,000,000 마이크로초입니다. 따라서 100ms는 100,000입니다.

조금 헷갈릴 수 있지만, UI에서는 사람이 읽기 쉽게 "100ms"로 표시해줍니다. 느린 요청만 모아서 보면 성능 문제를 훨씬 빠르게 찾을 수 있습니다.

정상 요청은 20ms인데, 어떤 요청은 3초 걸렸다면 그게 바로 조사 대상입니다. 태그 기반 검색 앞에서 커스텀 Span에 태그를 추가했던 거 기억하시나요?

태그는 바로 이럴 때 쓰입니다. annotationQuery=order.amount>1000처럼 검색하면 주문 금액이 1000 이상인 요청만 표시됩니다.

"고액 주문만 느린 것 같아요"라는 가설이 있다면, 이렇게 검증할 수 있습니다. 사용자 ID, 지역, 상품 카테고리 등 다양한 비즈니스 속성으로 필터링할 수 있습니다.

단, 민감한 정보는 태그에 넣지 않도록 주의하세요. Trace 상세 보기 김개발 씨가 리스트에서 하나를 클릭했습니다.

새로운 화면이 나타났습니다. 가로로 긴 타임라인이 보였습니다.

"이게 바로 타임라인 뷰예요." 박시니어 씨가 설명했습니다. 타임라인은 시간 축을 기준으로 각 Span을 막대 그래프로 표시합니다.

가장 위는 전체 Trace이고, 그 아래로 각 서비스의 Span들이 계층적으로 나열됩니다. 막대의 길이가 소요 시간입니다.

한눈에 어느 부분이 오래 걸렸는지 보입니다. 예를 들어 결제 서비스 막대가 유독 길다면, 결제 처리에 병목이 있다는 뜻입니다.

Span 정보 확인 각 막대를 클릭하면 상세 정보가 나타납니다. 시작 시간, 종료 시간, 소요 시간이 정확히 표시됩니다.

우리가 추가한 태그들도 모두 보입니다. 특히 유용한 건 어노테이션입니다.

Sleuth가 자동으로 추가하는 정보인데, "Client Send", "Server Receive", "Server Send", "Client Receive" 같은 이벤트 시점이 기록됩니다. 네트워크 지연을 측정하고 싶다면, Client Send와 Server Receive의 시간 차이를 보면 됩니다.

실제 처리 시간은 Server Receive와 Server Send 차이입니다. 의존성 다이어그램 상단 메뉴에서 "Dependencies"를 클릭하면 또 다른 뷰가 나타납니다.

서비스들이 동그라미로 표시되고, 화살표로 연결되어 있습니다. 이건 서비스 의존성 그래프입니다.

주문 서비스에서 결제 서비스로 화살표가 가있고, 화살표에 호출 횟수가 표시됩니다. "아, 주문할 때마다 결제 API를 3번이나 호출하네?" 같은 문제를 발견할 수 있습니다.

실시간 vs 과거 데이터 Zipkin은 실시간 모니터링 도구는 아닙니다. 데이터가 수집되고 인덱싱되는 데 몇 초 정도 걸립니다.

따라서 방금 전 요청은 새로고침을 몇 번 해야 나타날 수 있습니다. 하지만 한 번 저장되면 과거 데이터를 자유롭게 탐색할 수 있습니다.

"지난주 화요일 점심시간에 뭔가 이상했는데" 하고 나중에 분석할 수 있는 겁니다. 김개발 씨의 발견 김개발 씨가 검색을 해보다가 소리쳤습니다.

"여기요! 이 요청은 10초나 걸렸어요!" 타임라인을 펼쳐보니 재고 서비스의 Span이 9초를 차지하고 있었습니다.

"재고 확인 API에 문제가 있나 봐요." 박시니어 씨가 웃으며 말했습니다. "바로 찾았네요.

Zipkin이 없었으면 어디가 문제인지 몰랐을 거예요."

실전 팁

💡 - 지연 시간 필터를 적극 활용해서 문제 요청만 집중적으로 분석하세요

  • 태그를 미리 잘 설계해두면 나중에 검색이 훨씬 편해집니다

5. 서비스 의존성 그래프

김개발 씨는 Zipkin의 Dependencies 탭을 클릭했습니다. 화면에 동그라미와 화살표가 복잡하게 연결된 그래프가 나타났습니다.

"우리 시스템이 이렇게 복잡했나요?" 박시니어 씨가 고개를 끄덕였습니다. "네, 실제로 보니 놀랍죠?"

서비스 의존성 그래프는 마이크로서비스들이 서로 어떻게 연결되어 있는지 자동으로 시각화합니다. 어떤 서비스가 어떤 서비스를 호출하는지, 호출 빈도는 얼마나 되는지 한눈에 파악할 수 있습니다.

마치 지하철 노선도처럼, 복잡한 서비스 관계를 직관적으로 이해할 수 있게 해줍니다.

다음 코드를 살펴봅시다.

// Zipkin API로 의존성 데이터 가져오기
// curl 또는 HTTP 클라이언트로 호출
curl http://localhost:9411/api/v2/dependencies?endTs=1640000000000&lookback=86400000

// 응답 예제 (JSON)
[
  {
    "parent": "order-service",
    "child": "payment-service",
    "callCount": 1523
  },
  {
    "parent": "order-service",
    "child": "inventory-service",
    "callCount": 1523
  },
  {
    "parent": "payment-service",
    "child": "billing-service",
    "callCount": 1523
  }
]

// 이 데이터를 기반으로 UI에서 그래프를 그립니다

김개발 씨는 화면에 나타난 그래프를 응시했습니다. 주문 서비스가 중앙에 있고, 거기서 여러 방향으로 화살표가 뻗어나갔습니다.

결제, 재고, 배송, 알림... 생각보다 많은 서비스와 연결되어 있었습니다.

"이게 다 실제 호출 내역에서 자동으로 만들어진 거예요." 박시니어 씨의 말이었습니다. 왜 의존성 그래프가 필요한가? 마이크로서비스 아키텍처의 가장 큰 장점은 서비스를 독립적으로 개발하고 배포할 수 있다는 겁니다.

하지만 큰 단점도 있습니다. 바로 전체 시스템을 이해하기 어렵다는 점입니다.

전통적인 모놀리식 애플리케이션은 코드만 보면 됐습니다. 어떤 클래스가 어떤 클래스를 호출하는지 IDE로 쉽게 추적할 수 있었습니다.

하지만 마이크로서비스는 다릅니다. 각 서비스가 다른 저장소에 있고, 다른 언어로 작성되기도 합니다.

네트워크 너머로 통신하기 때문에 코드만 봐서는 전체 흐름을 알기 어렵습니다. 문서화의 한계 물론 문서를 작성할 수 있습니다.

"주문 서비스는 결제 서비스와 재고 서비스를 호출합니다" 같은 아키텍처 문서를 만들 수 있죠. 하지만 문제는 문서가 금방 구식이 된다는 겁니다.

개발자가 코드를 수정할 때마다 문서를 업데이트하나요? 현실적으로 어렵습니다.

결국 문서는 실제와 다른 내용을 담게 됩니다. Zipkin의 의존성 그래프는 이 문제를 해결합니다.

실제 트래픽을 분석해서 자동으로 그려지기 때문에, 항상 최신 상태를 반영합니다. 거짓말을 하지 않는 문서인 셈입니다.

그래프 읽는 법 그래프를 자세히 살펴보겠습니다. 각 동그라미는 하나의 서비스를 나타냅니다.

서비스 이름이 표시됩니다. 크기는 보통 동일하지만, 도구에 따라 호출량을 반영해서 크기를 다르게 표시하기도 합니다.

화살표는 호출 방향입니다. A에서 B로 화살표가 있다면, A가 B를 호출한다는 뜻입니다.

화살표 위에 숫자가 표시되는데, 이게 바로 호출 횟수입니다. 위 JSON 예제를 보면, 주문 서비스가 결제 서비스를 1523번 호출했다고 나옵니다.

이건 선택한 시간 범위 동안의 총 호출 횟수입니다. 의존성 분석 이 그래프로 어떤 분석을 할 수 있을까요?

첫째, 순환 의존성을 발견할 수 있습니다. 만약 A → B → C → A처럼 화살표가 순환한다면, 뭔가 잘못 설계된 겁니다.

마이크로서비스는 일방향 의존성을 가져야 합니다. 둘째, 과도한 의존성을 찾을 수 있습니다.

한 서비스에서 화살표가 10개씩 뻗어나간다면, 그 서비스가 너무 많은 책임을 가진 건 아닌지 검토해야 합니다. 셋째, 핫스팟을 식별할 수 있습니다.

모든 화살표가 한 서비스로 몰린다면, 그 서비스가 병목이 될 가능성이 높습니다. 스케일링이나 캐싱을 고려해야 합니다.

호출 빈도의 의미 호출 횟수를 보면 예상치 못한 패턴을 발견하기도 합니다. 김개발 씨가 그래프를 보다가 이상한 점을 발견했습니다.

"어? 주문 한 번에 결제 API를 3번 호출하네요?" 코드를 확인해보니 정말 그랬습니다.

결제 검증, 결제 실행, 결제 확인을 각각 API 호출로 하고 있었습니다. 이걸 하나로 합치면 성능이 개선될 것 같았습니다.

이처럼 의존성 그래프는 비효율적인 호출 패턴을 찾는 데도 유용합니다. 시간대별 비교 Zipkin에서는 시간 범위를 바꿔가며 그래프를 볼 수 있습니다.

어제와 오늘을 비교해보면 어떤 변화가 있었는지 알 수 있습니다. 예를 들어 새로운 기능을 배포한 후 그래프를 보면, 어떤 서비스 간 연결이 새로 생겼는지 확인할 수 있습니다.

의도하지 않은 의존성이 추가됐다면 문제일 수 있습니다. API 응답 구조 위 코드에서 API 응답을 보면, 매우 간단한 구조입니다.

parent, child, callCount 세 가지 필드만 있습니다. endTs는 종료 시간(타임스탬프), lookback은 얼마나 과거로 거슬러 올라갈지 밀리초 단위로 지정합니다.

86400000은 24시간(하루)입니다. 이 API를 직접 호출해서 커스텀 대시보드를 만들 수도 있습니다.

Grafana 같은 도구와 연동하면 더 풍부한 시각화도 가능합니다. 실무 활용 사례 실제 현업에서는 이 그래프를 어떻게 쓸까요?

새로 입사한 개발자에게 시스템 구조를 설명할 때 매우 유용합니다. 아키텍처 문서를 읽는 것보다 그래프 하나 보여주는 게 훨씬 직관적입니다.

또한 성능 최적화 프로젝트를 시작할 때, 어느 서비스부터 개선할지 우선순위를 정하는 데 도움이 됩니다. 호출량이 많고 지연이 큰 서비스를 먼저 최적화하면 효과가 큽니다.

주의사항 의존성 그래프는 실제 트래픽을 기반으로 합니다. 따라서 사용되지 않는 경로는 표시되지 않습니다.

예를 들어 관리자 전용 기능이 거의 호출되지 않으면, 그 경로는 그래프에 안 나타날 수 있습니다. 완전한 아키텍처 문서로 사용하기엔 한계가 있습니다.

김개발 씨의 깨달음 김개발 씨는 그래프를 한참 들여다봤습니다. "우리 시스템이 이렇게 복잡했구나.

근데 Zipkin 덕분에 한눈에 보이네요." 박시니어 씨가 말했습니다. "맞아요.

복잡성을 줄일 순 없지만, 이해하기 쉽게 만들 수는 있죠."

실전 팁

💡 - 주기적으로 의존성 그래프를 리뷰해서 불필요한 의존성이 추가되지 않았는지 점검하세요

  • 호출 횟수가 비정상적으로 많다면 N+1 쿼리 같은 문제가 있을 수 있습니다

6. 지연 분석

드디어 김개발 씨는 느린 요청의 원인을 찾는 단계에 도달했습니다. Zipkin UI에서 10초 걸린 요청을 열어보니, 타임라인이 복잡하게 펼쳐졌습니다.

"이제 이걸 어떻게 분석하죠?" 박시니어 씨가 방법을 알려주기 시작했습니다.

지연 분석은 Zipkin의 핵심 활용 사례입니다. 타임라인 뷰에서 각 Span의 소요 시간을 비교하고, 병목 지점을 찾아내며, 병렬 처리 가능 여부를 판단합니다.

마치 의사가 혈액 검사 결과를 보고 어디가 문제인지 진단하듯이, 개발자는 Trace 데이터를 보고 성능 문제를 진단합니다.

다음 코드를 살펴봅시다.

// 느린 Span을 찾기 위한 커스텀 로직 예제
@RestController
public class OrderController {

    @Autowired
    private Tracer tracer;

    @GetMapping("/orders/{id}")
    public Order getOrder(@PathVariable Long id) {
        Span span = tracer.currentSpan();

        // 데이터베이스 조회 Span
        Span dbSpan = tracer.nextSpan().name("db-query").start();
        try (Tracer.SpanInScope ws = tracer.withSpan(dbSpan)) {
            Order order = orderRepository.findById(id);
            dbSpan.tag("query.rows", "1");
        } finally {
            dbSpan.end();
        }

        // 외부 API 호출 Span (병렬 처리 가능)
        Span apiSpan = tracer.nextSpan().name("external-api").start();
        try (Tracer.SpanInScope ws = tracer.withSpan(apiSpan)) {
            enrichOrderWithExternalData(order);
        } finally {
            apiSpan.end();
        }

        return order;
    }
}

김개발 씨는 타임라인 뷰를 펼쳐놓고 막대 그래프들을 하나씩 살펴봤습니다. 전체 요청은 10초가 걸렸는데, 어느 부분이 문제일까요?

박시니어 씨가 화면을 가리켰습니다. "먼저 가장 긴 막대를 찾으세요." 병목 찾기의 기본 지연 분석의 첫 단계는 가장 오래 걸린 Span을 찾는 것입니다.

타임라인에서 육안으로 쉽게 확인할 수 있습니다. 김개발 씨의 경우, 재고 서비스의 Span이 9초를 차지하고 있었습니다.

전체의 90%입니다. 명백한 병목입니다.

하지만 항상 이렇게 명확하지는 않습니다. 때로는 여러 Span이 비슷하게 느린 경우도 있습니다.

이럴 때는 각각을 개별적으로 조사해야 합니다. 직렬 vs 병렬 타임라인을 볼 때 주의할 점이 있습니다.

Span들이 순차적으로 실행되는지, 병렬로 실행되는지 구분해야 합니다. 만약 Span A와 Span B가 세로로 겹쳐 있다면, 동시에 실행된 겁니다.

이 경우 전체 시간은 둘 중 더 긴 쪽이 됩니다. 반대로 Span A가 끝난 후에 Span B가 시작된다면, 순차적으로 실행된 겁니다.

전체 시간은 둘을 합친 것입니다. 이걸 이해하면 최적화 기회를 찾을 수 있습니다.

순차적인데 사실 병렬로 실행해도 되는 작업이라면, 비동기 처리로 바꿔서 성능을 개선할 수 있습니다. 데이터베이스 쿼리 분석 위 코드 예제를 보면, 데이터베이스 조회를 별도 Span으로 만들었습니다.

태그로 조회한 행 수도 기록했습니다. 실무에서 가장 흔한 성능 문제는 비효율적인 데이터베이스 쿼리입니다.

Zipkin에서 DB Span을 보면, 몇 가지 패턴을 발견할 수 있습니다. 첫째, N+1 쿼리 문제입니다.

하나의 요청에서 수십, 수백 개의 작은 DB Span이 있다면 의심해봐야 합니다. 한 번에 조회하도록 쿼리를 개선하면 획기적으로 빨라집니다.

둘째, 느린 쿼리입니다. 하나의 DB Span이 수 초 걸린다면, 인덱스가 없거나 쿼리가 복잡한 겁니다.

데이터베이스 프로파일링 도구와 함께 사용하면 정확한 원인을 찾을 수 있습니다. 외부 API 호출 마이크로서비스에서는 다른 서비스의 API를 호출하는 경우가 많습니다.

이것도 지연의 주요 원인입니다. Zipkin에서 외부 호출 Span을 보면, 네트워크 지연을 측정할 수 있습니다.

Client Send와 Server Receive의 시간 차이가 네트워크 레이턴시입니다. 만약 네트워크 지연이 크다면, 서비스가 다른 리전에 있거나 네트워크 상태가 안 좋은 겁니다.

캐싱을 도입하거나, 서비스를 같은 리전에 배치하는 등의 해결책을 고려할 수 있습니다. 타임아웃과 재시도 가끔 Span 하나가 정확히 3초, 5초처럼 딱 떨어지는 시간으로 끝나는 경우가 있습니다.

이건 타임아웃입니다. 타임아웃이 발생했다는 건, 호출된 서비스가 응답하지 않았다는 의미입니다.

그 서비스로 들어가서 왜 느렸는지 조사해야 합니다. 또한 재시도 로직이 있다면, 같은 Span이 여러 번 나타날 수 있습니다.

첫 번째 시도가 실패해서 두 번째, 세 번째 시도를 한 거죠. 이럴 때는 근본 원인을 해결해야지, 재시도만 늘리면 안 됩니다.

캐싱 효과 확인 캐싱을 도입한 후 효과를 확인할 때도 Zipkin이 유용합니다. 캐시 히트와 미스를 각각 Span으로 만들거나 태그로 표시하면, Zipkin에서 캐시 히트율을 시각적으로 확인할 수 있습니다.

캐시 히트 시 응답 시간이 10ms, 미스 시 100ms처럼 명확한 차이가 보일 겁니다. 백분위수 분석 Zipkin UI에서는 단일 Trace만 보는 게 아니라, 여러 Trace를 통계적으로 분석할 수도 있습니다.

예를 들어 특정 API의 P50, P95, P99 지연 시간을 확인할 수 있습니다. P99가 3초라면, 100명 중 1명은 3초를 기다린다는 뜻입니다.

사용자 경험에 나쁜 영향을 줍니다. 이런 통계는 Zipkin의 원시 데이터를 Prometheus나 Grafana로 가져가서 분석하면 더 풍부하게 볼 수 있습니다.

코드 개선 예시 김개발 씨는 재고 서비스의 코드를 열었습니다. 재고 확인 로직에서 외부 API를 동기적으로 호출하고 있었습니다.

박시니어 씨가 제안했습니다. "이 부분을 비동기로 바꿔보면 어떨까요?

CompletableFuture를 써서 병렬로 처리하면 시간이 절반으로 줄 거예요." 코드를 수정하고 다시 테스트했습니다. Zipkin 타임라인을 보니, 정말로 두 개의 Span이 겹쳐서 실행되었습니다.

전체 시간이 10초에서 5초로 줄었습니다. 지속적인 모니터링 지연 분석은 한 번 하고 끝이 아닙니다.

시스템은 계속 변화합니다. 새로운 기능이 추가되고, 트래픽이 증가하고, 데이터가 쌓입니다.

주기적으로 Zipkin을 확인해서 새로운 병목이 생기지 않았는지 점검해야 합니다. 주간 성능 리뷰 미팅에서 Zipkin 데이터를 공유하면 팀 전체가 성능에 대한 인식을 가질 수 있습니다.

알림 설정 Zipkin 자체는 알림 기능이 없지만, 데이터를 다른 도구로 보내서 알림을 설정할 수 있습니다. 예를 들어 P99 지연이 1초를 넘으면 Slack으로 알림을 보내도록 설정할 수 있습니다.

문제가 커지기 전에 미리 대응할 수 있습니다. 김개발 씨의 성공 김개발 씨는 재고 서비스를 최적화한 후 배포했습니다.

다음 날 Zipkin을 확인하니, 주문 요청의 평균 응답 시간이 절반으로 줄었습니다. 고객 지원팀에서도 연락이 왔습니다.

"요즘 주문이 훨씬 빨라졌다는 피드백이 많아요!" 박시니어 씨가 등을 두드려줬습니다. "잘했어요.

Zipkin만 제대로 활용해도 이렇게 큰 개선을 만들 수 있습니다."

실전 팁

💡 - 타임라인에서 병렬 실행 가능한 부분을 찾아 비동기로 전환하면 성능이 크게 개선됩니다

  • P95, P99 같은 고백분위수 지연에 집중하세요. 평균은 극단값을 숨깁니다

이상으로 학습을 마칩니다. 위 내용을 직접 코드로 작성해보면서 익혀보세요!

#Zipkin#DistributedTracing#Microservices#Observability#Performance#마이크로서비스

댓글 (0)

댓글을 작성하려면 로그인이 필요합니다.

함께 보면 좋은 카드 뉴스

Istio 설치와 구성 완벽 가이드

Kubernetes 환경에서 Istio 서비스 메시를 설치하고 구성하는 방법을 초급 개발자도 쉽게 이해할 수 있도록 실무 스토리와 비유로 풀어낸 가이드입니다. istioctl 설치부터 사이드카 주입까지 단계별로 학습합니다.

서비스 메시 완벽 가이드

마이크로서비스 간 통신을 안전하고 효율적으로 관리하는 서비스 메시의 핵심 개념부터 실전 도입까지, 초급 개발자를 위한 완벽한 입문서입니다. Istio와 Linkerd 비교, 사이드카 패턴, 실무 적용 노하우를 담았습니다.

Helm 마이크로서비스 패키징 완벽 가이드

Kubernetes 환경에서 마이크로서비스를 효율적으로 패키징하고 배포하는 Helm의 핵심 기능을 실무 중심으로 학습합니다. Chart 생성부터 릴리스 관리까지 체계적으로 다룹니다.

관찰 가능한 마이크로서비스 완벽 가이드

마이크로서비스 환경에서 시스템의 상태를 실시간으로 관찰하고 모니터링하는 방법을 배웁니다. Resilience4j, Zipkin, Prometheus, Grafana, EFK 스택을 활용하여 안정적이고 관찰 가능한 시스템을 구축하는 실전 가이드입니다.

EFK 스택 로깅 완벽 가이드

마이크로서비스 환경에서 로그를 효과적으로 수집하고 분석하는 EFK 스택(Elasticsearch, Fluentd, Kibana)의 핵심 개념과 실전 활용법을 초급 개발자도 쉽게 이해할 수 있도록 정리한 가이드입니다.