LabHub
배우기 러닝패스 코스

注文が二度届き、一度は消えた

再試行が二度目の到着を生む — 冪等プロデューサーで防ぐ

LabHub 에서 이어서 보기

한국어 원문으로 표시합니다.

이 실습은 VM 에서 돕니다

우분투 VM 에 Apache Kafka 4.3.1 이 KRaft 단일 노드로 떠 있습니다 (localhost:9092). 도우미 kafka-lab-slow on|off 는 브로커로 가는 4KB 이상의 요청만 600ms 늦춰 프로듀서의 시간 초과와 재시도를 일으킵니다. 처음 뜨는 데 4분쯤 걸립니다.

목표

프로듀서가 응답을 받지 못해 재시도할 때 로그에 같은 레코드가 여러 벌 남는 것을 재현하고, 멱등 프로듀서가 그것을 한 벌로 만드는 것을 kafka-dump-log.sh 로 확인합니다. 그리고 멱등이 덮지 못하는 자리(프로듀서 세션을 넘는 재전송)도 봅니다.

왜 중요한가

설계 문서가 말하듯 프로듀서는 네트워크 오류를 만나면 그 오류가 메시지가 커밋되기 전에 난 것인지 뒤에 난 것인지 알 수 없습니다. 그래서 다시 보내고, 그러면 원래 요청이 성공했던 경우 로그에 두 번 남습니다 — at-least-once 입니다. 멱등 프로듀서는 브로커가 프로듀서마다 ID 를 주고 레코드마다 순번을 붙여 같은 순번을 걸러 내는 방식으로 이것을 막습니다. 4.x 에서는 기본으로 켜져 있지만(enable.idempotence 기본값 true), 설정 하나를 잘못 건드리면 조용히 꺼지고, 세션을 넘는 재전송은 애초에 덮지 않습니다. 이 셋을 눈으로 봐 두면 "두 번 도착" 신고를 받았을 때 어디를 볼지 압니다.

단계

  1. 토픽 payments 를 파티션 1개로 만들고 기본 설정으로 두 줄(pay-1, pay-2)을 넣으세요.
  2. kafka-dump-log.shpayments-0 의 세그먼트를 덤프해 /root/kafka/dump-default.txt 에 저장하세요. 배치의 producerId 가 -1 이 아니고 baseSequence 가 0 이어야 합니다(기본 프로듀서는 멱등입니다).
  3. enable.idempotence=false 로 한 줄(pay-3)을 더 넣고 다시 덤프해 /root/kafka/dump-noidem.txt 에 저장하세요. 새 배치는 producerId: -1 이어야 합니다.
  4. kafka-lab-slow on 을 켜고 토픽 payments-dup 을 만든 뒤, 5,000자짜리 한 줄(/root/kafka/big.txt)을 멱등을 끄고 request.timeout.ms=300, delivery.timeout.ms=2000, retries=2, max.block.ms=10000 으로 넣으세요. 프로듀서의 stderr 를 /root/kafka/dup-producer.log 에 남기고, 끝나면 kafka-lab-slow off. 로그에는 같은 레코드가 두 벌 이상 남아야 합니다.
  5. 같은 조건으로 토픽 payments-idem멱등을 켜고(enable.idempotence=true) 넣고 stderr 를 /root/kafka/idem-producer.log 에 남기세요. 재시도는 일어나지만 로그에는 한 벌만 남아야 합니다.
  6. slow 를 끈 상태에서 토픽 payments-apppay-77프로듀서를 두 번 따로 실행해 넣으세요(멱등은 켠 채). 덤프를 /root/kafka/two-sessions.txt 에 저장하세요 — 배치 두 개의 producerId 가 서로 달라야 합니다.
  7. /root/kafka/producer-report.txtdup_copies=<payments-dup 의 레코드 수>, idem_copies=<payments-idem 의 레코드 수>, two_sessions_copies=<payments-app 의 레코드 수>, acks_default=all, idempotence_default=true 다섯 줄을 쓰세요.

참고

기본 프로듀서로 두 건

토픽 payments 를 파티션 1개로 만들고 기본 설정으로 두 줄(pay-1, pay-2)을 넣으세요.

