ข้ามไปยังเนื้อหา

Kafka ทำงานอย่างไรข้างใน

โมดูลก่อน ๆ สอนให้คุณ ใช้ Kafka: produce, consume, ตั้งค่า topic, จูน batching, รัน transaction แต่ไม่มีบทไหนอธิบายว่าจริง ๆ แล้วมีอะไรวิ่งผ่านสายตอน producer.send() คืนค่า, rebalance คืออะไรในระดับ RPC จริง ๆ, follower ทำอะไรอยู่ระหว่างที่คุณ produce จนถึงตอนที่ acks=all คืนค่า, __cluster_metadata record หน้าตาเป็นยังไงจริง ๆ, หรือทำไม .log segment ถึงถูกจัดวางแบบที่เป็นอยู่ ช่องว่างนี้เองที่ทำให้คำอธิบาย Kafka ที่ฟังดูมั่นใจ (จากคนหรือจาก AI) ผิดพลาดได้ในแบบที่ kafka-topics.sh --describe เพียงอย่างเดียวจับไม่ได้

โมดูลนี้ตอบคำถามภายใน 5 ข้อ แต่ละข้อพิสูจน์กับ broker จริง ไม่ใช่แค่กล่าวอ้าง:

  1. RPC อะไรเกิดขึ้นจริงระหว่าง producer.send() กับ commitOffsets ของ consumer? (Produce-to-Fetch Pipeline — Mermaid sequence เดียว พิสูจน์ทีละ hop ด้วยบรรทัด request log จริง)
  2. consumer group rebalance หน้าตาเป็นยังไงจริง ๆ บนสาย ระหว่าง classic กับ KIP-848? (Consumer Group Protocol Internals — JoinGroup/SyncGroup จริงจาก KafkaJS, ConsumerGroupHeartbeat จริงจาก confluent client)
  3. follower ทำอะไรอยู่จริง ๆ และ ISR shrink หน้าตาข้างในเป็นยังไง? (Replication and ISR Internals — คลัสเตอร์ KRaft 3-broker จริง, controller failover จริง, PartitionChangeRecord จริง)
  4. cluster metadata คืออะไรจริง ๆ และ controller quorum ตกลงกันยังไง? (KRaft Metadata Log — __cluster_metadata record จริงที่ decode ด้วย kafka-dump-log.sh --cluster-metadata-decoder)
  5. ไบต์บนดิสก์หน้าตาเป็นยังไงจริง ๆ และทำไม fsync ไม่ใช่กลไก durability? (Storage and Segments — hexdump -C จริงของไฟล์ .log decode ด้วยมือแล้ว cross-check กับ kafka-dump-log.sh)

ทุกคำกล่าวอ้างระดับ RPC ด้านล่างคือบรรทัดจริง ตัดมาสั้น ๆ จาก request log ของ broker — log stream คนละตัวจาก server.log ปิดอยู่โดย default (kafka.request.logger ที่ WARN ซึ่ง log แค่ request ที่พังหรือช้า) broker scratch ของโมดูลนี้เปิดขึ้นเป็น TRACE โดย mount log4j2.yaml ที่แก้แล้วเข้าไปที่ /mnt/shared/config/log4j2.yaml — จุด merge เดียวกันที่สคริปต์ launch ของ image apache/kafka:4.3.1 เองใช้อยู่แล้ว (kafka.docker.KafkaDockerWrapper setup --mounted-configs-dir /mnt/shared/config) เพื่อซ้อน override ของผู้ดูแลระบบทับค่า default ของ image ไม่มี environment variable ชื่อ KAFKA_LOG4J_OPTS บน image นี้ (ชื่อนี้ไม่มีจริงใน apache/kafka:4.3.1) — bind-mount คือกลไกจริงที่ตรวจสอบแล้ว ที่ log level นี้ ทุก request ที่เสร็จแล้วคือ JSON หนึ่งบรรทัดที่บอก requestApiKeyName, correlationId, clientId, และ field ของ request/response ตรง ๆ (acks, fetchOffset, highWatermark, memberEpoch, …) — โมดูลนี้ยกบรรทัดเหล่านั้นมาตรง ๆ ไม่แก้ไขนอกจากตัดให้สั้น

