Database Transactions (Database Transaction)

โดยทั่วไป แล้วเมื่อเราคิดถึง transaction ในบริบทของงานคอมพิวเตอร์ เราคิดถึงการกระทำหนึ่งอย่างหรือมากกว่านั้นที่จะเกิดขึ้น ซึ่งเราต้องการปฏิบัติเสมือนเป็นหน่วยเดียว เมื่อทำการเปลี่ยนแปลงหลายอย่างในฐานะส่วนหนึ่งของ operation เดียวกัน เราต้องการยืนยันว่าการเปลี่ยนแปลงทั้งหมดสำเร็จแล้วหรือไม่ เรายังต้องการวิธีทำความสะอาดตัวเองถ้าเกิด error ขึ้นระหว่างที่การเปลี่ยนแปลงเหล่านี้กำลังเกิดขึ้น โดยทั่วไปแล้วนี่ทำให้เราใช้อะไรบางอย่างเช่น database transaction

กับ database เราใช้ transaction เพื่อให้แน่ใจว่าการเปลี่ยนแปลง state หนึ่งอย่างหรือมากกว่านั้นสำเร็จแล้ว ซึ่งอาจรวมถึงข้อมูลที่ถูกลบ เพิ่ม หรือเปลี่ยนแปลง ใน relational database นี่อาจเกี่ยวข้องกับหลาย table ที่ถูกอัปเดตภายใน transaction เดียว

ACID Transactions (ACID Transaction)

โดยทั่วไป แล้วเมื่อเราพูดถึง database transaction เรากำลังพูดถึง ACID transaction ACID เป็นตัวย่อที่สรุปคุณสมบัติสำคัญของ database transaction ที่นำไปสู่ระบบที่เราสามารถพึ่งพาได้เพื่อรับประกัน durability และ consistency ของ data storage ของเรา ACID ย่อมาจาก atomicity, consistency, isolation, and durability และนี่คือสิ่งที่คุณสมบัติเหล่านี้ให้เรา:

Atomicity

รับประกันว่า operation ที่พยายามทำภายใน transaction จะสำเร็จทั้งหมดหรือล้มเหลวทั้งหมด ถ้าการเปลี่ยนแปลงใด ๆ ที่เรากำลังพยายามทำล้มเหลวด้วยเหตุผลบางอย่าง operation ทั้งหมดจะถูก abort และเสมือนว่าไม่มีการเปลี่ยนแปลงใด ๆ เกิดขึ้นเลย

Consistency

เมื่อมีการเปลี่ยนแปลงเกิดขึ้นกับ database ของเรา เรารับประกันว่ามันจะอยู่ใน state ที่ valid และ consistent

Isolation

อนุญาตให้ transaction หลายตัวทำงานพร้อมกันได้โดยไม่รบกวนกัน สิ่งนี้เกิดขึ้นได้โดยรับประกันว่าการเปลี่ยนแปลง state ระหว่างทางที่เกิดขึ้นใน transaction หนึ่งจะมองไม่เห็นจาก transaction อื่น

Durability

รับประกันว่าเมื่อ transaction เสร็จสมบูรณ์แล้ว เรามั่นใจได้ว่าข้อมูลจะไม่หายไปในกรณีที่ระบบล้มเหลว

ควรสังเกตว่าไม่ใช่ database ทุกตัวที่รองรับ ACID transaction relational database system ทุกตัวที่ผมเคยใช้ทำได้ เช่นเดียวกับ NoSQL database ใหม่ ๆ หลายตัวอย่าง Neo4j MongoDB ในหลายปีที่ผ่านมารองรับ ACID transaction เฉพาะกับการเปลี่ยนแปลงที่ทำกับ document เดียวเท่านั้น ซึ่งอาจก่อปัญหาได้ถ้าคุณต้องการทำ atomic update กับมากกว่าหนึ่ง document 1

หนังสือเล่มนี้ไม่ได้ตั้งใจสำรวจแนวคิดเหล่านี้อย่างละเอียด ผมทำให้คำอธิบายบางส่วนเรียบง่ายขึ้นเพื่อความกระชับ สำหรับผู้ที่อยากสำรวจแนวคิดเหล่านี้เพิ่มเติม ผมแนะนำ Designing Data-Intensive Applications 2 เราจะเน้นเรื่อง atomicity เป็นหลักในเนื้อหาที่ตามมา นั่นไม่ได้หมายความว่าคุณสมบัติอื่น ๆ ไม่สำคัญ แต่การรับมือกับ atomicity ของ database operation มักเป็นปัญหาแรกที่เราเจอเมื่อเริ่มแยก functionality ออกเป็น microservice

