1 คะแนน โดย GN⁺ 2025-11-16 | 1 ความคิดเห็น | แชร์ทาง WhatsApp
  • กรณีศึกษาที่มีการยืนยันเชิงทดลองถึง บั๊ก 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)

บทเรียนสำคัญ

  1. จำเป็นต้องเตรียมการกู้คืนสำหรับ การเปลี่ยนสถานะที่ไม่คาดคิดระหว่าง migration
  2. การมี observability คือหัวใจของการตรวจจับปัญหา
  3. ความสำคัญของ การออกแบบเพื่อลดผลกระทบระหว่างองค์ประกอบของระบบกระจาย
  4. ต้องตระหนักถึง ความแตกต่างระหว่างสภาพแวดล้อมทดสอบกับสภาพแวดล้อม production จริง

ไม่มีข้อมูลเพิ่มเติมในต้นฉบับ

1 ความคิดเห็น

 
GN⁺ 2025-11-16
ความเห็นจาก Hacker News
  • พออ่านบทความนี้แล้วดูเหมือนว่า ระหว่าง การสลับระบบฉุกเฉินด้วยตนเอง (failover) ถ้าแอปพลิเคชันยังพยายามรักษาทราฟฟิกการเขียนเหมือนปกติ ก็จะล้มเหลวเสมอ
    แต่ก็มีคำถามอยู่บ้าง ว่าทำไมผู้ใช้ Aurora คนอื่นถึงไม่ได้เจอปัญหานี้ตลอด, AWS ไม่น่าจะไม่รู้เรื่องนี้, และถ้ารู้อยู่แล้วทำไมถึงไม่จัดการเป็น ปัญหาเร่งด่วนระดับ P0
    เลยคิดว่าอาจมี เงื่อนไขละเอียดอ่อน บางอย่าง เช่น สถานะของทรานแซกชันที่กำลังดำเนินอยู่หรือ timeout เข้ามาเกี่ยวข้อง

    • จากประสบการณ์ที่เคยรับมือปัญหาคล้ายกันบน Azure หลายคนเจอปัญหานี้ แต่เพราะ รีสตาร์ตแล้วหาย ก็เลยปล่อยผ่านกันไปบ่อยมาก การหาสาเหตุที่แท้จริงทำได้ยาก และกระบวนการวิเคราะห์ร่วมกับเวนเดอร์ก็ทรมานเกินไปจนคนส่วนใหญ่ยอมแพ้
    • เราก็ยืนยันปัญหาเดียวกันนี้ได้ระหว่างทำงานร่วมกับ AWS เช่นกัน รูปแบบทราฟฟิกไม่ได้พิเศษอะไร และก็ไม่สามารถทำให้เกิดซ้ำได้ในรีเจียนอื่น นี่มีโอกาสสูงว่าจะเป็น ข้อบกพร่องเชิงกลไกของระบบ failover เองใน Aurora
    • เคยเจอบั๊กในชุด Python + MySQL ที่ SELECT ... FOR UPDATE ล้มเหลวแบบเงียบ ๆ และทรานแซกชัน เปลี่ยนไปเป็นโหมด autocommit ไม่มีใครสนใจเลยจนผมเหมือนบ่นอยู่คนเดียว แล้วหลายเดือนต่อมาก็มีคนอื่นติดต่อมาว่าเจอปัญหาเดียวกัน สุดท้ายมันก็ถูกแก้ แต่ตอนนั้นผมเลิกตามไปแล้ว
      ลิงก์ที่เกี่ยวข้อง: คำถามบน Stack Overflow
    • AWS แทบไม่เปิดเผยข้อมูลภายในเลย เพราะไม่บอกอะไรที่ลึกเกินกว่าระดับ API ทำให้รู้สึกว่าเรื่องแบบนี้มักถูกมองเป็น เคสหายาก แล้วก็ปล่อยผ่าน
    • ส่วนหนึ่งของปัญหาอาจมาจากวิธีที่แอปพลิเคชันตอบสนองต่อ failover ที่ถูกย้อนกลับ (reverted failover) แคชน่าจะพังจนยังพยายามเขียนไปยัง primary ที่ผิดอยู่ แม้ความล้มเหลวแบบนี้จะเกิดเป็นครั้งคราว แต่พอลองใหม่แล้วสำเร็จ ผู้ใช้ก็มักไม่แจ้ง AWS ทำให้ AWS อาจไม่รับรู้ปัญหานี้
  • เคยเห็น พฤติกรรมที่ไม่คาดคิด ใน Aurora PostgreSQL หลายครั้ง
    โดยเฉพาะระหว่าง Zero Downtime Patching (ZDP) ที่สถานะของเซสชันถูกคงไว้ผิด ๆ จนแม้แต่คิวรีง่าย ๆ ก็ถูกยกเลิกเร็วกว่าค่า statement_timeout มาก
    ข้อสันนิษฐานของผมคือ ตอนที่ไคลเอนต์เชื่อมต่อใหม่ Aurora น่าจะรับ สถานะตัวจับเวลาเก่า ของเซสชันก่อนหน้ามาต่อ ทำให้คิวรีถูกยกเลิกทันที

  • เราก็ทำ failover เป็นประจำในสภาพแวดล้อมที่มีทราฟฟิกการเขียนสูงมาก แต่ใช้งาน AWS JDBC wrapper และรันได้เสถียรด้วยกระบวนการอัตโนมัติ

    • ในทางปฏิบัติ เลเยอร์สตอเรจของ Aurora ช่วยป้องกันการละเมิด ACID ได้จริง กล่าวคือความถูกต้องของข้อมูลยังคงถูกรักษาไว้
  • คนเราจ่ายเงินก็เพราะเชื่อว่า Aurora จะรักษาคุณสมบัติพื้นฐานที่ไม่ควรถูกละเมิดแบบนี้ได้ เลยรู้สึกแปลกใจที่ยังเกิดปัญหาเช่นนี้

    • แต่ตัวเลเยอร์สตอเรจเองก็ยังทำงานถูกต้อง โดย ป้องกันการเขียนพร้อมกัน เอาไว้ได้
  • จาก log และคำอธิบายของ AWS ดูเหมือนว่าสมมติฐานของผู้เขียนต้นฉบับจะไม่ถูกต้อง
    ดูเหมือนว่า หลังจากการโปรโมตล้มเหลว มีกระบวนการเฝ้าระวังภายนอกตรวจพบความไม่สอดคล้องของสถานะคลัสเตอร์และสั่งบังคับปิด (kill -9) ส่วนข้อความที่เกี่ยวกับ storage subsystem เกิดขึ้นหลังจากนั้น

  • อยากถามเรื่อง การเปรียบเทียบประสิทธิภาพ ระหว่าง Aurora กับ RDS Postgres
    ถ้าไม่ได้ต้องการ Multi AZ หรือ failover ที่เร็วมาก การตั้งค่า gp3 64k IOPS บน RDS จะให้ประสิทธิภาพดีกว่าไหม? Aurora ดูเหมือนจะ ช้าเรื่อง insert และมีต้นทุนสูง โดยเฉพาะ benchmark ต่าง ๆ ก็ไม่ค่อยบอกการตั้งค่า RDS ชัดเจน จนเชื่อถือได้ยาก

    • เราได้ทั้งประสิทธิภาพที่ดีกว่าและต้นทุนที่ต่ำกว่าบน Aurora PG 14 ด้วยการตั้งค่า 1 writer + 1~2 reader Aurora ได้เปรียบตรงที่คิดค่าพื้นที่เก็บข้อมูลเป็น ระดับคลัสเตอร์ ไม่ใช่ต่ออินสแตนซ์
      นอกจากนี้ยังไม่ต้อง provision IOPS เอง และได้ประมาณ 80k IOPS
      โมเดลคิดค่าบริการ IO ก็มีสองแบบ โดย pay-per-IO เหมาะกับโหลดต่ำ ส่วน โหมดค่าบริการคงที่ เหมาะกับ workload ที่ใช้ IO มาก
      และ Serverless แทบจะไม่คุ้มค่าเสมอ ยกเว้นกรณีที่มีช่วงพีคสั้น ๆ
    • ทีมของเราเคยเจอ ค่าใช้จ่าย I/O พุ่งสูง บน Aurora Postgres RDS แค่ fuzzy query ไม่กี่ตัวก็ทำให้บิลเกิน $3,000 ต่อเดือน ทั้งที่เดิมควรจะต่ำกว่า $100
    • ทั้ง ต้นทุน, ประสิทธิภาพ, และ latency ของ Aurora น่าผิดหวัง จนสุดท้ายย้ายไป PostgreSQL แบบ on-premises
    • ถ้าอิงจาก Aurora MySQL การจะทำ IOPS ระดับเดียวกันบน RDS ต้องใช้ ต้นทุนแพงกว่ามาก
    • Aurora ไม่ได้ใช้ EBS และ ไม่สามารถเลือกประเภทสตอเรจหรือ latency ได้ เลือกได้เพียงรูปแบบการคิดค่าบริการ IO เท่านั้น
  • โมเดลแบบ “Lego block model” ที่วิศวกร AWS ชอบพูดถึงเห็นภาพชัดมาก
    พวกเขาออกแบบเลเยอร์สตอเรจให้แยกอิสระอย่างสมบูรณ์ ดังนั้นถึงบริการชั้นบนจะล้มเหลวก็ยัง รับประกันความสอดคล้องของข้อมูล ได้ ถือเป็นตัวอย่างที่ดีของงานวิศวกรรม AWS

  • มีการบอกว่า AWS แนะนำว่า ระหว่าง failover ให้หยุดเขียนข้อมูล เลยอยากรู้ว่าสามารถแชร์ หมายเลขเคส ที่เกี่ยวข้องได้ไหม

    • เราเองก็ใช้ Aurora MySQL อยู่ จึงอยากยืนยันว่าคำแนะนำนี้ ใช้กับ MySQL ด้วยหรือไม่
  • ดีใจที่รู้ว่าไม่ใช่มีแค่ผมคนเดียวที่เจอปัญหาแบบนี้

    • ตอนแรก AWS Support แย้งว่าเป็นเพราะ replication lag แต่ดันใช้ metric เก่าตั้ง 24 ชั่วโมงมาสรุป ผมอยากรู้มากว่าเกิดความล้มเหลวแบบไหนกันแน่ และทำไมถึงทำให้เกิดซ้ำไม่ได้ในรีเจียนอื่น
  • สถาปัตยกรรมแบบ แยก compute กับ storage ของ Aurora น่าสนใจมาก
    Hyperscale ของ MSSQL ก็มีโครงสร้างคล้ายกัน และเป็นหนึ่งในไม่กี่บริการบน Azure ที่ผมคิดว่าน่าใช้งานจริง