เพราะการเปิดสิ่งนี้ต้องแก้ log4j ที่ harness verification กลางไม่มี proof ที่มาจาก request log ทุกอันในโมดูลนี้จึงเป็น text transcript จากการรันจริงบน broker scratch ของโมดูลนี้เอง (KRaft, single combined node, พอร์ต 29092) ไม่ใช่ fence ที่ harness รันจริง ส่วนอะไรที่ต้องการแค่ broker ปกติ — kafka-dump-log.sh, kafka-topics.sh --describe, kafka-metadata-quorum.sh — จะเป็น bash fence ที่ harness รันจริง เหมือนบทอื่นในคอร์ส Replication and ISR Internals ใช้ compose KRaft 3-broker ชั่วคราวเพิ่ม (ปิดทันทีหลังใช้) เพราะพฤติกรรม ISR/failover ไม่มีทางเกิดบน node เดียว

flowchart TD
  p["Produce-to-Fetch Pipeline\n(ลำดับ RPC)"] --> g["Consumer Group Protocol Internals\n(JoinGroup/SyncGroup vs ConsumerGroupHeartbeat)"]
  p --> r["Replication and ISR Internals\n(follower fetch, ISR shrink, failover)"]
  r --> k["KRaft Metadata Log\n(log ที่ทำให้ r เป็นไปได้)"]
  p --> s["Storage and Segments\n(ไบต์ที่ p append จริง)"]
  k --> s
5 บท internals เรียงตามลำดับที่ต่อกัน

ลำดับการอ่านและสิ่งที่แต่ละบทสมมติไว้แล้ว

หัวข้อที่มีชื่อว่า “ลำดับการอ่านและสิ่งที่แต่ละบทสมมติไว้แล้ว”
บทต่อจาก (Phase 3)ตอบคำถาม
Produce-to-Fetch PipelineThe Log Abstraction, Acks and DurabilityRPC อะไรวิ่งผ่านสายจริงจากหนึ่ง producer.send() ถึงหนึ่ง commitOffsets()?
Consumer Group Protocol InternalsConsumer Groups and Rebalancing, Consumer APIJoinGroup/SyncGroup เทียบกับ ConsumerGroupHeartbeat หน้าตาเป็นยังไงจริง ๆ ทีละ RPC?
Replication and ISR InternalsReplication and ISRFetchRequest ของ follower มีอะไรอยู่ข้างใน และ ISR shrink เกิดอะไรขึ้นจริง?
KRaft Metadata LogKRaft & the Cluster__cluster_metadata record จริงหน้าตาเป็นยังไง และ controller failover จริงเร็วแค่ไหน?
Storage and SegmentsThe Log Abstractionไบต์ดิบของไฟล์ .log/.index เก็บอะไรจริง ๆ และทำไม fsync ไม่ได้ให้ durability?

บท Phase 3 ที่ลิงก์ไว้ข้างบนแสดงมุมมอง ข้างนอก ให้คุณแล้ว (error message, CLI describe, timing ที่ client มองเห็น) โมดูลนี้แสดงกลไก ข้างใน ที่อยู่หลังมุมมองนั้น — ไม่สอนมุมมองข้างนอกซ้ำ แค่ลิงก์ไปหา

