Kafka에서 Dead Letter Queue를 모니터링하는데 필요한 기본 개념을 정리한다.
1. Dead Letter Queue(DLQ)란?
DLQ의 개념
Dead Letter Queue(DLQ)는 메시징 시스템에서 처리 실패한 메시지를 관리하는 보조 큐(Queue) 역할을 한다.
Kafka, RabbitMQ, AWS SQS 같은 메시지 큐 시스템에서 많이 사용되며, 실패한 메시지를 따로 보관하여 재처리하거나 분석하는 역할을 한다. 메시지 처리 실패는 네트워크 장애, 메시지 손상, 대상 시스템의 비정상 상태 등 다양한 이유로 발생할 수 있다. 메시지가 정상적으로 전달되지 않을 경우, 대신 DLQ로 메시지를 redirect 시켜 유실되거나 무한 재시도되는 것을 방지한다. 이를 통해 주요 메시지 처리 흐름의 안정성과 효율성을 유지할 수 있다. DLQ는 메시지 처리 오류를 식별하고 시스템 전체가 중단되지 않도록 하는 데 중요한 역할을 한다.
문제가 있는 메시지를 별도로 저장하여 개발자와 운영자가 분석 및 해결할 수 있도록 하며, 이를 통해 메시지 흐름이 중단되지 않고 시스템이 원활하게 운영될 수 있도록 돕는다.
DLQ가 필요한 이유
Dead Letter Queue(DLQ)는 메시지 처리 실패로 인한 시스템 장애를 방지하고 안정성을 유지하는 데 필수적이다.
1. 에러 격리 및 시스템 안정성 유지
- 처리 실패한 메시지를 별도로 보관하여, 하나의 오류가 전체 메시지 큐를 방해하지 않도록 한다.
- 이를 통해 메시지 흐름이 중단되지 않고 안정적인 운영이 가능하다.
2. 트러블슈팅 및 장애 분석
- 실패한 메시지를 DLQ에 저장하면, 원인을 분석하고 문제를 해결하는 데 도움이 된다.
- 반복적인 오류 패턴을 파악하여 시스템을 개선할 수 있는 기회가 된다.
3. 성능 최적화 및 재처리 가능성 확보
- 실패한 메시지를 무한 재시도하는 대신 DLQ로 이동시켜 주요 메시지 처리 성능을 유지할 수 있다.
- DLQ에 보관된 메시지는 필요할 때 재처리(Replay)하거나 수동으로 복구 가능하다.
2. 필요한 Kafka 기본 개념
Kafka의 기본 구성 요소
토픽(Topic)
카프카에 저장되는 메시지는 토픽(Topic) 단위로 분류된다. 데이터베이스의 테이블이나 파일시스템의 폴더가 비슷한 개념이다.
토픽은 Producer가 메시지를 보내고 Consumer가 메시지를 읽는 대상이 된다.
파티션(Partition)
Kafka Topic은 여러 개의 Partition으로 나눠진다. 각 파티션은 독립적인 로그로 동작하며, 파티션에 메시지가 쓰여질 때는 추가만 가능(append-only)한 형태로 쓰여진다. 읽을 때는 맨 앞부터 제일 끝까지의 순서로 읽힌다. 토픽에 여러 개의 파티션이 있는 만큼 토픽 안의 메시지 전체에 대해 순서는 보장되지 않으며, 단일 파티션 안에서만 순서가 보장된다. 또한, 파티션은 복제될 수 있다. 서로 다른 서버들이 동일한 파티션의 복제본을 저장하고 있기 때문에 서버 중 하나에 장애가 발생한다고 해서 읽거나 쓸 수 없는 상황이 벌어지지는 않는다.
프로듀서(Producer)
프로듀서는 새로운 메시지를 생성하며, 발행자(Publisher) 또는 작성자(Writer)라고도 부른다. 메시지는 특정한 토픽에 쓰여진다. 기본적으로 프로듀서는 메시지를 쓸 때 토픽에 속한 파티션들 사이에 고르게 나눠서 쓰도록 되어있다.
컨슈머(Consumer)
Kafka Topic에서 메시지를 읽어와 처리하며, 구독자(Subscriber) 또는 독자(Reader)라고도 한다. 컨슈머는 1개 이상의 토픽을 구독해서 여기에 저장된 메시지들을 각 파티션에 쓰인 순서대로 읽어 온다. 컨슈머는 메시지의 오프셋(offset)을 기록함으로써 어느 메시지까지 읽었는지를 유지한다. (오프셋은 지속적으로 증가하는 정수값. 카프카가 메시지를 저장할 때 각각의 메시지에 부여해 주는 또 다른 메타데이터)
브로커(Broker)
Kafka 클러스터를 구성하는 서버 노드로 여러 개의 Broker가 함께 동작하여 메시지를 저장하고 분산 처리한다. 클러스터 내에서 특정 Broker가 리더(Leader) 역할을 수행하고, 나머지는 팔로워(Follower) 역할을 맡는다.
Kafka Consumer의 동작 방식
Offset 관리
Kafka에서 Consumer는 마지막으로 읽은 메시지의 위치(Offset)를 기록하여 중복 소비를 방지한다. Offset을 커밋(Commit)해야만 Kafka가 메시지를 성공적으로 처리했다고 판단한다.
Auto Commit(자동 커밋)
- enable.auto.commit=True 설정 시 Kafka가 일정주기로 자동 Offset 커밋
- 메시지가 실제 처리되기 전 커밋될 수 있어 데이터 유실이 발생할 수 있다.
Manual Commit (수동 커밋)
- enable.auto.commit=False 설정 후, 메시지 처리 완료 후 consumer.commit() 호출한다.
- 메시지 처리 성공 후에만 커밋하여 데이터 유실을 방지한다.
DLQ에서 enable.auto_commit=False를 설정해야 하는 이유
❌ Auto Commit을 사용하면 DLQ에서 문제가 발생할 수 있음
- Consumer가 메시지를 받자마자 자동으로 Offset을 커밋하면, 처리 실패 후에도 Kafka는 메시지를 성공적으로 소비한 것으로 인식하기 때문이다.
- 이 경우 실패한 메시지가 DLQ로 가지 못하고 사라질 위험이 있다.
✅ Manual Commit(수동 커밋) 방식이 필요한 이유
- enable.auto_commit=False 설정 후, 정상 처리된 메시지만 수동으로 Offset을 커밋해야 한다.
- DLQ로 메시지를 보낸 후에도 Offset을 커밋해야 동일한 메시지를 무한 재처리하는 문제를 방지할 수 있다.
3. Kafka에서 Dead Letter Queue(DLQ)를 구현 단계
Kafka에서 DLQ 시스템을 구축하려면 다음과 같은 구성 요소가 필요하다.
🔹 Producers: Kafka 토픽으로 메시지를 전송하는 애플리케이션 또는 시스템
🔹 Consumers: Kafka 토픽에서 메시지를 읽고 처리하는 애플리케이션
🔹 Error Handler: 메시지 처리 중 예외가 발생하면 이를 감지하고 DLQ로 보낼지 결정하는 오류 처리 로직
🔹 DLQ Topic: 처리 실패한 메시지를 저장하는 전용 Kafka 토픽
Kafka에서 Dead Letter Queue(DLQ)는 처리 실패한 메시지를 저장하는 전용 Kafka 토픽을 생성하여 구현하는 것이 일반적이다.
메시지 처리 중 실패가 발생하면, Consumer 애플리케이션에서 오류 핸들러(Error Handler)가 호출되며, 이 핸들러가 실패한 메시지를 DLQ 토픽으로 전송할 수 있다.
일부 시스템에서는 지정된 횟수만큼 재시도(Retry) 한 후에도 실패할 경우 DLQ로 메시지를 이동하는 재시도 로직(Retry Logic)을 추가로 구현하기도 한다.
1) DLQ용 Kafka 토픽 생성
가장 먼저, 처리 실패한 메시지를 저장할 별도의 Kafka 토픽(DLQ Topic)을 생성해야 한다.
📌 Kafka CLI를 사용한 DLQ 토픽 생성 예시
kafka-topics.sh --create --topic orders_dlq --bootstrap-server localhost:9092 --partitions 3 --replication-factor 1
2) 오류 처리 로직(Error Handling Logic) 구현
Kafka Consumer에서 메시지를 처리하는 도중 예외가 발생하면 이를 감지하고 DLQ로 메시지를 전송해야 한다. 이를 위해 오류 감지 및 예외 처리 로직을 구현해야 한다.
- 역직렬화 오류(Deserialization Error)
- Consumer가 메시지를 읽을 때, 예상한 데이터 형식과 다르면 발생
- 예: JSON 데이터를 기대했지만, 잘못된 포맷이 들어온 경우
- 스키마 검증 오류(Schema Validation Error)
- Consumer가 특정 스키마(예: Avro, Protobuf 등)를 기대했지만, 필수 필드가 없거나 데이터 형식이 맞지 않는 경우
- 비즈니스 로직 오류(Business Logic Exception)
- 예를 들어, 주문 데이터를 처리할 때 총 주문 금액이 음수인 경우 예외 발생
3) 라우팅 로직(Routing Logic) 정의
Kafka에서 DLQ로 메시지를 보낼 때, 단순히 오류가 발생했다고 바로 DLQ로 이동시키는 것이 아니라, 재시도(Retry) 정책을 먼저 적용한 후 DLQ로 보내는 것이 일반적이다.
- 특정 횟수(예: 3회)만큼 재시도한 후에도 실패하면 DLQ로 이동
- 일정 시간 안에 처리되지 않는 메시지를 DLQ로 보냄 (타임아웃 설정)
- 특정 유형의 오류(예: 역직렬화 오류)는 즉시 DLQ로 이동, 비즈니스 로직 오류는 재시도 후 DLQ로 이동
4) 오프셋 관리(Offset Management)
Kafka에서 DLQ를 사용할 때, 오프셋을 적절히 관리하지 않으면 동일한 메시지가 중복 처리될 가능성이 있다.
Kafka Consumer의 오프셋을 관리하는 방법
- enable_auto_commit=False를 설정하여 수동으로 커밋
- DLQ로 보낸 메시지도 오프셋을 커밋하여 무한 재시도 방지
- DLQ에서 재처리할 때, 기존 오프셋을 유지하면서 원본 토픽으로 복구
이를 통해 Kafka Consumer가 동일한 메시지를 반복해서 재처리하지 않고, DLQ에서 필요한 경우만 재처리할 수 있도록 제어할 수 있다.