Contribution · Spring Kafka · Kotlin Coroutines

Spring Kafka — 두 레코드 silent 유실 (GH-4504)

같은 파티션의 두 레코드가 같이 비동기 실패하면 earlier-offset 레코드가 한 번만 invoke되고 recoverer에 도달하지 못해 silent 유실되는 회귀. PR #4505로 수정 머지됐고, 후속으로 head-of-line amplification을 linear로 bound하는 PR #4512와 4.0.x backport 회귀를 풀어주는 PR #4517이 진행 중입니다.

문제

PR #4469 머지 후 같은 reporter가 후속 이슈 신고: "두 개 이상의 레코드가 같은 파티션에서 실패하면 r1이 silent 유실되고 r2만 retry/recover된다". handleAsyncFailure가 같은 파티션 실패들을 도착 순서로 하나씩 처리하면서 각 호출이 독립적인 seek를 등록 → 뒤 호출의 seek가 앞 호출의 seek를 덮어써서 r1이 영영 재배달되지 않고 committed offset이 그 너머로 advance → 영구 유실.

수정 (PR #4505, merged 2026-06-24, commit 87710ea)

같은 파티션의 async failure들을 (topic, partition, offset) 순으로 정렬하고, head record가 retry 들어가면 같은 파티션의 뒤 레코드는 큐에서 drop. 등록된 seek가 다음 poll에서 자연 재배달하도록 맡김.

후속 — amplification bound (PR #4512, open)

reporter가 burst 시 invocation이 N·(N+2) (quadratic)로 증가하는 head-of-line amplification을 측정. 메인테이너 요청으로 PR을 분리, asyncRetryOffsets + seenMultiAttemptRetry 게이트로 dispatch loop skip + manual seek를 적용해 invocation을 N·(n+2)−1 (linear)로 bound. @RetryableTopic 시나리오 회귀 없이.

4.0.x backport fix (PR #4517, open)

PR #4505가 4.0.x로 backport됐을 때 main에만 있던 precursor commit이 누락돼 테스트 3개가 깨졌습니다. fix-forward로 cherry-pick.

관련 자료