☕ 5-Min Tech Story #43

ตัดเงินแล้วแต่ตัดสต็อกไม่ผ่าน: ทำไม Two-Phase Commit ถึงพาไมโครเซอร์วิสกอดคอกันตายหมู่?

⏱️ ใช้เวลาอ่านประมาณ: 4 นาที 30 วินาที

ในยุคแอป Monolith การซื้อของเป็นเรื่องง่ายดาย: ครอบโค้ดด้วย @Transactional บรรทัดเดียว ตัดเงินในตาราง A ตัดสต็อกในตาราง B ถ้าพังตรงไหน Database จะช่วย ROLLBACK ข้อมูลคืนให้สะอาดหมดจด

แต่วันนี้ระบบของคุณแตกกระจายเป็น Microservices:

โจทย์คือ: "ถ้าหักเงินไปแล้ว 1,000 บาท... แต่พอจะไปตัดสต็อก ปรากฏว่าของหมดพอดี! เราจะเอาเงินคืนลูกค้าและยกเลิกออเดอร์ข้าม 3 ฐานข้อมูลนี้ได้อย่างไร โดยไม่มีคำสั่ง ACID Transaction แบบเดิม?"

⛓️ โซ่ตรวนมรณะ: Two-Phase Commit (2PC)

วิศวกรยุคเก่ามักคิดถึง Two-Phase Commit (2PC / XA Transaction) โดยมีตัวกลาง (Coordinator) คอยคุมบังเหียน 2 สเต็ป:

  1. Phase 1 (Prepare): ผู้จัดการถามทุกเซอร์วิส: "ทุกคนพร้อมบันทึกข้อมูลไหม?" ทั้ง 3 เซอร์วิสจะสั่งล็อกแถวข้อมูล (Lock Row) ใน Database ของตัวเองไว้แน่นหนา แล้วตะโกนตอบว่า "พร้อม!"
  2. Phase 2 (Commit): ถ้าทุกคนตอบพร้อม ผู้จัดการจะสั่ง: "ทุกคน Commit ได้!"
"แต่จุดวิบัติคือ: ในระหว่างที่ทุกคนถือ Lock ไว้ใน Phase 1 เพื่อรอคำสั่ง Commit... ถ้าเกิดเน็ตเวิร์กสะอึก หรือตัว Coordinator ดับวูบไป ทุก Database จะติดสถานะ 'แช่แข็ง (Blocking Lock)' ค้างเติ่งไว้ตลอดกาล คนอื่นห้ามแตะสต็อก ห้ามแตะยอดเงิน ทราฟฟิกทั้งระบบจะสำลักตายในพริบตา!"

🎭 ทางรอดแห่งโลกกระจายศูนย์: The Saga Pattern

ในระบบ Distributed Systems ระดับโลกอย่าง Netflix หรือ Uber ไม่มีใครใช้ 2PC อีกต่อไป... พวกเขาเปลี่ยนมาใช้ Saga Pattern

หัวใจของ Saga คือ: "เลิกบังคับให้ทุกอย่างเสร็จพร้อมกันในวินาทีเดียว (ACID) แต่ยอมรับความจริงแบบเสร็จทีละขั้น (Eventual Consistency)"

การทำงานจะแบ่งเป็น Transaction ย่อยๆ ที่ทำเสร็จแล้ว Commit ทันทีทีละตัว:

↩️ คาถาแก้กรรม: Compensating Transaction (ธุรกรรมชดเชย)

เมื่อสเต็ปที่ 3 ล้มเหลว ระบบจะ ไม่สามารถกด Ctrl+Z ย้อนเวลาได้ เพราะเงินถูกหักลง Database ไปแล้วจริงๆ!

สิ่งเดียวที่ทำได้คือการรัน "ธุรกรรมชดเชย (Compensating Transaction)" เดินถอยหลังทีละสเต็ปเพื่อหักล้างผลลัพธ์เดิม:

// เส้นทางเดินถอยหลังเมื่อเกิดข้อผิดพลาด (Rollback เชิงตรรกะ)
[Inventory] สต็อกหมด ➔ ยิง Event "STOCK_FAILED"
[Payment]   ได้รับ Event ➔ สั่ง "โอนเงินคืน (Refund) 1,000 บาท"
[Order]     ได้รับ Event ➔ อัปเดตสถานะเป็น "CANCELLED"

เหมือนเวลาเราจองตั๋วเครื่องบิน: ถ้าหักเงินสำเร็จแต่ดันออกตั๋วไม่ผ่าน สายการบินไม่ได้ย้อนเวลา แต่เขาส่งคำสั่ง "คืนเงินเข้าบัตรเครดิต" ให้คุณตามหลังแทน

🧭 2 รูปแบบของ Saga: Choreography vs Orchestration

💡 5-Min Takeaway

"ในโลก Distributed การล็อกข้อมูลข้ามเครือข่ายคือยาพิษ แต่การยอมรับความจริงแล้วเตรียมแผนชดเชยคือทางรอด" จงหยุดพยายามสร้าง ACID Transaction ข้ามไมโครเซอร์วิสด้วย Two-Phase Commit ที่พร้อมจะล่มสลายยามเครือข่ายสะอึก หันมาใช้ Saga Pattern ร่วมกับ Compensating Transactions เพื่อให้ทุกเซอร์วิสทำงานได้อย่างอิสระ รวดเร็ว และสามารถฟื้นฟูความถูกต้องของข้อมูลกลับมาได้เสมอแม้ในวันที่เลวร้ายที่สุด