Feature proposal · Spring Kafka · enhancement
Spring Kafka Issue #4523 — First-class DLT replay API
@RetryableTopic 파이프라인에서 DLT 에 들어간 레코드를 운영자가 직접 손대지 않고 framework가 자동으로 다시 retry chain으로 흘려주는 first-class API. 다시 실패하면 parking queue로 보내 human-only 검토 대상으로 격리. 전부 opt-in.
배경
@RetryableTopic chain이 retry 소진되면 레코드는 DLT 에 안착하고 거기서 멈춤. 그 다음 — "root cause 가 수정된 뒤 DLT 레코드를 다시 흘려보내기" — 는 현재 100% 운영자 수동 작업: 콘솔에 들어가서 레코드 persist, 직접 producer 호출, 헤더 맞추기, 빌어서 원본 토픽으로 재전송.
DLT 에 어떤 레코드가 자동 재처리 안전 대상이고 어떤 건 사람이 봐야 하는지 결정하는 정책, 그리고 재처리도 또 실패한 진짜 poison-pill 의 종착지(parking queue) 까지 — 모두 팀마다 ad-hoc 으로 다시 짜고 있음. 헤더 컨벤션 / cap / parking 시맨틱이 팀마다 일관되지 않음.
2022년 #2172 가 같은 주제로 열렸다가 "직접 DestinationTopicResolver 로 만드세요" 로 닫혔지만, 이후로도 동일 use case가 SO·Slack·discussions 에 반복 등장. framework 차원의 contract 가 필요한 시점.
흐름
Main Topic
|
v
Consumer
|
+--> DLT (retries exhausted)
|
v
DLT Reprocessor (new — automated)
|
+--------+--------+
| |
Success Still Fail
| |
Main Topic Parking Queue
|
v
human-only review
(terminal)
제안 요지 — 4 dimensions
- Replay entry point —
DltReplayService빈.ConsumerRecord또는(key, value, headers, originalTopic)tuple 받아서DestinationTopicResolver가 resolve한 retry chain entry로 republish. - Eligibility policy —
DltReplayPolicy. 어떤 DLT 레코드가 auto-replay 대상인지 결정. 빌트인:ALL/EXCEPTION_ALLOWLIST/EXCEPTION_DENYLIST/HEADER_MARKER(운영자가 콘솔에서kafka_dlt-replay-ready마킹) /MANUAL_ONLY(기본) / 커스텀Predicate. - Cap + parking topic —
max-replay-attempts(default 1) 카운터를kafka_dlt-replay-attempt헤더에 stamp. 초과 시parkingTopic(default<original>-DLT-PARKED) 으로 라우팅. 종착지, human-review-only. - Audit hook —
DltReplayAuditorSPI.onReplay/onParked/onReplayFailed. 기본은 INFO 로깅만, 사용자가 티켓팅·감사 시스템과 연동.
API sketch
public interface DltReplayService {
CompletableFuture<SendResult<?, ?>> replay(ConsumerRecord<?, ?> dltRecord);
CompletableFuture<SendResult<?, ?>> replay(byte[] key, byte[] value, Headers headers, String originalTopic);
}
public interface DltReplayPolicy {
boolean isEligible(ConsumerRecord<?, ?> dltRecord);
}
public interface DltReplayAuditor {
void onReplay(ConsumerRecord<?, ?> dltRecord, SendResult<?, ?> result);
void onParked(ConsumerRecord<?, ?> dltRecord, int attempts);
void onReplayFailed(ConsumerRecord<?, ?> dltRecord, Throwable cause);
}
대안과 비교
- "DIY 패턴만 docs 화" — #2172의 결론. 팀마다 같은 plumbing 재구현, 헤더/cap/parking 시맨틱 비일관.
- "DLT 자체에 타이머 두고 N분 지난 거 자동 replay" — root-cause 해결은 사람 신호이지 시계 신호가 아님. 시계 기반 replay = retry-storm 위험.
- "cap 없이 무한 replay" — 진짜 poison-pill 이 영원히 순환, DLT 가 정착하지 않고, ops 가 transient/permanent 구분 불가.
- "클러스터 공통 graveyard topic 하나" — per-listener topology 보장 깨짐. per-listener parking (DLT 패턴 그대로 미러) 가 단순.
구현 로드맵
- Phase 1 —
DltReplayService+DltReplayPolicy+DltReplayAuditor+ 헤더 상수 + autoconfiguration 토글. parking 아직 없음 (cap=1 시 두 번째 실패는 그냥 다시 DLT, status quo). - Phase 2 — parking topic +
onParkedaudit + auto-create (opt-in). - Phase 3 (별도 issue) — REST facade autoconfiguration (manual replay 용).
maintainer 협의 후 Phase 1 + 2 PR 진행 의향 있음. PR body에 #2172 링크 + 본 issue 링크 첨부 예정.
제출 현황
- Issue #4523 First-class DLT replay API — feature proposal (type: enhancement) open · awaiting triage
- Issue #2172 (2022) Prior discussion — closed without first-class implementation closed (referenced)