Sentry SDK 성능 모니터링 아키텍처의 진화와 과제

성능 추적 기능의 초기 설계

2019년 초, Sentry는 Python 및 JavaScript SDK를 중심으로 성능 추적 기능을 실험하기 시작했습니다. 이 두 언어는 Sentry 내부의 백엔드와 프론트엔드 시스템에 직접 계측(instrumentation)을 적용해볼 수 있는 이상적인 후보였습니다. 최초의 프로토타입은 2019년 4월 말 구현되어同年 5월 초 본 서버에 통합되었습니다.

당시 OpenTracing과 OpenCensus가 통합되어 OpenTelemetry로 발전하던 시기와 맞물려 있었으며, Sentry는 초기 OpenTelemetry 사양에서 영감을 받아 자체 추적 모델을 설계했습니다. 예를 들어, Span 상태 코드는 이후 OpenTelemetry에서 채택된 값들과 상당 부분 일치합니다.

성능 데이터는 기존의 오류 보고 인프라를 재사용하여 전송되었습니다. 즉, 새로운 transaction 유형의 이벤트를 정의하고, 기존의 이벤트 전송 파이프라인을 활용함으로써 빠르게 기능을 출시할 수 있었습니다. 이 접근법은 개발 속도를 높였지만, 장기적으로 여러 제약을 야기하게 됩니다.

문제 식별: 동시성과 스코프 관리

Sentry SDK는 Hub-Scope-Client 아키텍처를 기반으로 합니다. 각 동시성 단위마다 독립된 Hub 인스턴스가 존재해야 하지만, 실제로는 JavaScript와 모바일 환경처럼 전역 컨텍스트가 공유되는 경우가 많습니다. 이로 인해 추적 정보, 특히 현재 활성화된 Span을 정확히 추적하는 데 어려움이 발생합니다.

다음 코드는 문제를 잘 보여줍니다:

async function fetchData(url, transactionName) {
  const hub = getCurrentHub();
  const txn = hub.startTransaction({ name: transactionName });
  hub.getScope().setSpan(txn);

  try {
    await fetch(url);
  } finally {
    txn.finish();
  }
}

// 동시에 실행
Promise.all([
  fetchData('https://api.example.com/user', 'load-user'),
  fetchData('https://api.example.com/feed', 'load-feed')
]);

두 비동기 함수가 동일한 전역 Hub를 공유하므로, fetch 계측 코드가 어느 트랜잭션에 속한 요청인지 판단할 수 없습니다. 결과적으로 생성된 Span 계층 구조가 무너지거나, 모든 하위 Span이 최상위 트랜잭션 아래에 평면화(flattened)되어 버립니다.

이러한 문제는 Angular의 Zone.js와 같은 도구로 해결 가능하지만, 런타임에 핵심 API를 수정(monkey-patching)해야 하므로 안정성과 번들 크기 측면에서 부담이 됩니다.

데이터 흐름의 모순된 기대

Scope는 태그(tag), 사용자 정보, 브레드크럼(breadcrumb) 등 다양한 컨텍스트 정보를 저장합니다. 그러나 이 정보들의 전파 방식에 대한 기대가 서로 다릅니다:

  • 태그는 자식 호출로만 전달되기를 기대함 (오버라이딩 가능)
  • 브레드크럼은 에러 발생 시 역추적할 수 있도록 호출 스택 전체에 누적되기를 기대함
  • 현재 Span은 실행 흐름에 따라 명확히 구분되어야 함

기존 Scope 개념으로는 이러한 상충되는 요구를 자연스럽게 표현하기 어렵습니다. 이를 해결하려면 Scope의 내부 구조를 근본적으로 재설계하고, 주요 API 변경 없이는 불가능할 정도의 깊은 수정이 필요합니다.

Span 수집 모델의 한계

Sentry는 하나의 트랜잭션에 속한 모든 Span을 메모리에 유지한 후, 완료 시점에 JSON 형식의 단일 이벤트로 집약하여 전송합니다. 반면 OpenTelemetry는 Span 완료 시점에 가까운 시점에서 배치 전송(batch export)을 수행합니다.

아래 다이어그램은 세 서비스(Frontend, Backend, Storage) 간의 분산 추적을 보여줍니다:

F*
├─ B*
│  ├─ B
│  │  └─ S*
│  ├─ B
│  │  └─ S*
│  └─ B
│     └─ S*

