Real-Time CDC & Event Sourcing at Scale
สถาปัตยกรรม CDC & Event Sourcing สมัยใหม่
เรียนรู้วิธีการทำ Real-time Data Integration ผ่าน Change Data Capture (CDC), Debezium, Kafka Connect และการแก้ปัญหา Dual-Write ด้วย Transactional Outbox Pattern เพื่อสร้าง Microservices ที่แข็งแกร่ง
- Step 1: Data Ingestion & Event Processing
- Step 2: Distributed Computation & State Management
- Step 3: Orchestration & Resource Allocation
1. Debezium & Change Data Capture (CDC)
ทฤษฎีและกลไกการทำงาน
Learning Progression
- 🟢 Basic Level: ปูพื้นฐานภาษาเข้าใจง่าย - เข้าใจคอนเซปต์ภาพรวมและการทำงานเบื้องต้นแบบไม่ซับซ้อน
- 🟡 Intermediate Level: โค้ด/คอนฟิกไวยากรณ์จริง - การเขียนโค้ดและตั้งค่าพารามิเตอร์เพื่อใช้งานในระบบจริง
- 🔴 Professional Level: Under-the-hood & Performance/FinOps - กลไกเบื้องลึก การจูนประสิทธิภาพ และการจัดการทรัพยากร
🏢 Real-World Enterprise Scenario
เคสระบบการเงิน/Big Tech: การรองรับ Transaction จำนวนมหาศาลต่อวินาทีพร้อมประกัน Data Integrity สูงสุด ลด Latency และประหยัดค่าใช้จ่ายผ่าน FinOps practices
Change Data Capture (CDC) คือรูปแบบการดึงข้อมูลที่มีการเปลี่ยนแปลงระดับแถว (INSERT, UPDATE, DELETE) ในฐานข้อมูลแล้วส่งออกไปแบบ Real-time. Debezium คือแพลตฟอร์ม Open-source ที่ทำหน้าที่นี้ แทนที่จะรันคำสั่ง Query ดึงข้อมูลรัวๆ (ซึ่งทำให้ DB อืด) Debezium จะเข้าไปอ่านไฟล์ Transaction Log ลึกๆ ของ Database (เช่น binlog ของ MySQL, WAL ของ PostgreSQL) แปลงเป็น Event แบบ JSON/Avro แล้วสตรีมเข้า Apache Kafka
ตัวอย่างโค้ดหรือการตั้งค่า
// ตัวอย่างการตั้งค่า Debezium PostgreSQL Connector บน Kafka Connect
{
"name": "inventory-connector",
"config": {
"connector.class": "io.debezium.connector.postgresql.PostgresConnector",
"database.hostname": "postgres-db",
"database.port": "5432",
"database.user": "debezium",
"database.password": "secret",
"database.dbname": "inventory",
"database.server.name": "dbserver1",
"table.include.list": "public.orders",
"plugin.name": "pgoutput"
}
}
Use Case ในชีวิตจริง
ระบบ E-commerce รุ่นเก่าใช้ MySQL ดาต้าเบสขนาดใหญ่ ต้องการสร้างระบบค้นหา (Search Engine) ใหม่ที่เร็วขึ้น จึงใช้ Debezium จับการเปลี่ยนแปลงในตารางสินค้า หากมีการปรับราคาหรือสต็อก Debezium จะส่งข้อมูลผ่าน Kafka ไปอัปเดตที่ Elasticsearch ทันทีในเสี้ยววินาที ทำให้การค้นหาสินค้าตรงกับของจริงเสมอโดยไม่กระทบโหลดของฐานข้อมูลหลัก
ข้อควรระวังและวิธีแก้
Pitfall: หาก Kafka ล่ม หรือ Debezium พังเป็นเวลานาน Database จะไม่สามารถลบไฟล์ Log (WAL/binlog) ทิ้งได้ ทำให้ดิสก์ของ Database เต็มและอาจพาโปรดักชันล่มทั้งระบบ
Mitigation: ต้องทำ Monitoring ขนาดของ Replication Slot และเนื้อที่ดิสก์ของ Database เสมอ รวมถึงการตั้งค่า Limit ขนาดไฟล์ WAL ไม่ให้เกินกำหนด
2. The Transactional Outbox Pattern
ทฤษฎีและกลไกการทำงาน
ใน Microservices การเขียนข้อมูลลง Database พร้อมกับส่ง Message ไป Kafka มักเจอปัญหา "Dual-write failure" (เช่น เซฟ DB ผ่าน แต่ Kafka ล่ม) Transactional Outbox Pattern แก้ปัญหานี้โดยการเซฟข้อมูลธุรกิจ และเซฟ Message ลงตาราง `outbox` ภายใต้ Database Transaction เดียวกัน ดังนั้นจะสำเร็จทั้งคู่หรือพังทั้งคู่ จากนั้นใช้ CDC (Debezium) มาอ่านตาราง `outbox` แล้วส่งไป Kafka รับประกันว่าข้อมูลไม่หายแน่นอน (At-Least-Once Delivery)
ตัวอย่างโค้ดหรือการตั้งค่า
-- แอปพลิเคชันรันคำสั่งภายใต้ Transaction เดียวกัน:
BEGIN;
-- 1. อัปเดตข้อมูลธุรกิจหลัก
INSERT INTO orders (id, customer_id, total)
VALUES ('order-123', 'cust-55', 150.00);
-- 2. เขียน Event ลงตาราง Outbox
INSERT INTO outbox (id, aggregate_type, aggregate_id, event_type, payload)
VALUES (uuid_generate_v4(), 'Order', 'order-123', 'OrderCreated', '{"id":"order-123","total":150.00}');
COMMIT;
Use Case ในชีวิตจริง
ระบบตัดเงิน (Billing) ต้องบันทึกว่าตัดเงินแล้ว และต้องแจ้งระบบขนส่ง (Shipping) ทันที การเขียนลงตาราง Invoice และตาราง Outbox ใน Transaction เดียวกัน ทำให้ระบบมั่นใจ 100% ว่าจะไม่มีออเดอร์ไหนตกหล่นค้างอยู่ในระบบ แม้แอปจะดับไปวินาทีที่เพิ่งเซฟเสร็จก็ตาม
ข้อควรระวังและวิธีแก้
Pitfall: ตาราง Outbox จะโตขึ้นเรื่อยๆ แบบไร้ขีดจำกัด ทำให้ Database ช้าลงและกินพื้นที่
Mitigation: ต้องสร้าง Background Job มาคอยลบข้อมูลใน Outbox ที่เก่าเกินไป หรือใช้ฟีเจอร์ Outbox Event Routing SMT ของ Debezium ที่สามารถตั้งให้ส่งคำสั่งลบข้อมูลกลับมาที่ DB ทันทีหลังส่งเข้า Kafka สำเร็จ
3. นิเวศน์ของ Kafka Connect (Ecosystem)
ทฤษฎีและกลไกการทำงาน
Kafka Connect คือ Framework สำหรับเชื่อมต่อระบบภายนอกเข้ากับ Kafka โดยไม่ต้องเขียนโค้ดเอง ประกอบด้วย Source Connectors (เช่น Debezium ดึงข้อมูลเข้า) และ Sink Connectors (เช่น JDBC, Elasticsearch ดันข้อมูลออก) Kafka Connect ช่วยจัดการเรื่องความเสถียร, การสเกลแบบ Distributed, และมีฟีเจอร์ SMT (Single Message Transformations) เพื่อปรับเปลี่ยนหน้าตาข้อมูลกลางอากาศได้
ตัวอย่างโค้ดหรือการตั้งค่า
// ตัวอย่างการใช้ SMT ดึงเฉพาะก้อน Payload ออกมาจากโครงสร้าง Event ที่ซับซ้อนของ Debezium
"transforms": "unwrap",
"transforms.unwrap.type": "io.debezium.transforms.ExtractNewRecordState",
"transforms.unwrap.drop.tombstones": "false",
"transforms.unwrap.delete.handling.mode": "rewrite"
Use Case ในชีวิตจริง
ทีม Data Engineering ต้องการดึงข้อมูลลูกค้าจาก CRM ไปลง Snowflake เพื่อวิเคราะห์ แทนที่จะเขียน Airflow DAGs หรือสคริปต์รายวัน ทีมงานรัน Kafka Connect วาง Source Connector ดึงจาก CRM แบบ Real-time และตั้ง Sink Connector อัดลง Snowflake ได้ทันทีเพียงแค่เขียนไฟล์ตั้งค่า (Configuration-driven) ไม่ต้องเขียนโค้ดแม้แต่บรรทัดเดียว
ข้อควรระวังและวิธีแก้
Pitfall: หากมีการเพิ่ม/ลบคอลัมน์ใน Database ฝั่งต้นทาง อาจทำให้ Schema ขัดแย้งกัน และทำให้ Sink Connector ปลายทางพังทั้งระบบ
Mitigation: บังคับใช้ Schema Registry เสมอ และกำหนดกฎการกลายพันธุ์ของ Schema (Schema Evolution) เช่น ให้เป็น Backward Compatibility เพื่อรับประกันว่าการแก้คอลัมน์จะไม่ทำลายไปป์ไลน์ปลายทาง
Weekend Sandbox Challenge 🚀
สร้างไปป์ไลน์ไร้รอยรั่ว: ลองสร้างสถาปัตยกรรม Outbox Pattern ด้วย Docker Compose
- รันคอนเทนเนอร์ PostgreSQL, Kafka และ Kafka Connect
- สร้างตาราง `orders` และ `outbox` ใน Postgres
- ส่ง Config ให้ Kafka Connect ทาง REST API เพื่อรัน Debezium ติดตามตาราง `outbox`
- ทดสอบ INSERT ข้อมูลลงทั้งสองตารางแบบ Transaction เดียว และเปิดดู Event ที่ไปโผล่ใน Kafka Topic อย่างถูกต้อง
คำถามสัมภาษณ์ระดับ Senior (Interview Q&A)
Q: ทำไมเราถึงควรใช้ Log-based CDC (อย่าง Debezium) แทนที่จะตั้ง Query เช็คข้อมูลแบบง่ายๆ เช่น `SELECT * FROM table WHERE updated_at > ?`
A: การ Query เช็คเป็นรอบๆ มีข้อเสียรุนแรง 3 ข้อ: 1. สร้างภาระโหลดหนักให้ Database หลัก 2. พลาดข้อมูลที่แก้ไขซ้ำซ้อนกันในรอบเดียว (เห็นเฉพาะค่าสุดท้าย) 3. จับการทำ Hard DELETE ไม่ได้เลยเพราะบรรทัดหายไปแล้ว Log-based CDC แก้ปัญหาเหล่านี้ด้วยการแอบอ่านไฟล์ WAL เบื้องหลัง จึงได้ประวัติที่ถูกต้อง 100% ครบทุกการแก้ไข ลบ และแทบไม่กินโหลดของ DB
Q: Outbox Pattern มีโอกาสทำให้เกิด Event ซ้ำ (Duplicate) หรือไม่ และจะแก้ไขฝั่งผู้รับอย่างไร?
A: เกิดได้แน่นอน เพราะกลไกคือ At-Least-Once Delivery (ส่งสำเร็จอย่างน้อยหนึ่งครั้ง) หาก Debezium ส่งข้อมูลให้ Kafka แล้วแครชก่อนที่จะเซฟ Offset ครั้งต่อไปจะมีการส่งซ้ำ ดังนั้นฝั่ง Microservices ผู้รับ (Consumer) ต้องถูกออกแบบให้เป็น **Idempotent** (รันกี่ครั้งผลลัพธ์ก็เท่าเดิม) โดยการเช็คจาก Event ID ถ้ารหัสเดิมเคยประมวลผลไปแล้ว ให้ข้าม (Ignore) การทำงานทันที