백엔드

[Datadog 삽질기] Observability와 Trace 알아보기

hseong 2025. 4. 20. 18:34

들어가기에 앞서

최근 담당하고 있는 서비스에 모니터링 도구인 Datadog을 도입했습니다. 대부분은 DevOps에서 설정을 잡아주고 Datadog agent가 자동으로 계측을 수행해서 손댈 것이 없었습니다. 메트릭도 자동으로 측정되고 api 요청, 스케줄러 실행 모두 잘 계측되었습니다. 딱 하나 이벤트 컨슈머를 제외하고요. 나중에 알고 보니 이벤트 컨슈머도 잘 계측되고 있었지만 Datadog의 특정 기능으로 인해 찾아보기가 어려웠습니다.

이러한 배경에서 본 게시글은 Datadog 도입 과정에서 있었던 삽질을 기록하기 위해 작성했습니다. 그 첫번째 기록으로 Observability란 무엇인지, Observability를 구성하는 핵심 요소, 그리고 Trace에 대해 조금 더 알아본 내용을 기록합니다.

학습은 OpenTelemetry 공식 문서를 참고했습니다. 학습한 내용을 기록하는 과정에서 한국어로 번역하는 것이 애매하거나 원문 그대로 기록하는 편이 더 좋을 것 같다고 판단한 경우 원문을 그대로 사용했습니다.

Observability란?

Observability(관찰 가능성)이란 출력을 검사하여 시스템의 내부 상태를 이해하는 ability입니다. 이는 trace, metric, log를 포함한 telemetry data(원격 측정 데이터)를 검사하여 시스템의 내부 상태를 이해할 수 있음을 의미합니다. 즉, 시스템의 내부 동작을 알지 못하여도 해당 시스템에 대한 질문을 함으로써 외부에서 시스템을 이해할 수 있도록 합니다.

시스템에 질문을 던지기 위해서는 애플리케이션이 instrument(계측)되어야 합니다. 즉, 애플리케이션 코드는 반드시 trace, metric, log과 같은 시그널을 내보내야 합니다. 그런 다음 instrumented data는 observability backend로 전송해야 합니다.

Observability의 핵심 구성 요소

Observability의 핵심 구성 요소는 Traces(추적), Metrics(메트릭), Logs(로그) 세가지입니다.

1) Traces

  • 시스템 내에서 요청이 흐르는 경로를 기록
  • 하나의 trace는 하나 이상의 span 계층으로 구성
  • 각 span은 요청이 수행하는 특정 operation을 추적하고 어떤 일이 일어나는지 파악

2) Metrics

  • 시스템에 대해 일정 기간 동안 수치 데이터를 집계한 것
  • CPU 사용률, 서비스에 대한 요청 비율, 에러 비율과 같은 정보를 포함

3) Logs

  • 시스템이 내보내는 타임 스탬프 메세지
  • trace와 달리 특정 사용자의 요청 또는 트랜잭션과 관련될 필요는 없음
  • 시스템의 모든 곳에서 찾을 수 있는 메세지

Traces

Traces란?

trace는 애플리케이션에 요청이 이루어질 때 발생하는 큰 그림을 제공합니다. 하나 이상의 span으로 구성되며 계층적으로 구성된 span을 통해 애플리케이션의 요청이 이루어지는 전체 경로를 효과적으로 이해할 수 있습니다.

다음 이미지와 같이 하나의 요청이 발생했을 때, 어떤 메서드를 호출했는지, 어떤 큐에 메세지를 발행했는지, 어떤 리스너가 메세지를 소비했는지 등이 상세히 파악할 수 있습니다.

다음의 예제를 통해 이러한 span이 어떻게 하나의 trace를 구성하는지 알아보겠습니다.

  1. hello span
    {
    "name": "hello",
    "context": {
     "trace_id": "5b8aa5a2d2c872e8321cf37308d69df2",
     "span_id": "051581bf3cb55c13"
    },
    "parent_id": null,
    // ...
    }
  • hello span은 parent_id가 없습니다. 즉, 이 span이 trace의 root span임을 의미합니다.
  1. hello-greetings span
    {
    "name": "hello-greetings",
    "context": {
     "trace_id": "5b8aa5a2d2c872e8321cf37308d69df2",
     "span_id": "5fb397be34d26b51"
    },
    "parent_id": "051581bf3cb55c13",
    // ...
    }
  • hello-greetings span은 parent_id로 hello span의 span_id를 가집니다.
  • trace_id 역시 hello span과 동일합니다. 즉, hellohello-greetings는 동일한 trace의 일부임을 의미합니다.
  1. hello-salutations span
    {
    "name": "hello-salutations",
    "context": {
     "trace_id": "5b8aa5a2d2c872e8321cf37308d69df2",
     "span_id": "93564f51e1abe1c2"
    },
    "parent_id": "051581bf3cb55c13",
    // ...
    }
  • hello-salutations도 trace_id와 parent_id를 통해 hello와 동일한 trace의 일부임을 알 수 있습니다.

hello, hello-greetings, hello-salutations 각 span은 동일한 trace_id, parent_id를 가지며 계층 구조를 이루고, 하나의 trace를 구성하는 것을 알 수 있습니다.

Spans란?

span은 operation 또는 작업의 단위를 나타냅니다. span은 trace의 building block으로 다음과 같은 정보가 포함되어 있습니다.

  • 이름
  • trace_id, span_id
  • 부모 span_id
  • 시작/종료 타임 스탬프
  • span context
  • attributes(span의 메타데이터)
  • span events
  • span links
  • span status

Span Attributes

attribute는 span에 operation에 대한 정보를 전달하기 위해 사용할 수 있는 key-value 쌍으로 구성된 메타데이터입니다. 예를 들면 e-commerce 서비스에서 사용자의 id, 카트 id, 상품 id와 같은 정보를 attribute에 추가할 수 있습니다.

Span Status

각 span은 다음과 같은 상태를 가집니다.

  • Unset
  • Error
  • Ok

기본 값은 Unset입니다. Unset는 에러 없이 추적한 operation이 성공적으로 완료되었음을 의미합니다.

Error는 추적중인 operation에서 오류가 발생했다는 것을 의미합니다. 가령 http 500 에러 같은 것입니다.

Ok는 개발자가 에러가 없다는 것을 명시적으로 표시했음을 의미합니다. 개발자가 성공 외에 다른 해석이 없기를 바라는 상황에서 도움이 됩니다. 대부분의 경우 개발자가 명시적으로 Ok를 표시할 필요는 없습니다.

Span Kind

Span은 client, server, internal, producer, consumer 중 하나로 생성됩니다. 이러한 span kind는 trace를 어떻게 조립해야하는지 tracing backend에게 힌트를 제공합니다.

Client
  • client span은 http 요청 또는 데이터베이스 호출과 같은 밖으로 나가는 동기식 호출을 나타냅니다.
Server
  • server는 HTTP 요청과 같이 안으로 들어오는 동기식 호출을 나타냅니다.
Internal
  • internal은 프로세스 경계를 넘지 않는 operation을 나타냅니다. 함수 호출같은 것에 사용할 수 있습니다.
Producer
  • producer는 비동기로 처리될 수 있는 작업의 생성을 나타냅니다.
Consumer
  • consumer는 producer가 만든 작업을 처리하는 것을 나타냅니다.

참고

https://opentelemetry.io/docs/concepts/