여기서 * 표시는 트랜잭션 진입점입니다. Sentry 모델에서는 Backend의 B*가 종료될 때까지 모든 자식 Span(B, S*)이 메모리에 유지되어야 하며, 이는 지연 시간이 긴 시나리오에서 메모리 사용량을 급격히 증가시킵니다.

복잡한 직렬화 구조

일반 Span은 간단한 객체로 표현되지만, 트랜잭션 Span은 기존 오류 이벤트 스키마를 확장한 복합 구조를 가집니다. 서브 Span들은 리스트 형태로 중첩되어 포함되며, 추가적으로 breadcrumbs, user, tags 등의 컨텍스트 정보도 포함됩니다. 이는 직렬화/역직렬화 과정을 복잡하게 만들고, OpenTelemetry와 같은 표준과의 호환성을 저해합니다.

수명 주기 훅의 제한

Sentry는 오류 이벤트를 위한 beforeSend 훅을 제공하지만, 트랜잭션에는 적용되지 않습니다. 이는 기존 필터 로직이 트랜잭션을 의도치 않게 방해하는 것을 막기 위함이지만, 대신 더 복잡한 EventProcessor를 사용해야 하는 번거로움을 초래합니다.

중첩된 트랜잭션 처리의 미비

현재 모델은 중첩된 트랜잭션을 지원하지 않으며, 내부 트랜잭션이 외부의 Span을 "삼켜버리는" 현상이 발생합니다. 또한 UI는 각 트랜잭션을 고립된 단위로만 표시하며, 공통 trace_id를 가진 다른 트랜잭션과의 관계를 직관적으로 보여주지 못합니다.

withTransaction("page-load", async () => {
  const items = await db.queryItems();
  for (const item of items) {
    if (item.hasImage) {
      // 중첩 트랜잭션 — 어떤 스팬이 어디에 포함될까?
      withTransaction(`thumbnail-${item.id}`, () => generateThumbnail(item));
    }
  }
});

이 경우 청구 대상이 1건인지, 11건인지, 내부 Span들이 올바르게 연결되는지 명확하지 않습니다.

트랜잭션 바깥의 Span 소실

자동 생성되지 않은 환경에서 Span은 반드시 하나의 트랜잭션에 포함되어야만 전송됩니다. 따라서 프론트엔드에서 버튼 클릭을 Span으로만 기록하면, 그 후속 작업들이 모두 누락될 수 있습니다. 이는 사용자 인터랙션 기반 추적이 불완전해지는 주요 원인입니다.

웹 바이탈(Web Vitals) 데이터 손실

브라우저 SDK는 페이지 로딩 완료 후에도 LCP(Largest Contentful Paint)와 같은 지표를 업데이트하지만, 이미 완료된 트랜잭션은 재전송되지 않으므로 최종 측정값이 반영되지 않을 수 있습니다.

메모리 및 네트워크 효율성 문제

  • 메모리 버퍼링: 고병렬 워크로드에서는 수천 건의 Span이 메모리에 적재되어 OOM 위험이 증가합니다.
  • 비효율적 전송: 각 트랜잭션 완료 시 별도의 HTTP 요청이 발생하여, 네트워크 오버헤드가 심각해질 수 있습니다.

OpenTelemetry와의 호환성 결여

Sentry Exporter for OpenTelemetry Collector는 존재하지만, Sentry의 특수한 이벤트 구조 때문에 일부 Span 속성이 손실되거나 잘못 매핑되는 제한이 있습니다. 양방향 통합이 어렵습니다.

결론 및 향후 방향

기존 아키텍처는 신속한 출시라는 목표를 달성했지만, 장기 운영 관점에서 여러 구조적 한계를 드러냈습니다. 주요 개선 방향은 다음과 같습니다:

  • Span 컨텍스트 전파를 위한 격리된 실행 컨텍스트(Isolated Execution Context) 도입
  • Span과 Transaction의 개념 통합 또는 Span 중심의 모델 전환
  • 배치 기반, 실시간 스트리밍 수집 파이프라인 설계
  • OpenTelemetry와의 상호 운용성 강화

이러한 변화는 대부분의 SDK에서 주요 버전 업그레이드를 수반하며, 기존 API와의 호환성을 포기해야 할 수도 있습니다. 그러나 궁극적으로는 더 정확하고 효율적인 성능 모니터링 인프라를 구축하기 위한 필수 과정입니다.

태그: Sentry Performance Monitoring Distributed Tracing OpenTelemetry SDK architecture

7월 15일 17:53에 게시됨