토스 페이먼츠 면접에서 Observbility 관련한 질문을 받았었는데.. 제대로 답변을 못했던 기억이 있어서 토스 페이먼츠는 어떻게 분산 환경에서 로그를 통해 이슈를 파악하는지 내용을 정리해 보고 현재 회사의 환경과 비교해 보려고 합니다.

현대의 소프트웨어 시스템은 클라우드, 컨테이너, MSA(Microservices Architecture) 등의 기술을 기반으로 하며, 이는 시스템의 가상화 및 추상화를 심화시켜 기존의 IT 시스템이 가지고 있던 문제를 개선하며 민첩한 제품 조직을 구성하는데 도움이 되기에 많이 활용되고 있습니다. 하지만 이런 환경은 복잡성이 높아 추적을 어렵게 만드빈다. 수시로 업데이트 되는 서비스와 하나의 요청에서 여러 네트워크 홉을 거치게 되고 기존에 모니터링 시스템으로는 파악하기 어려울 수 있습니다.

전기공학자 루돌프 칼만은 Observeability를 "시스템의 출력으로부터 시스템의 상태를 이해할 수 있는 능력"으로 정의 했습니다.
우리의 시스템은 정상적으로 동작하는 것을 보장할 수 있어도 외부 시스템은 오류를 반환할 수 있습니다. 좋은 로그를 남겨 우리의 시스템이 어떻게 동작했는지 기록을 남기는 것은 중요합니다.
로그를 잘 남긴다는 것은 무엇인가?

기본적으로 spring web만 사용해 단순히 로그를 남겼을 때 생성 되는 로그에서는 기록된 시간, 로그 레벨, 프로세스 Id, 요청 처리에 사용된 스레드, 로그를 남긴 코드위치 정도를 알 수 있습니다.
이 정도 로그만 봐도 오류를 찾아낼 수 있을것 같지만 아래 처럼 동일한 위치에 동일한 로그가 추가되는 경우가 있습니다.


해당 경우엔 어디에서 로그가 남았는지 알 수 없게 됩니다. 이건 간단 하게 어떤 API에서 로그를 남긴건지 추가하는 방식으로 개선할 수 있게 됩니다.


이번엔 예외를 잡아서 Stacktrace를 로그로 남기는 코드가 작성되었고 Stacktrace가 남는것을 확인할 수 있습니다. 하지만 Stacktrace엔 어느 요청에 의해 남게된 것인지 알기 어려울 것 같습니다.
예외가 발생했을 때를 대비해 요청당 식별자를 하나 발급해 두고 모든 로그에 해당 식별자를 일관되게 남겨두면 도움될 것 같습니다.
트레이스 ID(Trace ID)로 연결 같은 맥락의 로그가 동일한 식별자(Trace ID)로 연결되어 기록되고 검색될 수 있게 추가하고, 이는 요청 처리 과정 중 발생한 예외 상황 등을 정확히 파악하는 데 필수적입니다.

사용자별 API 응답 평균 시간을 추출할 수 있나?(로그의 정형화)

정형화된 저장: 로그가 JSON 형식 등으로 정형화되어 저장되어야 합니다. 이는 로그 검색 및 외부 도구 연계를 용이하게 합니다.

이제 사용자가 App을 통해 거래 내역을 조회하는 상황을 가정해 보겠습니다. 이 요청 처리 흐름에서 각 서버는 어떻게 로그를 남기는것이 좋은 로그가 될 수 있을까요?

해당 로그는 위에서 말했던 조건을 모두 충족하지만 좋은 로그라고 할 수 있을까요?
분산 추적은 MSA 환경에서 단일 사용자 요청이 여러 서비스에 분산되어 처리되는 과정을 추적하는 방법입니다.

만약 userId:37번인 고객이 요청을 보냈다가 실패했는데 orderId는 모른다고 하면 추적할 수 있을까요?
기술적 관점으로는 3개의 요청으로 볼 수 있지만 비즈니스 관점으로는 1개의 요청이라고 볼 수 있습니다.
서로 다른 서버가 HTTP 헤더 등을 통해 동일한 Trace ID를 공유함으로써, 비즈니스 관점에서 하나의 요청으로 해석되는 호출 흐름 전체를 연결합니다. 하나의 ID로 서비스 간의 의존 관계 및 각 서비스의 처리 시간을 파악할 수 있으며, 이를 시각화하여 요청 처리 실패 지점이나 성능 병목 구간을 쉽게 찾는 것이 가능해집니다.

HTTP 메시지를 예로 들면 우이ㅔ그림 과 같은 형태로 Trace Id를 전파하는 형태로 하나의 ID로 서비스 간 요청을 파악할 수 있게됩니다.

