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

อ่านโค้ด Kafka ที่ AI เขียน

โมดูลก่อน ๆ ในคอร์สนี้สอนให้คุณ เขียน Kafka client, topic, และ configuration โมดูลนี้สอนอีกครึ่งหนึ่ง คือการอ่าน producer, consumer loop, docker-compose.yml, หรือไฟล์ .properties ที่คนอื่น — หรืออะไรบางอย่าง — เขียนไว้ แล้วตัดสินว่ามันถูกจริงหรือไม่ AI assistant ส่ง KafkaJS consumer ที่ connect, subscribe, และ print message ออกมาที่ console ได้ตั้งแต่ครั้งแรกที่ลอง และความสำเร็จตรงนั้นคือกับดัก: tsc --noEmit ผ่าน type-check, broker รับทุก config key, และ demo รันได้สมบูรณ์แบบ — ในขณะที่โค้ดทิ้ง durability guarantee ที่ training data ต้นฉบับกำลังสาธิตอยู่ไปเงียบ ๆ Kafka client library ตรวจสอบความหมาย (semantic) แทบไม่มีเลย: KafkaJS จะสร้าง producer ที่ idempotent ถูกทิ้งไว้ที่ค่า default จริงคือ false ได้เฉย ๆ, ยอมรับ consumer ที่ commit offset ผิด, และปล่อยให้ write ของ transactional producer ข้ามธุรกรรมไปเลยได้ — ไม่มี warning แม้แต่นิดเดียว เพราะไม่มีอันไหนเป็น type error หรือ malformed request

training data มีปัญหาสามอย่างที่ผสมกันจนหนักขึ้น อย่างแรก client landscape ของ Kafka เปลี่ยนรูปร่างไปจริง ๆ ระหว่างช่วง corpus: ZooKeeper ถูกถอดออกใน Kafka 4.0, group.protocol=consumer ของ KIP-848 ถึง GA แล้ว, และ KafkaJS เองก็ไม่มีคนดูแลตั้งแต่ 2023-02-27 — เท่ากับว่า tutorial Kafka ที่ดู “current” ออนไลน์จำนวนมากบรรยาย flag --zookeeper, setting ที่มีแค่ฝั่ง JVM client, หรือ client ที่ไม่มีใครแก้ bug ให้แล้ว ทั้งหมดยังดูคล่องปากอยู่ อย่างที่สอง ตัวอย่าง KafkaJS ที่เปิดเผยต่อสาธารณะส่วนใหญ่เป็นระดับ demo — quickstart ที่ปรับให้ “print message ได้” ไม่ใช่ payments pipeline ที่ปรับให้ “ไม่เสีย message แม้แต่ตัวเดียว” ค่า default ที่โอเคสำหรับ demo (acks ไม่ตั้ง, idempotent ไม่ตั้ง, autoCommit เปิดทิ้งไว้) จึงถูกก็อปวางตรง ๆ ลงในโค้ดที่รับความเสี่ยงนั้นไม่ได้ อย่างที่สาม วิธีแก้ error ของ Kafka ที่ AI assistant คว้าเร็วที่สุดคือ config value ที่ก็อปมาจากที่สุดท้ายที่เห็น error text นั้นถูกแก้ — enable.idempotence=false “แก้” duplicate-key error บน Java client ด้วยการปิด guarantee ที่ป้องกัน duplicate นั่นเอง และ unclean.leader.election.enable=true “แก้” เหตุการณ์ partition offline ด้วยการแลก availability กับสัญญาที่ว่า leader จะไม่ถูกเลือกจากข้อมูลเก่าเด็ดขาด เจ็ดขั้นด้านล่างคือลำดับที่ทำให้เจอ failure เหล่านี้จริง ๆ และทุกบทถัดไปในโมดูลนี้เจาะลึกส่วนหนึ่งของมัน

flowchart TD
  A["1. Client &amp; version<br/>which client, which Kafka version does the training data assume?"] --> B["2. Broker &amp; topic config<br/>RF, min.insync.replicas, retention -- do the numbers agree with each other?"]
  B --> C["3. Producer config<br/>acks, idempotent, keys -- durability and ordering, both unvalidated by the client"]
  C --> D["4. Consumer loop<br/>poll/heartbeat timing, commit offset math, autoCommit"]
  D --> E["5. Delivery semantics &amp; transactions<br/>sendOffsets vs a separate commit, retriable vs fatal, isolation level"]
  E --> F["6. Ops &amp; security<br/>hallucinated CLI tools/flags/keys, authorizer, listeners"]
  F --> G["7. Connect / Streams / Schema<br/>standalone vs distributed, SMT value shapes, compatibility mode"]