--create --topic payments --partitions 1 뒤에 printf 'pay-1\npay-2\n' | kafka-console-producer.sh .... 레코드 수는 kafka-get-offsets.sh 로 봅니다.

기본 프로듀서는 멱등이다

kafka-dump-log.shpayments-0 의 세그먼트를 덤프해 /root/kafka/dump-default.txt 에 저장하세요. 배치의 producerId 가 -1 이 아니고 baseSequence 가 0 이어야 합니다(기본 프로듀서는 멱등입니다).

kafka-dump-log.sh --files /var/lib/kafka/payments-0/00000000000000000000.log --print-data-log. 브로커가 프로듀서에 준 ID 와 레코드 순번이 배치 헤더에 그대로 적혀 있습니다 — 이것이 중복을 걸러 내는 재료입니다.

멱등을 끄면 ID 가 없다

enable.idempotence=false 로 한 줄(pay-3)을 더 넣고 다시 덤프해 /root/kafka/dump-noidem.txt 에 저장하세요. 새 배치는 producerId: -1 이어야 합니다.

--command-property enable.idempotence=false. 멱등이 꺼진 프로듀서는 ID 를 받지 않으므로 브로커가 같은 레코드가 다시 와도 알아볼 길이 없습니다.

재시도가 두 번째 도착을 만든다

kafka-lab-slow on 을 켜고 토픽 payments-dup 을 만든 뒤, 5,000자짜리 한 줄(/root/kafka/big.txt)을 멱등을 끄고 request.timeout.ms=300, delivery.timeout.ms=2000, retries=2, max.block.ms=10000 으로 넣으세요. 프로듀서의 stderr 를 /root/kafka/dup-producer.log 에 남기고, 끝나면 kafka-lab-slow off. 로그에는 같은 레코드가 두 벌 이상 남아야 합니다.

요청이 300ms 안에 답을 못 받으면 프로듀서는 같은 배치를 다시 보내고(retries), 브로커는 늦게 도착한 원래 요청도 늦게 도착한 재시도도 전부 씁니다. 프로듀서는 결국 실패라고 보고하지만 로그에는 세 벌이 있습니다. kafka-get-offsets.sh 로 세어 보세요.

멱등 프로듀서는 한 벌만 남긴다

같은 조건으로 토픽 payments-idem멱등을 켜고(enable.idempotence=true) 넣고 stderr 를 /root/kafka/idem-producer.log 에 남기세요. 재시도는 일어나지만 로그에는 한 벌만 남아야 합니다.

4단계와 똑같이 slow 를 켜고 보내되 enable.idempotence=true 만 바꿉니다. 재시도된 배치는 같은 순번을 달고 오므로 브로커가 걸러 냅니다. 프로듀서의 로그에는 여전히 REQUEST_TIMED_OUT 이 찍힙니다 — 재시도는 있었고 중복만 없는 것입니다.

세션이 다르면 멱등이 덮지 못한다

slow 를 끈 상태에서 토픽 payments-apppay-77프로듀서를 두 번 따로 실행해 넣으세요(멱등은 켠 채). 덤프를 /root/kafka/two-sessions.txt 에 저장하세요 — 배치 두 개의 producerId 가 서로 달라야 합니다.

printf 'pay-77\n' | kafka-console-producer.sh ... 를 두 번. 프로세스가 다르면 브로커가 새 프로듀서 ID 를 주고 순번도 0 부터라, 브로커 눈에는 다른 레코드입니다. 애플리케이션이 실패 뒤 다시 보내는 것은 이 모양이고, 그것을 막는 것은 멱등 프로듀서가 아니라 주문 번호 같은 업무 키입니다.

세 토픽의 레코드 수로 정리한다

/root/kafka/producer-report.txtdup_copies=<payments-dup 의 레코드 수>, idem_copies=<payments-idem 의 레코드 수>, two_sessions_copies=<payments-app 의 레코드 수>, acks_default=all, idempotence_default=true 다섯 줄을 쓰세요.

세 숫자는 kafka-get-offsets.sh 의 마지막 필드입니다. 나머지 둘은 프로듀서 설정 문서의 기본값입니다 — 채점기는 세 숫자를 지금 브로커에서 다시 세어 비교합니다.