Flink 잡이 재시작하면 Kafka 결과는 왜 두 번 들어갈까?
Flink 잡이 죽었다 다시 뜨면 마지막 체크포인트 이후 구간을 다시 처리하고, at-least-once 싱크는 그 결과를 출력 토픽에 한 번 더 씁니다. Flink 2.2와 Kafka 4.3에서 TaskManager를 강제로 종료해 중복을 재현하고, exactly-once 싱크를 read_committed 컨슈머로 읽으면 중복이 사라지는지 직접 확인합니다.
Flink 잡이 죽었다 다시 뜨면 마지막 체크포인트 이후 구간을 다시 처리하고, at-least-once 싱크는 그 결과를 출력 토픽에 한 번 더 씁니다. Flink 2.2와 Kafka 4.3에서 TaskManager를 강제로 종료해 중복을 재현하고, exactly-once 싱크를 read_committed 컨슈머로 읽으면 중복이 사라지는지 직접 확인합니다.
서비스마다 DB가 나뉘면 결제와 재고 차감을 한 트랜잭션으로 묶을 수 없어서, 실패한 단계 앞의 작업은 보상 트랜잭션으로 되돌려야 합니다. Kafka 4.3과 PostgreSQL 17로 2PC가 남기는 잠금을 보고, 오케스트레이션 Saga로 환불 흐름과 보상 실패의 재시도를 직접 확인합니다.

consumer lag가 0이어도 컨슈머가 모두 죽었거나, 처리 중에 멈췄거나, 그룹이 사라졌을 수 있습니다. Kafka 4.3에서 이 경우들을 재현해 그룹 상태와 멤버 수, 로그 끝과 commit 증가량으로 구분하는 법을 확인합니다.