ลำดับการอ่านโค้ด Kafka ที่คุณไม่ได้เขียนเอง

ก่อนอื่นใด ตรวจก่อนว่าโค้ดใช้ Kafka client ตัวไหนจริง ๆ และสมมติฐานของมันเขียนขึ้นบน Kafka version ไหน — เพราะ KafkaJS กับ @confluentinc/kafka-javascript ไม่ตรงกันทั้งเรื่อง default, เรื่อง setting ไหนมีอยู่จริงบ้าง, และเรื่อง client-side protocol ที่พูดด้วยเลย และ training data ครอบคลุม client ที่ไม่มี fix เลยตั้งแต่ต้นปี 2023

  • สิ่งที่ต้องมองหา: นี่คือ KafkaJS (kafkajs) หรือ @confluentinc/kafka-javascript ที่ยังมีคนดูแลและใช้ librdkafka เป็น backend? มี config object ที่พก key เฉพาะ Java client (group.protocol=consumer, KIP-848) ที่ KafkaJS เมินเงียบ ๆ แทนที่จะปฏิเสธไหม? script shell ส่ง --zookeeper ให้ CLI tool ทั้งที่ flag นี้ถูกถอดออกไปเต็ม ๆ ตั้งแต่ Kafka 4.0 หรือไม่? โค้ดสมมติว่าทุก compression codec หรือทุก partition assigner มีมาให้ในตัว ทั้งที่ KafkaJS มีแค่ gzip และแค่ RoundRobinAssigner หรือเปล่า?
  • คำถามที่ต้องถาม: ชื่อ option นี้จะ resolve เป็นอะไรจริง ๆ บน client ที่ import มาจริงหรือไม่ — หรือแค่ถูกรับไว้แล้วทิ้งเงียบ ๆ เพราะ object literal ของ JavaScript ไม่ปฏิเสธ key ที่ไม่รู้จักแบบที่ typed config class จะทำ?
  • บทไหนอธิบาย: กลุ่ม Client & versions ของ AI Bug Catalog (รายการ 1–3) สำหรับการปฏิเสธจริงของ CLI และการ no-op เงียบ ๆ จริง; The Producer API และ Consumer API สำหรับ client ทั้งสองตัวเทียบกันอย่างละเอียด

พอรู้ client แล้ว ตรวจว่า topic กับ broker configuration ที่โค้ดสมมติไว้สอดคล้องกันเองหรือไม่ — Kafka รับ durability setting ที่ขัดแย้งกันเองได้โดยไม่บ่นอะไร เพราะไม่มี config command ตัวเดียวที่เห็นภาพรวมทั้งหมดพร้อมกัน

  • สิ่งที่ต้องมองหา: replication-factor รองรับ min.insync.replicas ที่ topic เดียวกันตั้งไว้จริงหรือไม่ — หรือ min.insync.replicas=2 ตั้งอยู่บน replication factor 1 ที่ถูกรับเงียบ ๆ ตาม KIP-579 พร้อม write แบบ acks=all ที่สำเร็จวันนี้แต่จะพังทันทีที่ broker แค่ restart? retention.bytes เป็นเลขที่ comment อ้างจริง (byte ต่อ byte ไม่ใช่ “ประมาณกิกะไบต์เดียว”) หรือไม่? auto.create.topics.enable ถูกสลับด้วย kafka-configs.sh --alter ธรรมดา โดยสมมติว่ามีผลทันทีไม่ต้อง restart broker หรือไม่? การเพิ่ม partition ให้ topic ที่ใช้ key อยู่แล้วเกิดขึ้นโดยไม่มีแผนสำหรับ reader หรือไม่?
  • คำถามที่ต้องถาม: ถ้า topic นี้เสีย leader ปัจจุบันแล้วต้องสร้าง ISR ใหม่จากศูนย์ configuration บนกระดาษยังคง guarantee ที่ combination acks/min.insync.replicas บอกไว้อยู่หรือไม่ — และ client ทุกตัวที่ cache partition count เก่าของ topic นี้อยู่แล้วรู้หรือไม่ว่ามันเปลี่ยนไปแล้ว?
  • บทไหนอธิบาย: กลุ่ม Topic & broker config ของ AI Bug Catalog (รายการ 13–15) สำหรับการรับเงียบ ๆ จริงและการ remap partition ด้วย metadata ที่ค้างจริง; Retention and Compaction และ Replication and ISR สำหรับกลไกทั้งสองแบบละเอียด

