Data contract ควรพังใน CI ไม่ใช่ใน Production
ทำไมผมถึงทดลองใช้ data contract testing เพื่อจับ breaking change ของ data pipeline ให้เร็วขึ้นตั้งแต่ใน CI
- data-engineering
- data-contract
- ci-cd
- testing
Data pipeline สามารถขึ้นสถานะเขียวได้ ทั้งที่ข้อมูลข้างในเริ่มพังไปแล้ว
Job ทำงานจบ Container exit code 0 Scheduler มองว่าทุกอย่างปกติ แต่หลังจากนั้น dashboard อาจว่าง transformation อาจเริ่มสร้างค่า null หรือ downstream consumer อาจเปลี่ยนพฤติกรรมเพราะโครงสร้างข้อมูลถูกเปลี่ยน
ช่องว่างตรงนี้คือสิ่งที่ผมสนใจ
ปัญหาไม่ได้มีแค่ pipeline ล้ม
CI แบบทั่วไปเก่งมากในการตรวจโค้ด เรามี lint, type check, unit test และสามารถ block merge ได้เมื่อโปรแกรมไม่เป็นไปตามที่คาดไว้
แต่ระบบข้อมูลมี interface อีกชั้นที่ต้องปกป้อง นั่นคือ data contract ระหว่าง producer กับ consumer
Producer อาจยังส่ง JSON ที่ valid แต่เปลี่ยนชื่อ field ได้ Table อาจยังอยู่แต่เปลี่ยนชนิด column จาก integer เป็น string ได้ และ infrastructure ทุกอย่างอาจผ่านหมดทั้งที่ downstream job ใช้งานข้อมูลต่อไม่ได้
ถ้าข้อตกลงเหล่านี้อยู่แค่ในหัวของคนหรือซ่อนอยู่ใน implementation ของ consumer เราจะรู้ว่ามีปัญหาช้าเกินไป
Data contract ที่ผมอยากได้ควรทำอะไร
สำหรับระบบที่ผมกำลังทดลอง contract ที่มีประโยชน์ควร execute ได้จริง และอธิบายอย่างน้อยเรื่องต่อไปนี้
- field หรือ column ที่จำเป็น
- type ที่คาดหวัง
- nullable และ non-nullable
- กฎ compatibility เมื่อ schema เปลี่ยน
- invariant ทางธุรกิจที่สำคัญพอจะ block release
สิ่งสำคัญไม่ใช่ว่า contract เขียนด้วย format ไหน แต่คือ เราบังคับใช้มันตรงไหน
จุดที่ผมอยากให้ failure เกิดคือ CI
ย้าย failure มาอยู่ที่ Pull Request
สมมติ producer เปลี่ยนจาก
customer_id: integer
เป็น
customer_id: string
การเปลี่ยนนี้อาจตั้งใจทั้งหมด คำถามคือ consumer พร้อมหรือยัง
แทนที่จะรอเจอ mismatch หลัง deploy เราสามารถให้ CI เปรียบเทียบ schema ใหม่กับ contract แล้วจัดประเภทการเปลี่ยนได้ Compatible change ก็ผ่าน ส่วน breaking change ก็ fail พร้อมคำอธิบายที่เอาไปแก้ต่อได้
จาก production incident จึงกลายเป็นบทสนทนาใน pull request
Flow โดยคร่าว ๆ จะเป็น
code change -> build -> unit tests -> data contract validation -> compatibility check -> deploy
Contract check ไม่ได้มาแทน integration test หรือ production monitoring แต่มันย้าย failure บางประเภทให้เกิดเร็วขึ้น ตอนที่ต้นทุนในการแก้ยังต่ำ
ส่วนที่ยากกว่า schema validation ปกติ
การตรวจว่า JSON ตรง schema หรือไม่เป็นส่วนที่ค่อนข้างตรงไปตรงมา คำถามที่น่าสนใจกว่าคือเรื่อง evolution เช่น
- เพิ่ม nullable field ถือว่า breaking หรือไม่
- เปลี่ยน integer เป็น number ได้หรือเปล่า
- Producer ลบ field ที่ยังไม่มี consumer ใช้ได้ไหม
- ถ้ามีหลาย consumer ใครควรเป็นเจ้าของ contract
- CI ควรรายงาน failure แบบไหนให้ developer ลงมือแก้ต่อได้ทันที
คำถามเหล่านี้ทำให้เรื่องนี้ไม่ใช่แค่ validator แต่เป็น workflow problem
เพราะงั้นงานที่ผมกำลังทดลองจึงไม่ได้เน้นสร้าง schema language ใหม่ แต่เน้นว่า contract testing ควรวางตัวอยู่ตรงไหนใน CI/CD ของ data pipeline จริง
ทิศทางที่ผมกำลังทดลอง
Prototype ตอนนี้โฟกัส loop เล็ก ๆ
- กำหนด contract ที่เครื่องอ่านได้
- validate schema หรือ sample dataset ที่กำลังจะเปลี่ยน
- เปรียบเทียบกับ contract ที่ยอมรับอยู่
- แยก incompatible change
- คืนผลลัพธ์และคำอธิบายที่ CI ใช้งานได้
ผมอยากให้ interface เล็กพอที่จะรันได้ทั้งบนเครื่อง developer และ GitHub Actions โดยไม่ต้องตั้ง data platform ขนาดใหญ่เพิ่ม
ถ้าทำได้ดี ผลลัพธ์ที่มีค่าจะไม่ใช่ contract file ที่ดูเท่ แต่เป็น failure ที่ธรรมดา ชัดเจน และเกิดก่อน merge
และนี่คือจุดที่ผมอยากให้ปัญหาประเภทนี้กลายเป็นเรื่องน่าเบื่อ