Distributed Transactions—Two-Phase Commits (Distributed Transaction — Two-Phase Commit)

two-phase commit algorithm (บางครั้งเรียกย่อว่า 2PC ) ถูกใช้บ่อยครั้งในความพยายามที่จะให้ความสามารถแก่เราในการทำการเปลี่ยนแปลงแบบ transactional ใน distributed system ที่โปรเซสแยกกันหลายตัวอาจต้องถูกอัปเดตในฐานะส่วนหนึ่งของ operation ทั้งหมด distributed transaction และโดยเฉพาะ two-phase commit มักถูกทีมที่ย้ายไปใช้ microservice architecture พิจารณาเป็นวิธีแก้ปัญหาที่พวกเขาเจอ แต่อย่างที่เราจะเห็นกัน มันอาจไม่ได้แก้ปัญหาของคุณ และอาจนำความสับสนมาสู่ระบบของคุณมากขึ้นด้วยซ้ำ

2PC แบ่งออกเป็นสอง phase (ด้วยเหตุนี้จึงชื่อ two-phase commit ) คือ voting phase และ commit phase ระหว่าง voting phase central coordinator จะติดต่อ worker ทั้งหมดที่จะเป็นส่วนหนึ่งของ transaction และขอการยืนยันว่าการเปลี่ยนแปลง state บางอย่างสามารถทำได้หรือไม่ ใน Figure 6-3 เราเห็น request สองตัว ตัวหนึ่งเพื่อเปลี่ยน customer status เป็น VERIFIED และอีกตัวหนึ่งเพื่อลบ row ออกจาก table PendingEnrollments ของเรา ถ้า worker ทุกตัวเห็นด้วยว่าการเปลี่ยนแปลง state ที่ถูกขอนั้นสามารถเกิดขึ้นได้ algorithm จะดำเนินไปยัง phase ถัดไป ถ้า worker ตัวใดตัวหนึ่งบอกว่าการเปลี่ยนแปลงนั้นทำไม่ได้ อาจเพราะการเปลี่ยนแปลง state ที่ถูกขอนั้นละเมิดเงื่อนไข local บางอย่าง operation ทั้งหมดจะถูก abort

bms2 0603

Figure 6-3. ใน phase แรกของ two-phase commit worker จะโหวตเพื่อตัดสินใจว่าพวกเขาสามารถทำการเปลี่ยนแปลง state ในระดับ local ได้หรือไม่

สิ่งสำคัญที่ต้องเน้นย้ำคือการเปลี่ยนแปลงจะไม่มีผลทันทีหลังจาก worker บอกว่ามันสามารถทำการเปลี่ยนแปลงได้ แทนที่จะเป็นแบบนั้น worker กำลังรับประกันว่ามันจะสามารถทำการเปลี่ยนแปลงนั้นได้ ณ จุดใดจุดหนึ่งในอนาคต worker จะรับประกันแบบนี้ได้อย่างไร? ใน Figure 6-3 ยกตัวอย่างเช่น Worker A บอกว่ามันจะสามารถเปลี่ยน state ของ row ใน table Customer เพื่ออัปเดต status ของลูกค้าคนนั้นเป็น VERIFIED ได้ แต่ถ้ามี operation อื่นในภายหลังลบ row นั้นออกไป หรือทำการเปลี่ยนแปลงเล็ก ๆ อย่างอื่นที่ทำให้การเปลี่ยนเป็น VERIFIED ในภายหลังไม่ valid ล่ะ? เพื่อรับประกันว่าการเปลี่ยนเป็น VERIFIED จะทำได้ในภายหลัง Worker A น่าจะต้อง lock record นั้นเพื่อให้แน่ใจว่าการเปลี่ยนแปลงอื่น ๆ จะเกิดขึ้นไม่ได้