ทุกบทในโมดูลนี้คือไอเดียเดียวกันที่เอาไปใช้กับ subsystem ต่างกัน: พฤติกรรมของ Kafka ไม่ใช่ black box — มันคือ RPC และ on-disk record ชุดตายตัวที่ request log, metadata dump, หรือ hex editor แสดงให้คุณเห็นตรง ๆ ได้เสมอ “rebalance” ไม่ใช่การหยุดชะงักปริศนา มันคือลำดับ RPC ที่เรียกชื่อได้ชัดเจน (JoinGroup→SyncGroup หรือ ConsumerGroupHeartbeat หนึ่งตัว) ที่ request log แสดงให้เห็นทั้งหมด “ISR shrink” ไม่ใช่เหตุการณ์คลัสเตอร์ที่มองไม่เห็น มันคือ PartitionChangeRecord ที่ถูก append เข้า __cluster_metadata ดูได้ด้วย kafka-dump-log.sh --cluster-metadata-decoder “.log segment” ไม่ใช่ abstraction มันคือไบต์ที่ offset ตายตัวที่ hexdump -C กับ kafka-dump-log.sh เห็นตรงกันไบต์ต่อไบต์ พอรู้ว่าเครื่องมือไหนพิสูจน์คำกล่าวอ้างไหน คำว่า “เชื่อผมเถอะ Kafka ทำงานแบบนี้” ก็ไม่จำเป็นอีกต่อไป

  • “acks=all แปลว่า replica ทุกตัวยืนยันการเขียนก่อน producer จะได้ response” Produce-to-Fetch Pipeline พิสูจน์กลไกจริง: leader append แล้วรอให้ ISR (ไม่ใช่ “ทุก replica” แน่นอน — ดู Replication and ISR) เลื่อน high watermark ผ่าน offset ของ record ใหม่ ผ่าน FetchRequest ของ follower เอง ไม่ใช่ leader push
  • “ConsumerGroupHeartbeat ของ KIP-848 ก็แค่ JoinGroup เปลี่ยนชื่อ” Consumer Group Protocol Internals พิสูจน์ว่ามันต่างกันโดยโครงสร้าง: classic ใช้คู่ RPC สองตัว JoinGroup→SyncGroup (แต่ละตัวคือ barrier synchronize ทั้งกลุ่ม); KIP-848 ใช้ RPC เดียวที่วนซ้ำ พร้อม assignment ของ member เองอยู่ใน response เลย ไม่มี SyncGroup เลย
  • “ISR shrink กับ broker fencing เป็นกลไกเดียวกัน” Replication and ISR Internals พิสูจน์ด้วย controller log จริงว่าทั้งสองเป็นโค้ดพาธคนละเส้นใน KRaft controller ที่บังเอิญให้อาการที่เห็นเหมือนกัน (replica หลุดจาก ISR)
  • “cluster metadata อยู่ในฐานข้อมูลที่ controller query” KRaft Metadata Log พิสูจน์ว่า metadata คือ log แบบ append-only สไตล์ Kafka ปกติ (topic __cluster_metadata) อ่านได้ด้วยเครื่องมือ kafka-dump-log.sh ตัวเดียวกันที่ใช้กับ topic ข้อมูลทั่วไป
  • “fsync คือสิ่งที่ทำให้การเขียนของ Kafka durable” Storage and Segments พิสูจน์ตรงกันข้ามจาก config จริงของคลัสเตอร์นี้: log.flush.interval.messages default เป็น Long.MAX_VALUE (คือไม่เกิดขึ้นเลย) เพราะ durability มาจาก replication (ISR) ไม่ใช่การบังคับ flush ทุกครั้งก่อน ack

ตัวอย่างของแพทเทิร์นที่ทุกบทในโมดูลนี้ทำซ้ำอย่างละครั้ง: คำกล่าวอ้างเกี่ยวกับ Kafka internals ที่ฟังดูสมเหตุสมผล แต่การตรวจสอบระดับ RPC หรือไบต์หักล้างได้

🐛 หา bug ในโค้ดนี้

// A comment an AI assistant added above a producer config
// "acks: 'all' waits for every replica in the cluster to write the
// record before returning -- this guarantees zero data loss."
const producer = kafka.producer();
await producer.send({
topic: 'orders',
acks: -1,
messages: [{ key: orderId, value: JSON.stringify(order) }],
});
เฉลย

คอมเมนต์นี้มีปัญหาจริง 2 อย่างที่พิสูจน์แล้วในโมดูลนี้ ข้อแรก Produce-to-Fetch Pipeline แสดงว่า acks=all (-1) รอ ISR ไม่ใช่ “replica ทุกตัวในคลัสเตอร์” — ถ้า ISR shrink ลงเหลือแค่ leader (เป็นไปได้จริง พิสูจน์ใน Replication and ISR Internals) acks=all จะคืนค่าหลังจาก broker เดียวมี record เท่านั้น ข้อสอง “zero data loss” จะจริงก็ต่อเมื่อตั้ง min.insync.replicas สูงพอที่ ISR จะ shrink ลงเล็กเกินไปไม่ได้โดยที่ producer จะได้ NotEnoughReplicasException จริงที่มองเห็นได้ แทนที่จะสำเร็จแบบเงียบ ๆ — acks เพียงอย่างเดียวไม่ได้บังคับขนาดขั้นต่ำของ ISR

โมดูลนี้เปิด request log ของ broker จาก WARN ขึ้นเป็น TRACE กลไกจริงที่ตรวจสอบแล้วบน apache/kafka:4.3.1 คือข้อไหน?
ทำไม proof ที่มาจาก request log ในโมดูลนี้เป็น text transcript แทนที่จะเป็น bash fence ที่ harness verification รันจริงซ้ำ?
รูปร่างจริงบนดิสก์ของ cluster metadata ของ Kafka ที่พิสูจน์ในโมดูลนี้คือข้อไหน?
โมดูลนี้พิสูจน์ว่าอะไรต่างกันจริงระหว่าง classic rebalancing กับ KIP-848?
ตาม config จริงของคลัสเตอร์นี้เอง (พิสูจน์ใน Storage and Segments) อะไรคือสิ่งที่ให้ durability จริงกับการเขียนที่ ack แล้ว?