브로커가 죽으면 컨트롤러가 heartbeat 만료로 장애를 확정한 뒤에야 리더가 바뀝니다. Kafka 4.3 브로커 세 대에서 SIGKILL과 정상 종료를 세 번씩 일으켜 감지, 선출, 메타데이터 갱신 시간과 프로듀서·컨슈머가 멈춘 시간을 재고, 복구 뒤 선호 리더로 되돌리는 과정까지 살펴봅니다.
DB 커밋과 Kafka 전송은 한 트랜잭션으로 묶이지 않아, DB를 먼저 커밋하고 전송이 실패하면 이벤트가 사라집니다. Kafka 4.3과 PostgreSQL 17로 이 실패를 재현하고, outbox 테이블과 폴링 릴레이를 직접 만들어 릴레이가 죽을 때 생기는 중복까지 확인합니다.
Kafka Connect는 DB나 파일과 Kafka 사이의 데이터 이동을 설정만으로 맡지만, 소스 offset 저장 주기를 모르면 중복의 원인을 찾기 어렵습니다. Kafka 4.3 분산 모드 워커를 offset 저장 전에 강제 종료해 중복을 재현하고, exactly.once.source.support로 중복이 사라지는지 확인합니다.
min.insync.replicas는 acks=all 쓰기를 받아 줄 ISR 크기의 바닥입니다. Kafka 4.3 브로커 세 대에서 브로커를 한 대씩 빼며 NOT_ENOUGH_REPLICAS 거절을 확인하고, min.insync.replicas=1에서 성공 응답을 받은 메시지가 unclean 선출로 사라지는 과정을 재현합니다.
프로듀서와 컨슈머가 따로 배포되면 한 토픽에 여러 스키마 버전의 메시지가 섞입니다. Kafka 4.3과 Schema Registry로 메시지 앞 5바이트의 스키마 ID를 확인하고, 기본 모드 BACKWARD가 필드 추가와 삭제, 의미 변경을 어떻게 판정하는지와 그때 기존 컨슈머가 어떻게 되는지 재현합니다.
처리 결과를 다른 토픽에 쓰는 컨슈머가 offset commit 전에 죽으면 재시작 후 같은 결과가 출력 토픽에 두 번 남습니다. Kafka 4.3에서 이 중복을 재현하고 트랜잭션으로 결과와 offset을 함께 커밋해 비교한 뒤, 그래도 처리 코드는 다시 실행된다는 점을 확인합니다.
프로듀서가 받는 성공 응답은 acks 설정에 따라 리더 한 대의 기록만 뜻할 수도 있습니다. Kafka 4.3 브로커 세 대에서 acks=1로 성공을 받은 메시지가 리더 장애로 사라지는 과정과 acks=all의 차이를 재현하고, min.insync.replicas가 필요한 이유를 확인합니다.
브로커가 메시지를 저장했는데 응답만 사라지면 프로듀서는 같은 메시지를 다시 보내고, 토픽에는 두 번 남습니다. Kafka 4.3에서 이 중복을 재현해 멱등 프로듀서의 PID와 순번이 어떻게 막는지 확인하고, 실패를 받은 뒤 애플리케이션이 다시 보내는 것처럼 막지 못하는 경우를 짚습니다.
처리 후 commit하는 Kafka 컨슈머는 메시지를 잃지 않는 대신 같은 메시지를 다시 받을 수 있고, 결제 API 호출이나 포인트 적립은 그대로 두 번 실행됩니다. Kafka 4.3과 PostgreSQL에서 이중 결제와 이중 적립을 재현하고, 멱등 키와 처리 기록을 같은 트랜잭션에 묶는 처리 경계로 막는 과정을 확인합니다.
리밸런스로 파티션을 넘겨받은 컨슈머는 commit된 offset부터 읽기 때문에, 앞 담당자가 commit하지 않은 처리분은 다시 처리됩니다. Kafka 4.3에서 on_revoke commit 유무와 max.poll.interval.ms 초과를 직접 재현하며 중복이 생기는 과정을 확인합니다.
컨슈머가 실패한 메시지를 그 자리에서 재시도하면 같은 파티션의 뒤 메시지가 모두 멈추고, 재시도 토픽으로 빼면 파티션은 흐르지만 같은 키의 순서가 뒤집힐 수 있습니다. Kafka 4.3에서 그 자리 재시도의 lag 증가와 재시도 토픽·DLQ 구성을 직접 만들어 확인합니다.
Kafka가 지키는 것은 파티션에 저장된 순서까지이고, 처리 완료 순서와 재시도 중 저장 순서는 애플리케이션 설정과 코드가 지켜야 합니다. 스레드 풀 처리와 프로듀서 재시도로 같은 파티션 안에서 순서가 깨지는 과정을 Kafka 4.3에서 재현하고, 키 단위 직렬 처리와 멱등성으로 바로잡습니다.
파티션마다 들어오는 메시지 수가 같아도 특정 키의 처리 비용이 크면 그 파티션만 lag가 쌓이고, 이런 핫 파티션은 브로커의 건수나 바이트 지표에 드러나지 않습니다. Kafka 4.3에서 파티션별 lag와 로그 크기, 컨슈머가 잰 키별 처리 시간으로 원인 키를 직접 찾아봅니다.
consumer lag는 로그 끝과 컨슈머 그룹이 commit한 위치 사이에 남은 메시지 수이고, 몇 초 늦었는지는 유입 속도와 처리 속도에 따라 달라집니다. Kafka 4.3에서 같은 lag 200이 26.0초와 7.0초의 대기가 되는 경우를 재현하고, lag를 숫자가 아닌 추세로 읽는 법을 확인합니다.
Kafka는 컨슈머가 메시지를 읽어도 지우지 않고, 보관 정책에 따라 세그먼트 단위로 지웁니다. Kafka 4.3에서 retention.ms로 세그먼트가 지워지는 과정과, 멈춰 있던 컨슈머가 이미 지워진 구간을 만났을 때 auto.offset.reset에 따라 메시지를 놓치는 과정을 직접 확인합니다.
같은 group.id를 쓰는 컨슈머들은 파티션을 나눠 읽기 때문에, 파티션보다 많은 컨슈머는 할당 없이 대기합니다. 파티션 3개짜리 토픽에서 컨슈머를 5개까지 늘리고 컨슈머 수별 처리 시간을 Kafka 4.3에서 직접 비교합니다.
Kafka 컨슈머 그룹은 파티션마다 다음에 읽을 offset을 commit해 두고, 재시작하면 그 위치부터 다시 읽습니다. commit을 처리 전에 하느냐 후에 하느냐에 따라 메시지가 중복되거나 빠지는 과정을 Kafka 4.3에서 직접 재현합니다.