producer ที่ connect แล้ว send ได้โดยไม่ error ไม่ได้บอกอะไรเลยว่ามันจะเสีย message หรือ duplicate message หรือไม่ — acks, idempotent, และ transactional lifecycle ทั้งหมดเป็นตัวเลือกฝั่ง client ที่ broker รับไว้โดยไม่รู้เลยว่าผู้เรียกคิดว่าตัวเองได้ guarantee อะไร

  • สิ่งที่ต้องมองหา: idempotent ถูกตั้งชัดเจนไหม หรือถูกทิ้งไว้ที่ default จริงของ KafkaJS คือ false? acks ถูกตั้งเป็น 0 หรือ 1 บน path ที่สำคัญ โดยอิงจาก mental model ของ Java client ทั้งที่ default จริงของ KafkaJS เองคือ -1/all อยู่แล้วหรือไม่? มี comment อ้าง compression codec (lz4, snappy, zstd) ที่ KafkaJS ไม่มีให้ในตัวไหม? producer ที่สร้างด้วย transactionalId เคยเรียก .send() ตรง ๆ แทน .transaction() — ข้ามธุรกรรมไปเงียบ ๆ หรือเปล่า?
  • คำถามที่ต้องถาม: ถ้า client retry การเรียก send() นี้หลัง network สะดุด มี configuration อะไรของ producer นี้ที่ทำให้ retry นั้นปลอดภัยหรือไม่ — และทุก write ที่ producer instance นี้ทำ ผ่าน transaction ที่ constructor บอกไว้จริงหรือไม่?
  • บทไหนอธิบาย: กลุ่ม Producer ของ AI Bug Catalog (รายการ 4–6) สำหรับ default จริงจาก source code และการเขียนแบบไม่ผ่าน transaction เงียบ ๆ จริง; Idempotent Producer และ Acks and Durability สำหรับกลไกทั้งสองอย่างละเอียด

handler eachMessage ของ consumer และการเรียก commit ของมันเป็นสองจุดที่โค้ดที่ AI สร้างมักผิดแบบเงียบ ๆ และแอบแฝงที่สุด — เพราะ heartbeat และความหมาย offset ของ KafkaJS ทั้งคู่มีสัญญาที่ client บังคับใช้จริง ในจังหวะที่ผิดที่สุดที่จะสังเกตเห็นตอนทดสอบเร็ว ๆ

  • สิ่งที่ต้องมองหา: handler ทำอะไรที่ช้า — HTTP call, DB write ที่ไม่มี timeout — อยู่ใน eachMessage โดยลืมว่า heartbeat ของ KafkaJS จะยิง หลังจาก handler return แล้วเท่านั้นหรือไม่? การเรียก commitOffsets แบบ manual ส่ง offset ที่อ่านมา แทนที่จะเป็น offset + 1 (ตำแหน่งที่จะ resume ต่อ) หรือไม่? autoCommit ถูกทิ้งไว้ที่ default true บน path ที่ message ถูก route ไป dead-letter topic — และถ้าใช่ โค้ด commit offset ของ message ต้นฉบับ หลังจากการ route นั้นสำเร็จหรือไม่?
  • คำถามที่ต้องถาม: ถ้า handler นี้ใช้เวลานานกว่า session.timeout.ms ในวันที่ระบบช้า consumer จะถูก evict กลางการประมวลผลหรือไม่ — และถ้า process restart ตอนนี้เลย มันจะ resume จากตำแหน่งที่ทำให้ message หนึ่งถูกประมวลผลซ้ำสอง หรือข้าม message ที่จริง ๆ ยังไม่เสร็จไปหรือไม่?
  • บทไหนอธิบาย: กลุ่ม Consumer loop & commits ของ AI Bug Catalog (รายการ 7–9) สำหรับ eviction จริงแบบ coordinator is not aware of this member, duplicate off-by-one จริงตอน restart, และ DLQ loop ค้างจริง; Consumer API และ Offsets and Commits สำหรับกลไกทั้งสองอย่างละเอียด

