กรณีศึกษาการค้นพบ race condition ใน Aurora RDS
(hightouch.com)- กรณีศึกษาที่มีการยืนยันเชิงทดลองถึง บั๊ก race condition ที่เกิดขึ้นใน AWS Aurora RDS และได้รับการยืนยันสาเหตุจาก AWS
- ระหว่างที่ Hightouch ขยายระบบประมวลผลอีเวนต์ ได้พบปรากฏการณ์ที่การสลับ write instance ล้มเหลวในกระบวนการ failover (การสลับเมื่อระบบขัดข้อง) ของ Aurora
- จากการวิเคราะห์ล็อกพบว่า มีสองอินสแตนซ์ทำงานเขียนพร้อมกัน ส่งผลให้เกิดการชนกันในชั้นสตอเรจและโปรเซสถูกปิดการทำงาน
- AWS ยืนยันอย่างเป็นทางการว่าสาเหตุคือ ปัญหาในการจัดการสัญญาณภายใน ทำให้มีการโปรโมต writer ใหม่ก่อนที่การลดบทบาทของ writer เดิมจะเสร็จสมบูรณ์
- กรณีนี้เน้นย้ำถึง ความสำคัญของการควบคุมภาวะพร้อมกันในระบบกระจายขนาดใหญ่ และความจำเป็นของ ขั้นตอนหยุดการเขียน ระหว่างการทำ failover
ภูมิหลัง
- วันที่ 20 ตุลาคม 2025 ในรีเจียน AWS
us-east-1เกิดเหตุขัดข้องจาก บั๊ก race condition ในระบบจัดการ DNS - ส่งผลให้ Hightouch มี backlog ของการประมวลผลอีเวนต์ เพิ่มขึ้นอย่างรวดเร็วจนแตะขีดจำกัดของระบบ
- เพื่อเพิ่ม throughput จึงดำเนินการ อัปเกรด Aurora RDS instance ในวันที่ 23 ตุลาคม และในกระบวนการนี้ได้ค้นพบบั๊ก race condition ตัวใหม่
โครงสร้างระบบอีเวนต์ของ Hightouch
- ระบบที่รับรวบรวมและส่งต่ออีเวนต์ประกอบด้วย Kubernetes, Kafka และ Postgres(Aurora)
- Postgres ถูกใช้เป็น คิวเมทาดาทาแบบแบตช์ และรองรับการประมวลผล 500,000 อีเวนต์ต่อวินาทีภายใน 1 วินาที
- Aurora PostgreSQL ประกอบด้วยอินสแตนซ์ write-only (primary), อินสแตนซ์ read-only (replica) และ ชั้นสตอเรจแบบใช้ร่วมกัน
แผนอัปเกรด
- เพิ่ม read instance → อัปเกรด reader เดิมและกำหนดลำดับความสำคัญของ failover → ดำเนินการ failover → อัปเกรด writer เดิม → ลบ reader ชั่วคราว
- ขั้นตอนนี้เป็นวิธีที่ระบุไว้ในเอกสารของ AWS และเป็น กระบวนการที่ผ่านการตรวจสอบแล้วด้วยการทดสอบโหลดในสภาพแวดล้อม staging
ความพยายามอัปเกรดและการเกิดปัญหา
- วันที่ 23 ตุลาคม เวลา 16:39 EDT หลังดำเนินการ failover เกิดอาการที่ writer เดิมกลับมาเป็น primary อีกครั้ง
- ทั้งสองครั้งที่ลองได้ผลลัพธ์เหมือนกัน และบางบริการเกิดข้อผิดพลาด ไม่สามารถเขียนได้ (DatabaseError: cannot execute UPDATE in a read-only transaction)
- จากการวิเคราะห์ล็อกพบข้อความที่ยืนยันว่า สองอินสแตนซ์ทำงานเขียนพร้อมกันและยุติการทำงานจากการชนกันในสตอเรจ
สาเหตุของ race condition
- ระหว่างกระบวนการ failover ของ Aurora เกิด race condition ขึ้นระหว่าง ขั้นตอนที่ 3 (ลดบทบาท writer เดิม) และ ขั้นตอนที่ 4 (โปรโมต writer ใหม่)
- ส่งผลให้สองอินสแตนซ์มีสิทธิ์เขียนพร้อมกันและเกิดการชนกัน
- เมื่อทดลองใหม่โดยตัด write traffic ออกก่อน failover ก็เสร็จสมบูรณ์ตามปกติ จึง พิสูจน์สมมติฐานเรื่อง race condition ได้
การยืนยันและการตอบสนองจาก AWS
- หลังการตรวจสอบภายใน AWS ยืนยันว่า สาเหตุคือ ข้อผิดพลาดในการประมวลผลสัญญาณลดบทบาทของ writer และไม่เกี่ยวข้องกับการตั้งค่าหรือรูปแบบทราฟฟิกของ Hightouch
- การแก้ไขถูกบรรจุไว้ในโรดแมป แล้ว และมาตรการชั่วคราวที่แนะนำคือ หยุดการเขียนระหว่าง failover
มาตรการสุดท้าย
- Hightouch ดำเนินการอัปเกรดคลัสเตอร์เสร็จสมบูรณ์ และ
- เพิ่ม ขั้นตอนหยุดการเขียนก่อน failover ที่ตั้งใจดำเนินการ
- เสริมความเข้มงวดของ การมอนิเตอร์การเปลี่ยนบทบาท writer
- อัปเดต คู่มือปฏิบัติการ (playbook)
บทเรียนสำคัญ
- จำเป็นต้องเตรียมการกู้คืนสำหรับ การเปลี่ยนสถานะที่ไม่คาดคิดระหว่าง migration
- การมี observability คือหัวใจของการตรวจจับปัญหา
- ความสำคัญของ การออกแบบเพื่อลดผลกระทบระหว่างองค์ประกอบของระบบกระจาย
- ต้องตระหนักถึง ความแตกต่างระหว่างสภาพแวดล้อมทดสอบกับสภาพแวดล้อม production จริง
ไม่มีข้อมูลเพิ่มเติมในต้นฉบับ
1 ความคิดเห็น
ความเห็นจาก Hacker News
พออ่านบทความนี้แล้วดูเหมือนว่า ระหว่าง การสลับระบบฉุกเฉินด้วยตนเอง (failover) ถ้าแอปพลิเคชันยังพยายามรักษาทราฟฟิกการเขียนเหมือนปกติ ก็จะล้มเหลวเสมอ
แต่ก็มีคำถามอยู่บ้าง ว่าทำไมผู้ใช้ Aurora คนอื่นถึงไม่ได้เจอปัญหานี้ตลอด, AWS ไม่น่าจะไม่รู้เรื่องนี้, และถ้ารู้อยู่แล้วทำไมถึงไม่จัดการเป็น ปัญหาเร่งด่วนระดับ P0
เลยคิดว่าอาจมี เงื่อนไขละเอียดอ่อน บางอย่าง เช่น สถานะของทรานแซกชันที่กำลังดำเนินอยู่หรือ timeout เข้ามาเกี่ยวข้อง
SELECT ... FOR UPDATEล้มเหลวแบบเงียบ ๆ และทรานแซกชัน เปลี่ยนไปเป็นโหมด autocommit ไม่มีใครสนใจเลยจนผมเหมือนบ่นอยู่คนเดียว แล้วหลายเดือนต่อมาก็มีคนอื่นติดต่อมาว่าเจอปัญหาเดียวกัน สุดท้ายมันก็ถูกแก้ แต่ตอนนั้นผมเลิกตามไปแล้วลิงก์ที่เกี่ยวข้อง: คำถามบน Stack Overflow
เคยเห็น พฤติกรรมที่ไม่คาดคิด ใน Aurora PostgreSQL หลายครั้ง
โดยเฉพาะระหว่าง Zero Downtime Patching (ZDP) ที่สถานะของเซสชันถูกคงไว้ผิด ๆ จนแม้แต่คิวรีง่าย ๆ ก็ถูกยกเลิกเร็วกว่าค่า
statement_timeoutมากข้อสันนิษฐานของผมคือ ตอนที่ไคลเอนต์เชื่อมต่อใหม่ Aurora น่าจะรับ สถานะตัวจับเวลาเก่า ของเซสชันก่อนหน้ามาต่อ ทำให้คิวรีถูกยกเลิกทันที
เราก็ทำ failover เป็นประจำในสภาพแวดล้อมที่มีทราฟฟิกการเขียนสูงมาก แต่ใช้งาน AWS JDBC wrapper และรันได้เสถียรด้วยกระบวนการอัตโนมัติ
คนเราจ่ายเงินก็เพราะเชื่อว่า Aurora จะรักษาคุณสมบัติพื้นฐานที่ไม่ควรถูกละเมิดแบบนี้ได้ เลยรู้สึกแปลกใจที่ยังเกิดปัญหาเช่นนี้
จาก log และคำอธิบายของ AWS ดูเหมือนว่าสมมติฐานของผู้เขียนต้นฉบับจะไม่ถูกต้อง
ดูเหมือนว่า หลังจากการโปรโมตล้มเหลว มีกระบวนการเฝ้าระวังภายนอกตรวจพบความไม่สอดคล้องของสถานะคลัสเตอร์และสั่งบังคับปิด (kill -9) ส่วนข้อความที่เกี่ยวกับ storage subsystem เกิดขึ้นหลังจากนั้น
อยากถามเรื่อง การเปรียบเทียบประสิทธิภาพ ระหว่าง Aurora กับ RDS Postgres
ถ้าไม่ได้ต้องการ Multi AZ หรือ failover ที่เร็วมาก การตั้งค่า gp3 64k IOPS บน RDS จะให้ประสิทธิภาพดีกว่าไหม? Aurora ดูเหมือนจะ ช้าเรื่อง insert และมีต้นทุนสูง โดยเฉพาะ benchmark ต่าง ๆ ก็ไม่ค่อยบอกการตั้งค่า RDS ชัดเจน จนเชื่อถือได้ยาก
นอกจากนี้ยังไม่ต้อง provision IOPS เอง และได้ประมาณ 80k IOPS
โมเดลคิดค่าบริการ IO ก็มีสองแบบ โดย pay-per-IO เหมาะกับโหลดต่ำ ส่วน โหมดค่าบริการคงที่ เหมาะกับ workload ที่ใช้ IO มาก
และ Serverless แทบจะไม่คุ้มค่าเสมอ ยกเว้นกรณีที่มีช่วงพีคสั้น ๆ
โมเดลแบบ “Lego block model” ที่วิศวกร AWS ชอบพูดถึงเห็นภาพชัดมาก
พวกเขาออกแบบเลเยอร์สตอเรจให้แยกอิสระอย่างสมบูรณ์ ดังนั้นถึงบริการชั้นบนจะล้มเหลวก็ยัง รับประกันความสอดคล้องของข้อมูล ได้ ถือเป็นตัวอย่างที่ดีของงานวิศวกรรม AWS
มีการบอกว่า AWS แนะนำว่า ระหว่าง failover ให้หยุดเขียนข้อมูล เลยอยากรู้ว่าสามารถแชร์ หมายเลขเคส ที่เกี่ยวข้องได้ไหม
ดีใจที่รู้ว่าไม่ใช่มีแค่ผมคนเดียวที่เจอปัญหาแบบนี้
สถาปัตยกรรมแบบ แยก compute กับ storage ของ Aurora น่าสนใจมาก
Hyperscale ของ MSSQL ก็มีโครงสร้างคล้ายกัน และเป็นหนึ่งในไม่กี่บริการบน Azure ที่ผมคิดว่าน่าใช้งานจริง