소개
그룹은 토픽의 파티션을 여러 컨슈머에게 나눌 수 있습니다. 안정된 그룹 내에서 각 파티션에는 활성 소유자가 하나 있으며, 다른 그룹은 같은 보관 레코드를 독립적으로 읽을 수 있습니다. 덕분에 모든 애플리케이션이 하나의 전역 읽기 위치를 놓고 경쟁하지 않으면서 작업을 나눌 수 있습니다.
하나의 그룹에서 계속 실행되는 콘솔 클라이언트 두 개를 시작하고, 발행 전에 실제 할당을 확인한 뒤 별도 그룹의 관찰 결과와 비교합니다. 멤버 하나가 나가면 남은 멤버가 두 파티션을 모두 소유할 때까지 기다린 후 소량의 데이터를 추가로 전송합니다. 준비된 발행 도구는 실험 중 두 파티션에 모두 작업이 생기도록 각 파티션을 명시적으로 지정하며, 실제 브로커 전달 여부도 확인합니다.
이 독립된 실습에는 파티션이 두 개인 토픽, 공개된 이벤트 데이터, 제한 시간 동안 상태를 읽기만 하는 대기 도구가 제공됩니다. 기본적인 터미널 사용법이면 충분하며 제공 도구의 Python 코드를 수정할 필요는 없습니다. 콘솔 클라이언트는 마지막 단계에서 중지할 때까지 백그라운드에서 실행됩니다. 프로세스 변수를 계속 사용할 수 있도록 같은 터미널 셸을 유지하세요.
멤버 두 개를 시작하고 소유권이 안정될 때까지 기다리기
이 단계에서는 그룹에 작업을 주기 전에 할당을 관찰합니다. 준비된 토픽에는 파티션 두 개가 있고 레코드는 없습니다. 메타데이터를 확인하세요.
cd /home/labex/project
kafka-topics.sh --bootstrap-server localhost:9092 --describe --topic orders-shared
다음 클라이언트들은 같은 그룹인 orders-team을 사용합니다. --command-property는 각 설정값을 컨슈머 클라이언트에 전달합니다. client.id는 보고서에서 각각을 reader-a와 reader-b로 표시합니다. 앞서 사용한 자동 커밋 설정을 통해 폴링을 계속하는 동안 읽기 진행 상태가 반영됩니다.
끝의 &는 셸 백그라운드에서 클라이언트를 시작하므로 같은 터미널에서 확인 명령을 실행할 수 있습니다. $!는 방금 시작한 백그라운드 프로세스 ID이며, 바로 뒤의 대입문이 이를 저장해 나중에 해당 프로세스만 정리합니다.
셸에는 레코드용 표준 출력과 진단용 표준 오류라는 두 출력 스트림이 있습니다. >는 첫 스트림을 JSONL 파일에 쓰고, 2>는 오류 스트림을 로그에 씁니다. consumer_a_pid=$! 대입문은 = 주위에 공백 없이 셸 변수를 저장하며, 나중에 "$consumer_a_pid"로 해당 프로세스 ID를 가져옵니다. 일반 레코드와 진단 내용은 프롬프트를 방해하지 않도록 별도 파일에 저장됩니다. 이 클라이언트들은 마지막 부분에서 대상이 한정된 kill 명령을 실행할 때까지 계속 동작합니다.
kafka-console-consumer.sh --bootstrap-server localhost:9092 --topic orders-shared --group orders-team --from-beginning --command-property client.id=reader-a --command-property enable.auto.commit=true --command-property auto.commit.interval.ms=1000 --command-property max.poll.records=1 > /home/labex/project/output/reader-a.jsonl 2> /home/labex/project/output/reader-a.log &
consumer_a_pid=$!
kafka-console-consumer.sh --bootstrap-server localhost:9092 --topic orders-shared --group orders-team --from-beginning --command-property client.id=reader-b --command-property enable.auto.commit=true --command-property auto.commit.interval.ms=1000 --command-property max.poll.records=1 > /home/labex/project/output/reader-b.jsonl 2> /home/labex/project/output/reader-b.log &
consumer_b_pid=$!
실제 두 멤버 할당이 완료될 때까지 기다린 뒤 보고서를 저장하세요.
/home/labex/project/tools/wait-assignment 2 > /home/labex/project/output/two-members.txt
cat /home/labex/project/output/two-members.txt
읽기 전용 도우미는 두 클라이언트가 각각 비어 있지 않고 겹치지 않는 파티션을 소유하며, 그 합집합이 파티션 0과 1이 될 때까지 컨슈머 그룹 멤버 보고서를 반복 확인합니다. 제한 시간이 있으며 할당을 만들어 내거나 진행 상태를 바꾸지 않습니다.
보고서에서 CLIENT-ID는 reader-a 또는 reader-b를 식별하고, CURRENT-ASSIGNMENT는 실제 현재 소유권을 나열합니다. 예를 들어 orders-shared:0은 해당 토픽의 파티션 0을 뜻합니다. 목표 할당은 아직 적용 대기 중일 수 있으므로 현재 할당 열을 확인하세요. 각 멤버가 파티션 하나씩 소유해야 하지만 reader-a가 특정 번호를 받을 필요는 없습니다. 리밸런스는 그룹 멤버가 들어오거나 나갈 때 파티션 소유권을 재분배합니다. 그룹이 새 할당에 도달하려면 시간이 필요합니다. 프로세스가 시작되었다고 해서 소유권이 이미 안정된 것은 아닙니다. 안정된 할당이 작업 분담의 증거이며, 프로세스 ID 두 개만으로 어떤 파티션을 소유하는지는 알 수 없습니다.
작업을 발행하고 독립 그룹과 비교하기
이 단계에서는 할당된 두 파티션에 레코드를 넣고 두 번째 그룹이 같은 기록을 읽는 모습을 관찰합니다. 공개된 데이터를 확인하세요. initial에는 share1부터 share6까지, followup에는 share7과 share8이 들어 있습니다.
cat /home/labex/project/data/group-batches.json
제공된 발행 도구는 선택한 데이터를 실제 브로커에 보내고 전달 콜백을 기다리며, 전달이 완료되지 않으면 실패합니다. 할당이 안정된 후에만 초기 이벤트 여섯 개를 발행하세요.
/home/labex/project/tools/publish-shared-batch initial
/home/labex/project/tools/wait-caught-up orders-team 3 > /home/labex/project/output/team-initial-position.txt
cat /home/labex/project/output/team-initial-position.txt
읽기 전용 진행 확인 도우미는 두 파티션 모두 커밋된 다음 오프셋 3, 끝 오프셋 3, 지연 0이 될 때까지 기다립니다. 각 파티션은 레코드 세 개를 받았습니다. 두 클라이언트가 관찰한 내용을 확인하세요.
cat /home/labex/project/output/reader-a.jsonl
cat /home/labex/project/output/reader-b.jsonl
두 파일을 합치면 초기 ID 여섯 개가 모두 포함되어야 합니다. 현재 실행 중인 멤버의 할당 보고서가 안정된 소유권의 증거입니다. 파일에는 할당이 바뀌기 전의 관찰 결과도 포함될 수 있습니다.
이제 별도 그룹인 orders-observer로 보관된 맨 앞부터 제한된 개수만 읽으세요.
kafka-console-consumer.sh --bootstrap-server localhost:9092 --topic orders-shared --group orders-observer --from-beginning --max-messages 6 --timeout-ms 30000 --command-property enable.auto.commit=true --command-property auto.commit.interval.ms=1000 --command-property max.poll.records=1 > /home/labex/project/output/observer-initial.jsonl
cat /home/labex/project/output/observer-initial.jsonl
/home/labex/project/tools/wait-caught-up orders-observer 3 > /home/labex/project/output/observer-initial-position.txt
cat /home/labex/project/output/observer-initial-position.txt
이 그룹은 레코드 여섯 개를 모두 독립적으로 받아야 합니다. 다른 그룹에 속하므로 orders-team의 파티션을 빼앗지 않습니다. 두 그룹은 별도의 커밋 위치를 유지하면서 동시에 끝까지 읽을 수 있습니다.
멤버 하나를 중지하고 남은 할당 관찰하기
이 단계에서는 reader-a를 실행해 둔 채 reader-b만 중지합니다. kill은 저장된 프로세스에 종료 신호를 보내며 브로커를 중지하지 않습니다. wait는 셸이 해당 자식 프로세스의 종료 처리를 마치게 합니다. 신호를 받은 프로세스가 0이 아닌 종료 상태를 반환할 수 있으므로, 끝의 || true는 이 의도적인 정리 명령이 애플리케이션 실패로 처리되지 않게 합니다.
kill "$consumer_b_pid"
wait "$consumer_b_pid" || true
/home/labex/project/tools/wait-assignment 1 > /home/labex/project/output/one-member.txt
cat /home/labex/project/output/one-member.txt
그룹이 안정되면 reader-a가 두 파티션을 모두 소유해야 합니다. 대기 도구는 다음 발행 전에 실제 보고서를 확인합니다. 파티션이 두 개일 때 멤버를 두 개보다 많이 추가하면 안정된 할당에서 일부 멤버는 파티션을 받지 못합니다. 이 실습에는 세 번째 클라이언트가 필요하지 않습니다.
각 파티션에 후속 이벤트를 하나씩 발행하고 남은 멤버가 끝까지 읽을 때까지 기다리세요.
/home/labex/project/tools/publish-shared-batch followup
/home/labex/project/tools/wait-caught-up orders-team 4 > /home/labex/project/output/team-followup-position.txt
cat /home/labex/project/output/team-followup-position.txt
cat /home/labex/project/output/reader-a.jsonl
이제 reader-a의 파일에는 share7과 share8이 모두 포함되어야 합니다. reader-b는 중지되었으므로 새 레코드를 처리할 수 없습니다. 관찰자 그룹은 이전 커밋 위치를 유지하고 있습니다. 새 이벤트 두 개를 읽도록 재개하세요.
kafka-console-consumer.sh --bootstrap-server localhost:9092 --topic orders-shared --group orders-observer --from-beginning --max-messages 2 --timeout-ms 30000 --command-property enable.auto.commit=true --command-property auto.commit.interval.ms=1000 --command-property max.poll.records=1 > /home/labex/project/output/observer-followup.jsonl
cat /home/labex/project/output/observer-followup.jsonl
/home/labex/project/tools/wait-caught-up orders-observer 4 > /home/labex/project/output/observer-followup-position.txt
cat /home/labex/project/output/observer-followup-position.txt
share7과 share8이 출력되고, 두 파티션에서 각 그룹 자체의 다음 오프셋이 4가 됩니다. 처음부터 읽는 옵션은 관찰자 그룹의 유효한 저장 오프셋 3을 덮어쓰지 않습니다.
나가는 멤버가 레코드를 처리했지만 진행 상태를 아직 커밋하지 않았다면 리밸런스로 재전달이 발생할 수 있습니다. 따라서 이벤트 관찰 결과에 반복이 포함될 수 있습니다. 그래도 안정된 할당에서는 한 시점에 파티션 소유자가 하나입니다. 이 실습은 실제 할당과 완전한 이벤트 내용을 확인하며 전체 처리 과정의 정확히 한 번 전달을 보장한다고 주장하지 않습니다.
남은 클라이언트를 종료하고 증거 보존하기
이 단계에서는 남은 클라이언트 하나를 중지해 계속 실행되던 실험을 마칩니다. 이미 끝에 도달한 최종 위치를 관찰했으므로 대기 중인 새 작업은 없습니다. 해당 클라이언트의 저장된 PID를 사용하세요.
kill "$consumer_a_pid"
wait "$consumer_a_pid" || true
/home/labex/project/tools/wait-assignment 0 > /home/labex/project/output/no-members.txt
cat /home/labex/project/output/no-members.txt
이제 그룹에 활성 멤버가 없어야 합니다. 저장된 오프셋과 브로커 이벤트는 남습니다. 클라이언트의 실행 수명과 저장된 진행 상태는 별개입니다. 개수를 제한한 관찰자 클라이언트는 이미 자동으로 종료되었습니다.
보존된 인계 전후의 멤버 보고서를 확인하세요.
cat /home/labex/project/output/two-members.txt /home/labex/project/output/one-member.txt
첫 보고서는 소유권이 겹치지 않는 두 멤버를 보여 주고, 두 번째는 두 파티션을 모두 소유한 한 멤버를 보여 줍니다. 실행마다 달라지는 멤버 식별자가 포함된 실제 보고서이므로 고정된 할당으로 외울 필요는 없습니다. 읽기 파일과 관찰자 파일에는 두 그룹의 이벤트 증거가 남습니다. 검토용 토픽과 커밋 위치를 보존하면서 자신의 콘솔 클라이언트 두 개만 종료했습니다.
요약
이 실습에서는 두 컨슈머의 실제 파티션 분담, 별도 그룹의 독립적인 동일 레코드 읽기, 새 작업 도착 전에 남은 멤버가 두 파티션을 모두 인계받는 모습을 관찰했습니다. 시간을 추측하는 대신 제한된 상태 확인을 사용하고, 대상 프로세스만 정리하여 진행 상태와 이벤트를 보존했습니다. 다음으로 준비된 발행 도구를 Python 프로듀서로 대체하여 직렬화와 전달 처리를 직접 읽고 수정합니다.