exactly-once processing ประกอบขึ้นจากหลายชิ้นที่ต้องตรงกันเองทั้งหมด และ Kafka ไม่มี check ตัวเดียวที่ยืนยันว่ามันตรงกันจริง — transaction ที่ commit สำเร็จไม่ได้บอกอะไรเลยว่า consumer offset ที่มันควรจะปกป้องถูก commit แบบ atomic ไปด้วยกันหรือไม่

  • สิ่งที่ต้องมองหา: loop consume-transform-produce commit consumer offset ด้วยการเรียก commitOffsets แยก ต่างหาก แทนที่จะเป็น txn.sendOffsets() ภายใน transaction เดียวกันหรือไม่? readUncommitted เคยถูกสลับเป็น true เพื่อ “แก้” consumer ที่ดูเหมือน “หาย” message ไป — ทั้งที่ message นั้นถูก abort ไปตั้งใจแล้วหรือไม่? logic retry/abort เคยทำงานกับ error ของ producer โดยไม่เช็ค error.retriable ก่อน จน retry error ที่ fenced และ fatal ไปตลอดกาลหรือไม่?
  • คำถามที่ต้องถาม: ถ้า producer นี้ crash ระหว่าง transaction commit กับ offset commit แยกต่างหาก การรันครั้งต่อไปจะเขียนซ้ำ downstream หรือไม่ — และ branch การจัดการ error นี้รู้จริงหรือไม่ว่า “ลองใหม่” กับ “producer ตัวนี้ตายแล้ว หยุดเถอะ” ต่างกันอย่างไร?
  • บทไหนอธิบาย: กลุ่ม Delivery & transactions ของ AI Bug Catalog (รายการ 10–12) สำหรับ duplicate จริงหลัง crash, record ที่ถูก abort แล้วอ่านกลับมาได้จริง, และ KafkaJSProtocolError แบบ fatal ที่ถูก retry ไปตั้งใจจริง; Transactions และ Exactly-Once Processing สำหรับกลไกทั้งสองอย่างละเอียด

command-line tool กับ broker security เป็นจุดที่ hallucination จับได้ง่ายที่สุดด้วยวิธีการทางกล — CLI มี flag นั้นอยู่จริงหรือไม่ก็ไม่มี — และพลาดง่ายที่สุดด้วยตาเปล่า เพราะข้อความ error เพียงอย่างเดียวมักไม่อธิบายว่าอะไรหายไปจริง ๆ

  • สิ่งที่ต้องมองหา: script เรียก tool ที่ไม่มีอยู่จริง (kafka-metadata.sh) หรือส่ง flag ที่ไม่มีอยู่จริงบน tool จริง (--run-all) หรือไม่? runbook เรียก kafka-acls.sh บน cluster ที่ไม่เคยตั้ง authorizer.class.name ไว้เลยหรือไม่? advertised.listeners ชี้ไปที่ hostname ที่ client ผู้เรียก resolve ไม่ได้จริง ๆ หรือไม่?
  • คำถามที่ต้องถาม: command นี้รันได้จริง เป๊ะ ๆ กับ broker จริงของ version ที่คอร์สนี้ใช้ — หรือแค่ดูสมเหตุสมผลเพราะชื่อ flag กับชื่อ tool ทั้งคู่ฟังดูเหมือนของที่มีอยู่จริง?
  • บทไหนอธิบาย: กลุ่ม Ops & security ของ AI Bug Catalog (รายการ 16–18) สำหรับ UnrecognizedOptionException จริงและ SecurityDisabledException จริง; Security and Production สำหรับเรื่อง authorizer เต็มรูปแบบ