이제 하나의 요청이 여러 서버를 돌면서 요청을 처리하더라도 해당 요청이 어떻게 처리 됐는지 분산 추적을 빠르게 할 수 있습니다.
코드의 변경없이 분산 추적을 하는건 어려울 수 있지만 분산 추적을 구현하는것은 간단한 코드 변경으로 가능하게 할 수 있습니다.
현재는 분산 추적 ㅅ애태계의 표준화를 위해

해당 사진 처럼 여러 라이브러리가 통합되는 등 통일되어가고 있습니다.
토스페이먼츠의 분산 추적 확장
1. 글로벌 트레이스 ID (Global Trace ID)
단순 요청 처리 단계를 넘어 하나의 사용자 시나리오 전체를 이해합니다. 결제 정보 확인 화면에서 결제 완료 화면으로 넘어가는 등 서비스 화면 전환 단계 전체를 엮어줄 수 있는 글로벌 트레이스 ID를 정의하고 전파합니다. 이를 통해 문제 발생 시 더 빠르고 정확하게 전체 맥락을 이해할 수 있습니다.

2. 추적 문맥 전파 항목 추가
글로벌 트레이스 ID 외에도 다양한 정보(예: 특정 금융사 정보)를 추적 문맥에 함께 담아 전파합니다. 요청을 받는 서비스들이 현재 요청이 처리되는 문맥을 더 잘 이해할 수 있게 되어, 장애 발생 시 원인과 영향을 더 상세하게 분석하고 답변할 수 있습니다.
3. 분산 추적 확장
분산 추적의 범위를 MSA 서비스를 넘어 CDN, 방화벽, 로드밸런서, DB, TCP 서버 등 다양한 인프라 구성요소로 확대합니다. Trace ID만 있다면 전 구간의 로그를 찾아볼 수 있도록 구성합니다.

쿼리의 주석 부분에 추적 문맥을 포함시켜 Trace ID를 전파하고, AOP(Aspect-Oriented Programming)를 이용해 쿼리를 보내는 메소드를 추적하여 로그를 남깁니다.TCP 요청 본문을 변경하지 않고 클라이언트 정보를 전달하는 프록시 프로토콜의 아이디어를 활용하여, TCP 본문의 첫 줄에 추적 문맥 정보를 심어 전파합니다.
프록시 프로토콜이 궁금하신 분은 영상의 18분50초를 보시면 될것 같습니다.
4. 추적 ID는 Client로 부터 발행
서버와의 통신이 시작되기 전부터 로직이 실행될 수 있는 프런트엔드 환경을 고려하여, 클라이언트(웹/앱)에서 Trace ID를 미리 생성하도록 합니다. 사용자가 경험한 웹 성능 지표, 크래시 발생 정보, 서버와의 통신 이력 등을 일관된 방식으로 확인하는 것이 가능해져, 사용자 경험 측면의 문제를 파악하는 데 유용합니다.
5. 분석 시스템과의 연계
토스페이먼츠는 분산 추적 시스템을 구축하면서 생성된 추적 정보를 사내에서 사용 중인 다양한 외부 분석 및 모니터링 시스템과 연동하여 효율을 높혔습니다. 로그에 기록된 글로벌 트레이스 ID나 트레이스 ID를 센트리 태그에 추가하여 인덱싱합니다. APM 시스템인 핀포인트를 사용하며, 핀포인트에서 사용하는 트랜잭션 ID를 연계합니다.
이제 결과적으로 복잡한 시스템 질의에 답변을 할 수 있게 됐습니다.
"사용자가 요청을 3번 보낸 것인지, 인프라 내부에서 리트라이 로직이 동작한 것인지 알고 싶습니다." 라는 요청이 와도 최초 요청 인입 지점의 로드밸런서 로그와 애플리케이션의 액세스 로그를 Trace ID를 기준으로 검색하여, 로그 건수가 일치하는지 확인해 실제 클라이언트가 요청을 3번 보냈다고 판단할 수 있습니다.
"특정 API를 수정하면 어떤 클라이언트가 영향을 받는지 알고 싶습니다." 라는 질문이 와도 액세스 로그와 동일한 Trace ID를 공유하는 요청들을 검색하고, 해당 요청을 보낸 서비스명을 추출하여 해당 API에 대한 의존성을 갖는 클라이언트 서비스 목록을 정확히 식별할 수 있습니다.
느낀점은 저희도 traceId와 SpanId라는 것으로 하나의 요청에서 어떤 부분에서 요청이 오래걸렸는지 또는 어떤 로직을 사용했는지 파악하고 있지만 클라이언트와 같은 traceId는 사용하고 있지 않아서 오류 추적이 어려웠던 경험이 있습니다. 이런 부분의 대해 통합을 하게 되면 앞으로 분산 추적이 더 쉬워질 것 같다는 생각을 하게 된거 같습니다.