Kafka Fundamentals
Topic & Partition — หน่วยจัดเก็บข้อมูลพื้นฐาน
Topic คือ "ชื่อกลุ่มข้อมูล" เชิง logical (เหมือนชื่อตารางใน database) แต่ข้างใน Topic จริงๆ ถูกแบ่งเป็น Partition หลายตัว — แต่ละ Partition คือ append-only log ที่เรียงลำดับ (ordered, immutable) และมี offset (เลขลำดับ) กำกับทุก message ไว้
Topic: orders
├── Partition 0: [msg0][msg1][msg2][msg3]... offset 0,1,2,3...
├── Partition 1: [msg0][msg1][msg2]... offset 0,1,2...
└── Partition 2: [msg0][msg1][msg2][msg3][msg4] offset 0,1,2,3,4...
Partition คือหน่วยของ parallelism — ยิ่งมี partition เยอะ ยิ่งกระจายงานให้หลาย consumer ทำงานพร้อมกันได้มากขึ้น (รายละเอียดอยู่ใน Producer & Consumer)
"Topic คือชื่อสมุดบันทึก, Partition คือเล่มย่อยที่แยกเขียน" — สมุดเล่มเดียวเขียนได้คนเดียวที่ละ บรรทัด (ตามลำดับ) แต่ถ้าแบ่งเป็นหลายเล่มย่อย (partition) หลายคนเขียนพร้อมกันได้ แต่ละเล่มก็ยัง เรียงบรรทัดในตัวเองอยู่ดี (ordered ภายใน partition)
Partition ไม่ได้เป็นไฟล์เดียวบน disk — มันถูกแบ่งเป็น segment files ย่อยๆ (default ขนาด
1 GB ต่อ segment หรือตามเวลา log.segment.ms) เหตุผลที่ไม่เก็บเป็นไฟล์เดียวยาวๆ คือ Kafka ต้อง
ลบข้อมูลเก่าทิ้งตาม retention policy การลบทำได้ง่ายแค่ "ลบทั้ง segment file ที่หมดอายุ"
(O(1) ไม่ต้อง rewrite อะไร) ต่างจากถ้าเป็นไฟล์เดียวที่ต้องมานั่งตัดข้อมูลเก่าออกจากกลางไฟล์ซึ่ง
แพงกว่ามาก — segment ล่าสุดที่กำลังถูกเขียนอยู่เรียกว่า active segment และมีแค่ segment
เดียวเท่านั้นที่เปิดเขียนได้ในแต่ละ partition ณ เวลาหนึ่ง
Broker & Cluster
Broker คือ Kafka server หนึ่งตัว (process เดียว) ที่เก็บ partition บางส่วนไว้ — Cluster คือกลุ่ม broker หลายตัวทำงานร่วมกัน แต่ละ partition ถูกกระจาย (distribute) ไปอยู่ บน broker ต่างๆ ไม่ใช่ broker เดียวเก็บทุกอย่าง
Cluster (3 brokers)
Broker 1: orders-partition-0 (leader), orders-partition-1 (replica)
Broker 2: orders-partition-1 (leader), orders-partition-2 (replica)
Broker 3: orders-partition-2 (leader), orders-partition-0 (replica)
"Broker = คลังสินค้าแต่ละสาขา, Partition = กล่องสินค้าที่กระจายไปเก็บ" — กล่องเดียวกัน (replica) อาจถูกเก็บซ้ำไว้หลายสาขาเผื่อสาขาหลักไฟไหม้ (broker ล่ม) แต่ "สาขาหลัก" ของกล่องนั้น (leader) มีแค่ที่เดียว
ตั้งแต่ Kafka 2.8+ มี mode ใหม่ที่ไม่ต้องพึ่ง ZooKeeper อีกต่อไปเรียกว่า KRaft (Kafka Raft metadata mode) — เดิมที Kafka ใช้ ZooKeeper แยกต่างหากเก็บ cluster metadata (broker ไหนอยู่ คุมพาร์ติชันไหน) ซึ่งเป็นจุดอ่อนเพราะต้องดูแลระบบสองระบบคู่กัน (Kafka + ZooKeeper) KRaft เปลี่ยนมาเก็บ metadata ไว้ใน Kafka เองผ่าน Raft consensus algorithm (คล้ายกับที่ etcd ใช้ใน Kubernetes) ทำให้ไม่ต้องรัน ZooKeeper แยกอีกต่อไป ลด operational complexity ลงมาก Kafka เวอร์ชันใหม่ (4.0+) ตัด ZooKeeper ออกไปเลย — ถ้า interviewer ถามเรื่อง "Kafka ไม่ใช้ ZooKeeper แล้วได้ยินมา จริงไหม" คือจริง และเป็นหนึ่งใน architectural change ที่สำคัญที่สุดของ Kafka ในรอบหลายปี
Offset — ตำแหน่งที่อ่านถึง
Offset คือเลขลำดับที่กำกับแต่ละ message ภายใน partition (เริ่มจาก 0 เพิ่มขึ้นเรื่อยๆ)
Consumer เป็นคนจำเองว่าตัวเองอ่านถึง offset ไหนแล้ว (เก็บไว้ใน topic พิเศษชื่อ
__consumer_offsets) — Kafka broker ไม่ได้ติดตามว่า consumer อ่านอะไรไปแล้วบ้าง ต่างจาก
message queue แบบดั้งเดิม (เช่น RabbitMQ) ที่ broker เป็นคนจัดการ state การอ่านให้
Partition 0: [msg0][msg1][msg2][msg3][msg4][msg5]
^
consumer offset = 3 (อ่านถึง msg2 แล้ว ตัวต่อไปคือ msg3)
"Offset คือที่คั่นหนังสือของผู้อ่านแต่ละคน ไม่ใช่ของห้องสมุด" — ห้องสมุด (broker) แค่เก็บ หนังสือไว้ให้ครบทุกหน้า ส่วน "อ่านถึงหน้าไหนแล้ว" เป็นหน้าที่ผู้อ่าน (consumer) ต้องจำเอง — ผู้อ่านหลายคนอ่านหนังสือเล่มเดียวกันได้โดยมีที่คั่นหนังสือคนละอัน ไม่ชนกัน
เพราะ consumer เป็นคนควบคุม offset เอง สิ่งที่ตามมาคือ Kafka รองรับ pattern ที่ message queue
ทั่วไปทำได้ยากมาก: "replay" ข้อมูลเก่าได้ตามต้องการ แค่สั่ง seek() offset กลับไปที่ตำแหน่ง
เก่า (หรือ reset consumer group offset ด้วย kafka-consumer-groups --reset-offsets) ก็อ่าน
ข้อมูลเดิมซ้ำได้ทันที — เป็นประโยชน์มากตอน deploy consumer logic ใหม่ที่ต้องการประมวลผลข้อมูล
เก่าใหม่ทั้งหมด (backfill) หรือตอน debug ปัญหาที่ต้องดู sequence การประมวลผลซ้ำ ความสามารถนี้
มาจากดีไซน์พื้นฐานที่ว่า message ใน Kafka ไม่ถูกลบทิ้งทันทีหลังถูกอ่าน (ต่างจาก queue
ทั่วไปที่ message หายไปหลังถูก consume) — message จะอยู่ต่อจนกว่าจะหมดอายุตาม retention policy
เท่านั้น ไม่ว่าจะมี consumer อ่านไปแล้วกี่ตัวก็ตาม
Replication Factor — ความทนทานต่อความล้มเหลว
Replication factor คือจำนวนสำเนาของแต่ละ partition ที่กระจายเก็บไว้คนละ broker (เช่น
replication.factor=3 หมายถึงแต่ละ partition มี 3 สำเนา) หนึ่งในนั้นเป็น leader (รับ
read/write ทั้งหมด) ที่เหลือเป็น follower (คอย sync ข้อมูลจาก leader เผื่อ leader ล่ม)
"Replication factor = จำนวนสำเนาเอกสารสำคัญที่เก็บไว้คนละตู้เซฟ" — ตู้เซฟหลัก (leader) พังไป ยังมีสำเนาในตู้อื่น (follower) ที่พร้อมเลื่อนขึ้นมาเป็นตู้หลักแทนได้ทันที ยิ่งมีสำเนาเยอะยิ่ง ทนทาน แต่ก็ยิ่งเปลือง storage และ network bandwidth สำหรับ sync มากขึ้นตามไปด้วย
Replication factor สูงไม่ได้แปลว่าข้อมูลปลอดภัย 100% เสมอไป — ต้องดูคู่กับแนวคิด ISR
(In-Sync Replicas) ซึ่งเป็น subset ของ replica ที่ "sync ทันกับ leader จริงๆ" เท่านั้น
(ไม่ใช่ทุก replica ที่ assign ไว้) follower ที่ sync ช้าเกินไป (เกิน
replica.lag.time.max.ms) จะถูกเตะออกจาก ISR ชั่วคราว — ถ้า producer ตั้งค่า
acks=all (รอ confirm จากทุก ISR ก่อนถือว่าเขียนสำเร็จ) และ ISR เหลือ broker เดียว (leader
เดี่ยวๆ เพราะ follower ตามไม่ทันหมด) การันตีความทนทานจะลดลงเหลือเท่ากับไม่มี replication เลย
แม้ replication factor จะตั้งไว้สูงแค่ไหนก็ตาม — รายละเอียดเรื่อง ISR และ acks เจาะลึกใน
Reliability & Ordering
A Topic is a logical name split into ordered, append-only Partitions — the unit of parallelism.
Partitions are physically stored as rotating segment files (not one giant file), which makes
retention-based deletion cheap. Brokers form a Cluster, each holding a subset of partitions;
Kafka 2.8+ can run without ZooKeeper via KRaft, storing cluster metadata through its own Raft
consensus instead. Offsets are tracked by the consumer, not the broker (stored in
__consumer_offsets) — this is why Kafka supports replaying old data via seek(), since
messages aren't deleted on read, only on retention expiry. Replication Factor sets how many
copies of each partition exist across brokers (one leader, rest followers); durability actually
depends on the In-Sync Replica (ISR) set, not the raw replication factor — a lagging follower
drops out of ISR, and acks=all only guarantees as much durability as the current ISR size.