Kafka Connect, Streams, และ Schema Registry ต่างเพิ่ม configuration ของตัวเองที่ถูกตรวจสอบน้อยกว่า client library ข้างบนอีก — ชื่อ class ของ connector เป็นแค่ string จนกว่า worker จะพยายาม load มันจริง ๆ และ compatibility mode ของ schema เป็นสัญญาที่ registry ตรวจ ไม่ใช่ผู้เขียน

  • สิ่งที่ต้องมองหา: Connect ถูก deploy แบบ standalone สำหรับอะไรที่ต้องการ fault tolerance หรือไม่? connector config อ้าง class ที่จริง ๆ ไม่ได้อยู่บน discoverable plugin path ของ worker เลย แม้ jar จะมีอยู่บน disk ที่ไหนสักแห่งหรือไม่? errors.deadletterqueue.topic.name ถูกตั้งโดยไม่มี errors.tolerance=all — setting ที่ทำให้มันทำงานจริง (default คือ none) หรือไม่? schema evolution ลบ field ที่ไม่มี default ภายใต้ compatibility BACKWARD หรือไม่? โค้ดสมมติว่า KafkaJS consumer เข้าร่วม share group (KIP-932) ได้เลยหรือไม่?
  • คำถามที่ต้องถาม: /config/validate ของ Connect REST API เองสำหรับ connector class ตัวนี้เป๊ะ ๆ รายงานว่า config นี้สะอาดหรือไม่ — และชื่อ class นี้ resolve บน worker ตัวนี้ ตอนนี้เลย ไม่ใช่ในหลักการ จริงหรือไม่?
  • บทไหนอธิบาย: กลุ่ม Connect/Streams/Schema ของ AI Bug Catalog (รายการ 19–21) สำหรับ ClassNotFoundException จริงทั้งที่ jar มีอยู่บน disk จริง และ type surface ของ KafkaJS ที่ว่างเปล่าจริงสำหรับ share group; Kafka Connect, Kafka Streams, และ Schema Registry สำหรับกลไกแต่ละอย่างละเอียด รวมถึงขอบเขตตรงไปตรงมาของโมดูลนี้ว่า docker stack บนแล็ปท็อปทำอะไรได้บ้างสำหรับทั้งสามอย่าง
  • The AI Bug Catalog — 21 pattern เจาะจง แต่ละอันมีโค้ดที่ AI มักเขียน, output จริงที่จับใหม่สด ๆ กับ broker Kafka 4.3.1 จริง, และวิธีแก้
  • Review Checklist — 21 pattern เดียวกันในรูป checklist หน้าเดียว เจ็ดกลุ่ม แต่ละบรรทัดลิงก์ไปบทที่พิสูจน์มัน
  • Verification Tools — tsc --noEmit, kafka-configs.sh --describe --all, kafka-topics.sh --describe, kafka-consumer-groups.sh, kafka-dump-log.sh, kafka-console-share-consumer.sh, kafka-features.sh, kafka-acls.sh, REST API ของ Connect และ Schema Registry, และ harness ของคอร์สนี้เอง แต่ละตัวพิสูจน์อะไรได้จริง
  • worked review สามบท — Worked Review 1, Worked Review 2, Worked Review 3 — ใช้ลำดับการอ่านนี้กับ service ที่ AI สร้างเต็มรูปแบบ (order-service producer ที่ต้อง review ทางเลือก acks/idempotence, payment consumer ที่สร้างบน exactly-once transaction บวก dead-letter queue, และ streaming pipeline ที่ครอบคลุม Connect, Streams, และ Schema Registry) สามบทนี้เขียนและดูแลแยกจากบทนี้ — ถ้าลิงก์ด้านล่าง 404 ระหว่างที่งานนั้นยังไม่เสร็จ ลำดับการอ่านและ catalog ข้างบนยังยืนได้ด้วยตัวเอง
  • ทุกบท Phase 3 ก่อนหน้าในคอร์สนี้มี section ## Reading AI-generated code ของตัวเองพร้อม <SpotTheBug> อยู่แล้ว — catalog ของโมดูลนี้ไม่ทำซ้ำอันเหล่านั้น มันรวบรวม pattern ใหม่ 21 อันที่กว้างกว่าไว้รอบลำดับการอ่านเดียวกัน เริ่มจาก What Is Kafka ถ้าต้องการเส้นทางบทต่อบทแบบเต็ม