ถ้า worker ตัวใดไม่ได้โหวตสนับสนุนการ commit ต้องส่ง rollback message ไปยังทุกฝ่ายเพื่อให้แน่ใจว่าพวกเขาสามารถทำความสะอาด local ได้ ซึ่งช่วยให้ worker ปลด lock ใด ๆ ที่พวกเขาอาจถืออยู่ ถ้า worker ทั้งหมดเห็นด้วยที่จะทำการเปลี่ยนแปลง เราจะย้ายไปยัง commit phase ดังใน Figure 6-4 ที่นี่ การเปลี่ยนแปลงจะถูกทำจริง ๆ และ lock ที่เกี่ยวข้องจะถูกปลด

bms2 0604

Figure 6-4. ใน commit phase ของ two-phase commit การเปลี่ยนแปลงจะถูก apply จริง ๆ

สิ่งสำคัญที่ต้องสังเกตคือในระบบแบบนี้ เราไม่สามารถรับประกันได้ไม่ว่าจะด้วยวิธีใดว่า commit เหล่านี้จะเกิดขึ้นในเวลาเดียวกันเป๊ะ ๆ Coordinator ต้องส่ง commit request ไปยัง participant ทั้งหมด และ message นั้นอาจมาถึงและถูกประมวลผลในเวลาที่ต่างกัน นั่นหมายความว่าเป็นไปได้ที่เราจะเห็นการเปลี่ยนแปลงเกิดขึ้นที่ Worker A แล้ว แต่ยังไม่เกิดที่ Worker B ถ้าเราสามารถสังเกต state ของ worker ตัวใดตัวหนึ่งได้โดยตรง ยิ่งมี latency ระหว่าง Coordinator กับ participant ใน two-phase commit มากเท่าไหร่ และยิ่ง worker ประมวลผล response ช้าเท่าไหร่ ช่วงเวลาที่ inconsistent นี้ก็ยิ่งกว้างขึ้นเท่านั้น กลับมาที่นิยามของ ACID isolation รับประกันว่าเราจะไม่เห็น intermediate state ระหว่าง transaction แต่ด้วย two-phase commit นี้ เราสูญเสียการรับประกันนั้นไปแล้ว

เมื่อ two-phase commit ทำงาน แก่นแท้ของมันมักเป็นแค่การประสาน distributed lock worker ต้อง lock resource ระดับ local เพื่อให้แน่ใจว่า commit จะเกิดขึ้นได้ใน phase ที่สอง การจัดการ lock และหลีกเลี่ยง deadlock ในระบบ single-process เดียวก็ไม่ใช่เรื่องสนุกอยู่แล้ว ลองนึกภาพความท้าทายในการประสาน lock ระหว่าง participant หลายตัว มันไม่สวยงามเลย

มี failure mode มากมายที่เกี่ยวข้องกับ two-phase commit ที่เราไม่มีเวลาสำรวจ ลองพิจารณาปัญหาที่ worker โหวตให้ดำเนิน transaction ต่อไป แต่แล้วก็ไม่ตอบสนองเมื่อถูกขอให้ commit เราควรทำอย่างไรในกรณีนี้? failure mode บางแบบสามารถจัดการได้อัตโนมัติ แต่บางแบบอาจทำให้ระบบอยู่ใน state ที่ต้องแก้ไขด้วยมือโดย operator

ยิ่งคุณมี participant มากเท่าไหร่ และยิ่งมี latency ในระบบมากเท่าไหร่ two-phase commit ก็ยิ่งมีปัญหามากขึ้นเท่านั้น 2PC สามารถเป็นวิธีที่รวดเร็วในการเพิ่ม latency จำนวนมากให้กับระบบของคุณ โดยเฉพาะถ้าขอบเขตของการ lock ใหญ่ หรือถ้าระยะเวลาของ transaction ยาวนาน ด้วยเหตุผลนี้ two-phase commit จึงมักถูกใช้เฉพาะกับ operation ที่มีอายุสั้นมากเท่านั้น ยิ่ง operation ใช้เวลานานเท่าไหร่ คุณก็ยิ่ง lock resource ไว้นานขึ้นเท่านั้น!