Still ACID, but Lacking Atomicity? (ยังคงเป็น ACID แต่ขาด Atomicity?)

ผม อยากชี้แจงให้ชัดเจนว่าเรายังสามารถใช้ transaction แบบ ACID ได้เมื่อใช้ microservice microservice หนึ่งมีอิสระที่จะใช้ ACID transaction สำหรับ operation กับ database ของตัวเองได้ ยกตัวอย่างเช่น แค่ว่าขอบเขตของ transaction เหล่านี้ถูกลดลงเหลือแค่การเปลี่ยนแปลง state ที่เกิดขึ้นภายใน microservice ตัวเดียวนั้นเท่านั้น ลองดู Figure 6-1 ที่นี่ เรากำลังติดตามกระบวนการที่เกี่ยวข้องกับการรับลูกค้าใหม่เข้ามาที่ MusicCorp เรามาถึงจุดสิ้นสุดของกระบวนการ ซึ่งเกี่ยวข้องกับการเปลี่ยน Status ของลูกค้า 2346 จาก PENDING เป็น VERIFIED เนื่องจากการลงทะเบียนเสร็จสมบูรณ์แล้ว เรายังต้องการลบ row ที่ตรงกันออกจาก table PendingEnrollments ด้วย ด้วย database เดียว สิ่งนี้ทำได้ในขอบเขตของ ACID database transaction เดียว คือการเปลี่ยนแปลง state ทั้งสองนี้เกิดขึ้นทั้งคู่ หรือไม่เกิดขึ้นเลยทั้งคู่

bms2 0601

Figure 6-1. การอัปเดตสอง table ภายในขอบเขตของ ACID transaction เดียว

ลองเปรียบเทียบกับ Figure 6-2 ที่เรากำลังทำการเปลี่ยนแปลงแบบเดียวกันเป๊ะ ๆ แต่แต่ละการเปลี่ยนแปลงเกิดขึ้นใน database คนละตัวกัน นั่นหมายความว่ามี transaction สองตัวที่ต้องพิจารณา ซึ่งแต่ละตัวสามารถทำงานสำเร็จหรือล้มเหลวได้อย่างเป็นอิสระจากกัน

bms2 0602

Figure 6-2. การเปลี่ยนแปลงที่ทำโดย Customer และ Enrollments microservice ตอนนี้ทำในขอบเขตของ transaction สองตัวที่แยกกัน

แน่นอนว่าเราอาจตัดสินใจเรียงลำดับ transaction ทั้งสองนี้ โดยลบ row ออกจาก table PendingEnrollments ก็ต่อเมื่อเราสามารถเปลี่ยน row ใน table Customer ได้เท่านั้น แต่เราก็ยังต้องคิดว่าจะทำอย่างไรถ้าการลบออกจาก table PendingEnrollments ล้มเหลว ทั้งหมดนี้คือ logic ที่เราต้อง implement เอง การจัดลำดับขั้นตอนใหม่เพื่อจัดการ use case เหล่านี้ได้ดีกว่าเป็นแนวคิดที่มีประโยชน์มาก (เราจะกลับมาพูดถึงเรื่องนี้อีกครั้งเมื่อเราสำรวจ saga) แต่โดยพื้นฐานแล้ว เราต้องยอมรับว่าการแยก operation นี้ออกเป็น database transaction สองตัวที่แยกกัน ทำให้เราสูญเสีย atomicity ที่รับประกันได้ของ operation ทั้งหมด

การขาด atomicity นี้อาจเริ่มก่อปัญหาที่มีนัยสำคัญได้ โดยเฉพาะถ้าเรากำลัง migrate ระบบที่เคยพึ่งพาคุณสมบัตินี้ โดยปกติแล้ว ตัวเลือกแรกที่คนเริ่มพิจารณาคือยังคงใช้ transaction เดียว แต่ตอนนี้ครอบคลุมหลายโปรเซส นั่นคือ distributed transaction แต่น่าเสียดายที่อย่างที่เราจะเห็นกัน distributed transaction อาจไม่ใช่แนวทางที่ถูกต้อง มาดูหนึ่งใน algorithm ที่พบบ่อยที่สุดสำหรับ implement distributed transaction คือ two-phase commit เพื่อสำรวจความท้าทายที่มากับ distributed transaction โดยรวม