distributed-tracing

2 개의 포스트

디스코드의 엘릭 (새 탭에서 열림)

Discord는 Elixir의 강력한 동시성 메커니즘을 활용하여 각 서버(길드)를 독립적으로 운영함으로써 수억 명의 사용자에게 실시간에 가까운 채팅 경험을 제공합니다. 그러나 급격한 트래픽 증가로 시스템 자정 능력이 한계에 도달할 때, 기존의 메트릭이나 자체 개발한 메모리 기반 분석 도구만으로는 복잡한 성능 병목 현상과 사용자 경험의 실질적인 저하 원인을 파악하는 데 한계가 있었습니다. 이를 해결하기 위해 Discord는 Elixir 환경에 맞춤화된 분산 추적(Distributed Tracing) 시스템을 직접 구축하여 서비스 중단 없이 시스템 전반의 가시성을 확보하는 데 성공했습니다. **기존 관측 도구의 한계와 실무적 어려움** * **지표와 로그의 한계:** 대시보드는 엔진 온도계처럼 시스템의 상태를 보여주지만, 온도가 높을 때 사용자가 느끼는 실제 주행 경험(지연 시간의 체감 등)이나 구체적인 결과까지는 설명해주지 못합니다. * **길드 타이밍(Guild Timings) 도구:** 길드별 작업 소요 시간을 분 단위로 메모리에 기록하는 커스텀 도구를 사용해왔으나, 데이터 양이 너무 방대하여 대형 길드를 제외하고는 데이터를 빠르게 순환(Rotation)시켜야 하므로 과거 이력 분석이 어렵습니다. * **다운스트림 효과 파악 불가:** 기존 도구들은 개별 작업의 소요 시간은 보여주지만, 해당 작업이 연쇄적으로 일으키는 다운스트림 서비스의 영향과 전체적인 실행 흐름을 시각화하지 못하는 단점이 있었습니다. **Elixir 환경에서의 분산 추적 도입 과정** * **분산 추적(APM)의 필요성:** 작업의 구성 요소별 소요 시간을 한눈에 파악할 수 있는 분산 추적 기술을 통해 시스템 내부의 복잡한 상호작용을 투명하게 확인하고자 했습니다. * **기술적 난관:** 일반적인 추적 도구는 HTTP 헤더와 같은 메타데이터 레이어를 통해 추적 정보를 전달하지만, Elixir의 기본 통신 도구들에는 이러한 메타데이터 레이어가 내장되어 있지 않았습니다. * **커스텀 메타데이터 레이어 구축:** 서비스 간 통신 방식에 추적 정보를 함께 전달할 수 있는 자체 메타데이터 전달 메커니즘을 설계하여 문제를 해결했습니다. * **무중단 통합:** 서비스 간의 통신 방식을 근본적으로 변경하는 작업임에도 불구하고, 철저한 설계를 통해 시스템 가동 중단(Downtime) 없이 새로운 추적 시스템을 성공적으로 통합했습니다. 복잡한 분산 시스템에서 단순한 성능 지표만으로는 문제의 근본 원인을 파악하기 어렵습니다. 특히 Elixir와 같이 특수한 통신 구조를 가진 환경에서는 표준적인 APM 도구를 그대로 적용하기보다, 시스템의 특성에 맞춰 메타데이터 전달 계층을 직접 구현함으로써 인프라 전반의 흐름을 명확히 파악할 수 있는 분석 환경을 구축하는 것이 중요합니다.

100년 가는 프론트엔드 코드, SDK (새 탭에서 열림)

토스페이먼츠는 결제 연동의 복잡성을 해결하기 위해 SDK를 제공하고 있으며, 최근 V1의 한계를 극복하고 안정성과 확장성을 극대화한 V2 SDK를 구축했습니다. 가맹점의 다양한 런타임 환경과 예측 불가능한 요구사항에 대응하기 위해 단순한 기능 구현을 넘어 체계적인 아키텍처와 모니터링 시스템을 도입했습니다. 결과적으로 개발자에게는 쉬운 연동 경험을, 비즈니스에는 견고한 신뢰성을 제공하는 결제 생태계를 완성했습니다. **SDK 개발의 특수성과 V1의 한계** * **환경의 의존성:** SDK는 가맹점의 코드 내에서 실행되므로, 가맹점의 호출 빈도나 네트워크 상태에 직접적인 영향을 받습니다. 일례로 사용량 분석을 위해 추가한 로그 코드가 특정 가맹점의 잦은 호출과 맞물려 네트워크 병목 현상을 일으키고 서비스 전체를 다운시키는 사례가 발생했습니다. * **런타임 예측 불가능성:** 가맹점에서 잘못된 데이터 타입(예: String 대신 Number)을 전달할 경우 `startsWith` 같은 표준 메서드에서 에러가 발생하는 등, 일반적인 프론트엔드 개발보다 훨씬 방어적인 코딩이 요구됩니다. * **커뮤니케이션의 접점:** SDK는 단순히 API를 호출하는 도구가 아니라 가맹점 개발자와 만나는 기술적 창구이며, 가맹점의 수많은 커스텀 요구사항을 수용해야 하는 복잡성을 안고 있습니다. **안정성 확보를 위한 테스트와 모니터링** * **촘촘한 테스트 체계:** 로직 검증을 위한 300개 이상의 단위 테스트와 다양한 유즈케이스를 반영한 500개 이상의 E2E 통합 테스트를 통해 코드 수준의 안정성을 확보했습니다. * **Global Trace ID:** 프론트엔드부터 백엔드까지 결제 전 과정을 하나의 식별자로 추적하는 체계를 도입하여, 장애 발생 시 시스템 레이어 전체를 쉽게 파악할 수 있도록 했습니다. * **모니터링 CLI:** 배포 전후의 결제 성공률을 가맹점 및 런타임 환경(OS, 브라우저, 웹뷰 등)별로 비교 분석하는 자체 도구를 개발했습니다. 이를 통해 특정 환경에서 발생하는 결제 중단 현상을 실시간으로 탐지하고 즉각 대응합니다. **확장성을 위한 레이어드 아키텍처** * **조립 가능한 구조:** 특정 가맹점만을 위한 예외 처리가 `if`문으로 산재되어 코드 복잡도가 올라가는 문제를 해결하기 위해, 기능을 레고 블록처럼 독립적으로 구성했습니다. * **3계층 분리:** "변경의 원인"을 기준으로 코드의 경계를 명확히 나누어 관리합니다. * **Public Interface Layer:** 가맹점과 약속한 인터페이스를 검증하고 도메인 언어로 번역하는 역할 * **Domain Layer:** 핵심 비즈니스 로직과 결제 정책을 담당하는 중심부 * **External Service Layer:** 서버 API나 Web API 등 외부 의존성과의 통신을 담당하는 계층 * **관심사 격리:** 이러한 계층화를 통해 가맹점별 커스텀 요구사항이 추가되더라도 기존의 핵심 로직에 영향을 주지 않고 특정 블록만 교체하거나 확장할 수 있는 유연성을 확보했습니다. 성공적인 SDK 개발을 위해서는 단순히 편리한 기능을 제공하는 것을 넘어, 타사의 코드 환경에서도 견고하게 동작할 수 있는 방어적인 설계와 문제 발생 시 즉시 원인을 파악할 수 있는 관측성(Observability) 확보가 필수적입니다. 가맹점별 특이 케이스를 코드 전반에 흩뿌리기보다는, 명확한 레이어 구분을 통해 비즈니스 로직과 커스텀 로직을 분리하는 설계 원칙을 권장합니다.