당연하게도 서버가 터진다 ㅎㅎ
썸네일은 우리 Grafana 대시보드 화면이다.
orchestrator-app 의 컨테이너 로그 파일(-json.log)이 295GB까지 커졌다.
이렇게 되면서 모든 docker가 unhealthy가 되고 서비스가 모두 먹통이 되어버렸다..
Orchestrator 서비스에 있는 DTO를 통해 보낸 메세지가 Stock 서비스에서는 Orchestrator.dto의 형식이 뭔지 몰라서 역직렬화가 실패하였다.
그런데 우리의 설정이 'JsonDeserializer' 라고 되어있었고, 이 설정 때문에 Kafka Consumer의 역직렬화가 실패 하면서 Consumer는 같은 오프셋의 메시지를 계속 반복 처리하려고 시도하는 것이었다.
그렇게 시도할 때마다 DeserializationException 에러가 발생하고 log에 폭발적으로 쌓이면서 EC2 서버가 다운되어버렸다...
근본적으로 default.type을 맞춰 주어 역직렬화의 문제를 해결했다.
이후 문제를 방지하기 위해 JsonDeserializer를 Spring Kafka에서 제공하는 ErrorHandlingDesrializer로 수정해주었다. 이렇게 하면 역직렬화가 실패하여도 null로 처리하고 반복시도를 하지 않게 된다.
또한 컨슈머에서 null을 감지하면, 메시지를 skip 하고 DLT(Dead Letter Topic)으로 publish 하여 backoff/skip 전략을 통해 무한 재시도를 방지하였다.
또한 Grafana + Prometheus 기반 디스크 사용량 모니터링 대시보드 구축을 통해 주기적인 점검 체계를 수립하였습니다.
일단 급한 해결책이었기 때문에 수정, 보완해야 할 부분들이 많다.
현재는 default.type
으로 고정된 DTO 타입을 지정하는 방식을 사용하고 있어 굉장히 불편하다.
spring.json.value.default.type: com.solsol.orchestrator.dto.TransactionConsumeEvent
문제점:
하나의 Consumer가 여러 타입의 이벤트를 처리하기 어려움
새로운 이벤트 타입 추가 시마다 Consumer 설정 변경 필요
Producer와 Consumer 간 타입 불일치를 컴파일 타임에 잡을 수 없음
개선 방향:
만약 조직이 커진다면 내부 Maven/Gradle 저장소에 contracts 라이브러리를 배포해서 공통 저장소에서 DTO를 사용하도록 수정할 것 같다.
📦 solsol-kafka-contracts (공통 모듈)
├── TransactionConsumeEvent.java
├── PaymentEvent.java
└── OrderEvent.java
↓ Nexus/Artifactory 배포
Producer Service ←→ Consumer Service
(같은 DTO 클래스 사용)
이렇게 하면:
Producer와 Consumer가 같은 DTO 정의를 사용하여 타입 안정성 확보
버전 관리를 통해 하위 호환성 관리 가능
IDE의 자동완성, 리팩토링 지원으로 개발 생산성 향상
현재 모든 패키지를 허용하고 있어 보안상 매우 위험한 상태다.
spring.json.trusted.packages: "*" # 모든 패키지 허용
역직렬화 공격에 노출될 수 있고, 악의적인 payload를 통해 임의의 클래스 인스턴스화가 가능하다. (Spring 공식 문서에서도 프로덕션 환경에서 절대 사용 금지 권고)
# 신뢰할 수 있는 패키지만 명시적으로 지정
spring.json.trusted.packages: >
com.solsol.orchestrator.dto,
com.solsol.common.events,
com.solsol.shared.models
Kafka Consumer의 역직렬화 실패가 무한 재시도를 유발하여 295GB의 로그가 쌓이면서 전체 서비스가 다운되는 심각한 장애를 경험했다.
핵심 원인:
긴급 조치:
남은 과제:
현재 해결책은 임시방편이다. 보안 취약점(trusted.packages: "*"
)과 타입 검증 우회(use.type.headers: false
) 문제를 해결하고, 장기적으로는 공통 DTO 모듈 구축 또는 Schema Registry 도입을 통해 근본적인 타입 안정성을 확보해야 한다.
이번 장애를 통해 에러 핸들링 전략의 중요성과 모니터링 체계 구축의 필요성을 뼈저리게 느꼈다. 단순히 기능 구현에만 집중할 게 아니라, 장애 상황에 대한 대비책을 미리 마련하는 것이 얼마나 중요한지 깨달았다.