AI Briefing

AOP와 MDC로 구현하는 보급형 APM: 인터페이스 이력 관리부터 호출 추적까지

·2025.03.05 09:19

AOP, MDC, Kafka Interceptor로 인터페이스 이력과 호출 스택을 추적한다.

인터페이스 이력은 API, Message Queue, Kafka처럼 시스템 사이를 오가는 요청과 응답의 히스토리다. 장애 분석, 성능 최적화, 리플레이, 복구, 사용 패턴 분석까지 가능해 운영과 개발 전반에 도움이 된다.

핵심 추적값은 MDC(Mapped Diagnostic Context) 로 관리했다. MDC는 스레드별로 메타 정보를 보관하는 구조라 TRACE_ID 같은 식별자를 유지하기 좋고, Spring Cloud Sleuth 와 함께 쓰면 여러 MS를 오가도 추적 아이디를 이어갈 수 있다.

시스템은 크게 세 단계로 동작한다.

  • 인터페이스 종류와 메타정보를 판단하고 MDC에 추적 아이디와 스팬 아이디를 저장한다.
  • 메서드 호출 전후에 호출 스택과 이력 정보를 ConcurrentHashMap 에 누적한다.
  • 요청이 끝나면 finally에서 저장 여부를 판단한 뒤, Map과 MDC를 정리해 메모리 누수를 막는다.

구현에는 Template Method Pattern, Decorator Pattern, Chain of Responsibility Pattern 을 함께 썼다. Provider들이 트래픽을 분류해 API, Internal API, External API, Kafka Consumer, Kafka Producer 같은 호출 유형을 판별하고, 데코레이터들이 시간 측정, 호출 스택 기록, 결과 저장을 분담한다.

비동기 처리와 가상 스레드까지 고려해야 해서 시행착오가 많았다. 처음에는 일반 ThreadLocal만으로는 스레드가 바뀌는 순간 추적이 끊겼고, InheritableThreadLocal 은 깊은 복사와 얕은 복사 모두 호출 스택 누락이나 메모리 누수 문제를 만들었다.

이후 BeanPostProcessorTaskDecorator 로 비동기 스레드에 컨텍스트를 전파했지만, 자식 스레드에서 값을 지우면 부모 스레드의 값까지 영향을 받는 문제가 남았다. 이를 해결하기 위해 변경이 잦은 데이터는 ConcurrentHashMap 으로, 변경이 없는 추적 정보는 MDC 로 분리했다.

HTTP 요청은 Sleuth와 MDC로 추적 아이디를 유지할 수 있었지만, Kafka 이벤트는 자동 전파가 되지 않았다. 그래서 Kafka Interceptor 로 producer는 header에 TRACE_ID를 넣고, consumer는 header에서 꺼내 MDC에 다시 넣는 방식으로 일관성을 확보했다.

마지막으로 동일한 TRACE_ID만 쓰면 같은 MS에서 재전송 시 DB PK Constraint Error 가 발생할 수 있어, 스팬 아이디 를 복합키에 포함해 요청 구간을 구분했다. 결국 이력 저장, 호출 추적, 멀티스레드 안전성, 메모리 관리까지 묶어 하나의 보급형 APM에 가까운 구조를 완성했다.

이 요약은 원문 이해를 돕기 위한 큐레이션입니다. 저작권은 원저작자에게 있으며, 정확한 내용과 맥락은 원문을 확인하세요.

요약 오류, 출처 표기 문제, 삭제 요청은 문의 · 건의로 알려주세요.