Reading order for Kafka code you did not write:
1. Client & version → which client, which Kafka version does this assume?
2. Broker & topic config → do RF, min.insync.replicas, and retention actually agree?
3. Producer config → acks, idempotent, keys -- durability the client never validates
4. Consumer loop → heartbeat timing, commit offset math, autoCommit
5. Delivery & transactions → sendOffsets vs a separate commit, retriable vs fatal
6. Ops & security → hallucinated tools/flags/keys, authorizer, listeners
7. Connect/Streams/Schema → standalone vs distributed, SMT value shapes, compatibility
"It ran without an error" answers almost none of these -- most of this module's 21
catalog entries run cleanly and lose data, duplicate it, or silently do nothing.
  • “ถ้า tsc กับ broker ทั้งคู่รับได้ ก็ถูกแล้ว” พิสูจน์ตลอดทั้ง catalog ของโมดูลนี้: KafkaJS producer ที่ idempotent ถูกทิ้งไว้ที่ default จริงคือ false ผ่าน type-check, connect, และ send ได้ — client ตรวจแค่ type กับ connectivity ไม่เคยตรวจ durability guarantee ที่ผู้เรียกคิดว่าตัวเองได้
  • “โค้ด Kafka ที่ AI สร้างแค่ต้องการ prompt ใหม่กว่านี้” ปัญหาไม่ใช่แค่ความล้าสมัย — group.protocol=consumer เป็น setting ของ KIP-848 ที่จริง current และ GA แล้ว แต่ไม่มีความหมายเลยบน client ที่มันถูกก็อปเข้าไปซ้ำ ๆ (KafkaJS) เพราะ client ตัวนี้ไม่เคยถูกอัปเดตให้พูด protocol นี้เลย
  • “no-op เงียบ ๆ เกิดขึ้นน้อยกว่า config ที่ถูกปฏิเสธ เลยสำคัญน้อยกว่า” ตรงข้ามต่างหากที่ใกล้ความจริงกว่าใน catalog นี้: min.insync.replicas=2 บน replication factor 1 ถูก รับ ไม่ใช่ถูกปฏิเสธ และจะโชว์ต้นทุนของมันแค่วันที่ broker restart จริง ๆ; manual commit ที่ผิดไปหนึ่ง offset ถูก รับ ไม่ใช่ถูกปฏิเสธ และจะโชว์ต้นทุนของมันในรูป record ที่ถูกประมวลผลซ้ำอีกหลายวันถัดมา
  • “KafkaJS กับ Java client เป็น API เดียวกันแค่คนละภาษา” ทั้งสองต่างกันเรื่อง default (acks, idempotent), เรื่อง lifecycle (ไม่มี initTransactions() แยก), เรื่องอะไรมีมาให้ในตัว (compression codec เดียว, partition assigner เดียว), และเรื่องเข้าร่วมอะไรได้เลย (ไม่รองรับ share group) — การเอา mental model ของ client ตัวหนึ่งไปใช้เป็นสากลเองก็เป็นหนึ่งใน pattern ที่พบบ่อยที่สุดใน catalog นี้
ทำไมโมดูลนี้ถึงตรวจ client กับ version ของมันก่อนตรวจอะไรเกี่ยวกับ producer config หรือ consumer loop?
จาก output จริงนี้ การสร้าง topic สำเร็จบอกอะไรจริงเกี่ยวกับว่า write แบบ acks=all ไปยัง topic นี้ปลอดภัยหรือไม่?
$ kafka-topics.sh --bootstrap-server localhost:9092 --create --topic t --replication-factor 1 --config min.insync.replicas=2
Created topic t.
ทำไมโมดูลนี้ถึงมอง 'consumer print message ทุกตัวถูกต้องในการทดสอบของฉัน' เป็นเรื่องของลำดับการอ่าน Consumer loop แทนที่จะเป็นหลักฐานว่า commit logic ถูกต้อง?
ขั้นไหนในลำดับการอ่านนี้ที่ถามเจาะจงว่า class name ของ connector resolve บน discoverable plugin path จริงของ worker หรือไม่ — ไม่ใช่แค่ว่า jar มีอยู่บน disk ที่ไหนสักแห่ง?
ทำไมโมดูลนี้บอกว่า config ที่ถูกรับเงียบ ๆ (อย่าง min.insync.replicas=2 บน RF=1) น่าเป็นห่วงกว่า config ที่ broker ปฏิเสธตรง ๆ แทนที่จะน่าเป็นห่วงน้อยกว่า?