3 คะแนน โดย GN⁺ 2023-12-20 | 1 ความคิดเห็น | แชร์ทาง WhatsApp
  • Repeatable Read ซึ่งเป็นระดับ isolation เริ่มต้นของ MySQL 8.0.34 แสดงการละเมิดความสอดคล้องของทรานแซกชันที่ไม่ตรงกับความคาดหวังของ ANSI SQL และ PL-2.99 ของ Adya แม้บนโหนดเดี่ยวที่ทำงานปกติ
  • ตรวจสอบ MySQL 8.0.34, MariaDB 10.11.3, คลัสเตอร์ replication ผ่าน binlog และ AWS RDS MySQL Multi-AZ DB Cluster ร่วมกัน โดยผสาน list-append checker ของ Elle, targeted workload และ LazyFS
  • เช่นเดียวกับผล Hermitage ของ Kleppmann ในปี 2014 พบซ้ำทั้ง G2-item, G-single และ lost update รวมถึงพบการละเมิด internal consistency, non-repeatable read และการละเมิด Monotonic Atomic View
  • Read Uncommitted, Read Committed และ Serializable ของ MySQL เดี่ยวดูเหมือนจะสอดคล้องกับ PL-1, PL-2 และ PL-3 ตามลำดับ แต่คลัสเตอร์ AWS RDS MySQL แสดง G2-item และ G-single แม้ใน Serializable
  • หากต้องการ Repeatable Read ระดับ ANSI หรือ PL-2.99 การพึ่งพา MySQL Repeatable Read เพียงอย่างเดียวนั้นทำได้ยาก และจำเป็นต้องใช้ Serializable หรือการล็อกแบบชัดเจน เช่น SELECT... FOR UPDATE

เป้าหมายและขอบเขตการประเมิน

  • MySQL เป็นฐานข้อมูลเชิงสัมพันธ์ที่ใช้กันอย่างแพร่หลาย และในบทวิเคราะห์นี้ “MySQL” หมายถึง MySQL ที่ใช้ InnoDB ซึ่งเป็น storage engine เริ่มต้น
  • จุดเน้นคือ MySQL แบบเซิร์ฟเวอร์เดี่ยว แต่ครอบคลุมคลัสเตอร์ที่มี primary สำหรับเขียนแบบเดี่ยวและ secondary แบบอ่านอย่างเดียวซึ่งใช้ binlog replication ด้วย
  • เป้าหมายการทดสอบมีดังนี้
    • MySQL 8.0.34
    • MariaDB 10.11.3
    • Debian Bookworm
    • โปรไฟล์ “Multi-AZ DB Cluster” ของ AWS RDS Cluster
  • งานนี้ดำเนินการอย่างอิสระโดยไม่มีค่าตอบแทน และเป็นไปตาม Jepsen ethics policy

ระดับ SQL isolation และเกณฑ์ของ Repeatable Read

  • ANSI SQL นิยาม Read Uncommitted, Read Committed, Repeatable Read และ Serializable ตามความเป็นไปได้ของ P1 dirty read, P2 non-repeatable read และ P3 phantom
  • ในปี 1995 Berenson และคณะวิจารณ์ความคลุมเครือและความไม่สมบูรณ์ของนิยาม ANSI ใน A Critique of ANSI SQL Isolation Levels
    • P1, P2, P3 เปิดช่องให้ตีความได้
    • ปรากฏการณ์สำคัญอย่าง P0 dirty write หายไป
    • P3 ห้ามเฉพาะ insert ที่มีผลต่อ predicate แต่ไม่ครอบคลุม update หรือ delete
  • งานวิจัยปี 1999 ของ Atul Adya นิยามระดับ isolation ที่ไม่ขึ้นกับ implementation โดยอิงจาก dependency graph ระหว่างทรานแซกชัน
    • PL-1 ห้าม G0 write cycle
    • PL-2 ห้าม G0 และ G1
    • PL-2.99 ห้าม G0, G1, G2-item และสอดคล้องกับ Repeatable Read
    • PL-3 ห้าม G0, G1, G2 และสอดคล้องกับ Serializable
  • โดยทั่วไป Jepsen ใช้รูปแบบของ Adya ในการตัดสินบันทึกทรานแซกชันและ anomaly

ความขัดแย้งระหว่างเอกสาร MySQL กับ Repeatable Read

  • เอกสาร MySQL อธิบายว่า InnoDB ให้ระดับ isolation ทั้งสี่ของมาตรฐาน SQL:1992
  • Repeatable Read ซึ่งเป็นระดับ isolation เริ่มต้น อธิบายว่า consistent read ภายในทรานแซกชันเดียวกันอ่าน snapshot ที่ตั้งขึ้นในการอ่านครั้งแรก
  • เอกสาร consistent read ก็อธิบายเช่นกันว่ามองฐานข้อมูลตาม timepoint ณ ตอนอ่านครั้งแรก
  • แต่หมายเหตุในเอกสารเดียวกันระบุว่า snapshot ใช้กับ SELECT แต่ไม่จำเป็นต้องใช้กับคำสั่ง DML และ DELETE หรือ UPDATE อาจแตะ row ที่ทรานแซกชันอื่น commit แล้วได้
  • หมายเหตุนี้ขัดแย้งกับข้อเท็จจริงที่ ANSI SQL และคู่มืออ้างอิงของ MySQL ถือว่า SELECT เป็น DML เช่นกัน และก่อให้เกิดความสับสนว่าใน Repeatable Read การเขียนอาจส่งผลต่อ row ที่อ่านไม่เห็นได้

การออกแบบการทดสอบ

  • ชุดทดสอบสำหรับ MySQL เขียนบนพื้นฐาน Jepsen testing library 0.3.4
  • ไคลเอนต์ใช้ JDBC adapter mysql-connector-j
  • การทดสอบรวม fault injection เช่น process pause, crash, network partition และการสูญหายของการเขียนลงดิสก์ที่ยังไม่ได้ fsync
  • อย่างไรก็ตาม การค้นพบเกือบทั้งหมดในบทวิเคราะห์นี้เกิดขึ้นใน โหนด MySQL เดี่ยวที่อยู่ในสภาวะปกติ
  • Elle list-append workload

    • workload หลักใช้ list-append checker ของ Elle
    • Elle อนุมาน dependency แบบ write-write, write-read และ read-write ระหว่างทรานแซกชัน และพิสูจน์การละเมิดระดับ isolation เฉพาะด้วย cycle ใน dependency graph
    • list-append workload ทำทรานแซกชันแบบสุ่มที่ประกอบด้วย read และ append กับ list หลายรายการที่ระบุด้วย primary key
    • list ถูกเข้ารหัสเป็นฟิลด์ text ที่เก็บค่าคั่นด้วยจุลภาค และ append ทำผ่าน SQL CONCAT
    • การปรับปรุงล่าสุดทำให้ Elle ตรวจจับสิ่งต่อไปนี้ได้ดีขึ้น
      • การอนุมาน ww/rw dependency สำหรับ append element ที่ไม่ได้ถูกอ่าน
      • การตรวจจับ P4 lost update อย่างชัดเจน
      • การค้นหา cycle ที่ซับซ้อนซึ่งรวม real-time edge และ process edge
  • Targeted workload

    • Non-repeatable read workload มุ่งไปที่ row หนึ่งในตาราง people
    • ทรานแซกชันกลุ่มหนึ่ง update เฉพาะ name ส่วนอีกกลุ่มอ่าน name แล้ว update gender จากนั้นอ่าน name อีกครั้ง
    • หาก name เปลี่ยนไประหว่างการอ่านสองครั้ง ถือเป็นการละเมิด Repeatable Read
    • Monotonic Atomic View workload ใช้ value ของสอง row
    • writer เพิ่มค่า value ของ row 0 แล้วเพิ่ม row 1
    • reader อ่าน row 0, update noop ของ row 1 แล้วอ่าน row 1 และ row 0
    • หากเห็นผลบางส่วนของทรานแซกชันหนึ่งแล้ว ก็ควรเห็นผลทั้งหมด
  • LazyFS

    • LazyFS เป็นระบบไฟล์ FUSE ที่จำลองการสูญหายของการเขียนที่ยังไม่ได้ fsync
    • ทดสอบโดย kill process ของ MySQL, ทิ้งแคช LazyFS แล้วรีสตาร์ต MySQL
    • รายงานนี้เป็นรายงาน Jepsen สาธารณะฉบับแรกที่รวม LazyFS

Anomaly ที่พบใน MySQL Repeatable Read

  • G2-item

    • PL-2.99 Repeatable Read ของ Adya ห้าม G2-item ซึ่งเป็น cycle ของ dependency แบบ write-write, write-read, read-write ที่ไม่รวม predicate
    • MySQL Repeatable Read อนุญาต G2-item ซ้ำ ๆ แม้บนโหนดเดี่ยวที่ทำงานปกติ
    • พฤติกรรมที่ Kleppmann รายงานใน Hermitage ปี 2014 ยังคงเกิดขึ้นใน MySQL 8.0.34
    • ตัวอย่างการทดสอบแสดง 214 cycle ในเวลา 40 วินาที
    • พฤติกรรมนี้ถูกห้ามใน PL-2.99 Repeatable Read แต่เนื่องจากนิยาม P2 ของ ANSI SQL ครอบคลุมเฉพาะกรณีอ่าน row เดิมสองครั้ง จึงยังเหลือช่องให้ตีความตามนิยาม ANSI
  • G-single และ read skew

    • MySQL Repeatable Read แสดง G-single ด้วย
    • G-single เป็น cycle ที่ประกอบด้วย edge แบบ write-write, write-read, read-write แต่ read-write edge ไม่อยู่ติดกัน
    • read skew ที่ Kleppmann รายงานในปี 2014 ได้รับการยืนยันใน MySQL 8.0.34
    • ใน append test 60 วินาที พบ G-single 244 กรณีและ G2-item 305 กรณี ที่ประมาณ 140 ทรานแซกชันต่อวินาที
    • append test ไม่ใช้การดำเนินการ predicate ดังนั้นทั้งหมดจึงถูกจัดเป็นการละเมิด Repeatable Read
  • Lost update

    • P4 lost update เป็นกรณีพิเศษของ G-single ที่ทรานแซกชันสองรายการอ่าน version เดียวกันของ key เดียวกันแล้วทั้งคู่ update
    • Snapshot Isolation และ PL-2.99 Repeatable Read ห้าม lost update
    • MySQL Repeatable Read อนุญาต lost update ซ้ำ ๆ แม้บนโหนดเดี่ยวที่ทำงานปกติ
    • ในการทดสอบหนึ่ง จากทรานแซกชันสำเร็จ 9,048 รายการ checker ใหม่พบทรานแซกชัน 446 รายการที่เกี่ยวข้องกับ lost update 198 กรณี
    • ในจำนวนนี้ มีเพียง 47 กรณีที่ปรากฏเป็น cycle
    • รูปแบบที่อ่านค่าก่อนแล้วค่อยเขียนไม่ปลอดภัยใน MySQL Repeatable Read
    • ใน pattern มาตรฐานของ ORM ที่อ่าน object, แก้ไขในหน่วยความจำ แล้วบันทึกกลับ การเปลี่ยนแปลงที่ commit แล้วอาจหายไปอย่างเงียบ ๆ
    • ผู้ใช้ต้องใช้การล็อกแบบชัดเจนด้วยตัวเอง
  • Non-repeatable read และการละเมิด internal consistency

    • MySQL Repeatable Read แสดง การละเมิด internal consistency แม้บนโหนดเดี่ยวที่ทำงานปกติ
    • ใน test run เดียวกัน จากทรานแซกชันที่ commit 9,048 รายการ มี 126 รายการที่แสดงข้อผิดพลาด internal consistency
    • ในตัวอย่างหนึ่ง เมื่อทรานแซกชันอ่าน key เป็น nil แล้ว append ค่าหนึ่ง จากนั้นอ่าน key เดิมอีกครั้ง กลับสังเกตเห็นสถานะที่มีค่าอื่นเพิ่มเข้ามาอีกสามค่า
    • ในอีกตัวอย่างหนึ่ง เมื่ออ่าน key 1096 เป็น [1 2 3] แล้ว append 7 จากนั้นอ่านอีกครั้ง กลับเห็น [1 2 3 4 5 6 7]
    • ใน targeted workload ภายในทรานแซกชัน Repeatable Read หนึ่ง อ่าน name เป็น "pebble", update gender เป็น "femme" แล้วอ่าน name เดิมอีกครั้งได้ "moss"
    • พฤติกรรมเช่นนี้ขัดกับนิยาม non-repeatable read ของ ANSI SQL และคำอธิบายในเอกสาร MySQL ที่ว่า “snapshot ที่ตั้งขึ้นในการอ่านครั้งแรก”
  • การละเมิด Monotonic Atomic View

    • Monotonic Atomic View เป็นคุณสมบัติที่ทรานแซกชันซึ่งเห็นผลบางอย่างของทรานแซกชันหนึ่ง ต้องเห็นผลทั้งหมดของทรานแซกชันนั้น
    • MySQL Repeatable Read ละเมิดคุณสมบัตินี้ซ้ำ ๆ แม้บนโหนดเดี่ยวปกติ
    • ใน workload writer เพิ่มค่า row 0 แล้วเพิ่มค่า row 1
    • reader เห็นค่าก่อนหน้า 0 ที่ row 0 จากนั้นเห็นค่าที่ writer เพิ่มเป็น 1 ที่ row 1 แล้วกลับมาเห็น 0 ที่ row 0 เหมือนเดิม
    • นี่คือ non-monotonic read ที่เห็นผลของ row 1 แต่ไม่เห็นผลของ row 0 และไม่สอดคล้องกับพฤติกรรม snapshot ทั่วไป

Anomaly ใน AWS RDS MySQL Serializable

  • คลัสเตอร์ AWS RDS MySQL ละเมิด Serializability ซ้ำ ๆ แม้อยู่ในระดับ isolation “Serializable”
  • ในคลัสเตอร์ RDS MySQL ที่ใช้โปรไฟล์ production เริ่มต้นที่แนะนำ append test แสดง anomaly แบบ G2-item และ G-single
  • anomaly ที่สังเกตพบมีรูปแบบที่ทรานแซกชันหนึ่งพลาด dependency ก่อนหน้าของทรานแซกชันอีกตัวที่เห็นผลของทรานแซกชันหนึ่ง
  • anomaly นี้ถูกจัดเป็นทั้ง G-single และ G2-item และละเมิดทั้ง Snapshot Isolation, Repeatable Read และ Serializability
  • การตั้งค่าที่เกี่ยวกับ replica_preserve_commit_order ยังคงเป็นปัจจัยที่น่าสงสัย
    • MySQL 8.0.27 ขึ้นไปตั้งค่าเริ่มต้นเป็น replica_preserve_commit_order=ON
    • พารามิเตอร์เริ่มต้นของ RDS ยังเลือกการตั้งค่าที่สอดคล้องกับ replica_preserve_commit_order=OFF
    • ใน RDS parameter group ใช้ชื่อเดิมของการตั้งค่านี้คือ slave_preserve_commit_order
    • เมื่อนำการตั้งค่านี้ไปใช้กับคลัสเตอร์ทดสอบในเครื่อง พบ G-single และ G2-item ที่คล้ายกัน

ส่วนที่ดูเหมือนทำงานปกติและผล LazyFS

  • Read Uncommitted, Read Committed และ Serializable ของ MySQL 8.0.34 ดูเหมือนจะเป็นไปตาม PL-1, PL-2 และ PL-3 ตามลำดับ
  • ผลนี้สังเกตพบทั้งในโหนดเดี่ยวและคลัสเตอร์ replica แบบ read-only ขนาดเล็กที่ใช้ binlog replication
  • ผลดังกล่าวยังคงอยู่แม้มี process pause, crash และ network partition
  • LazyFS fault injection ไม่พบปัญหาในการตั้งค่าเริ่มต้นของ MySQL
  • ด้วยค่าเริ่มต้น innodb_flush_log_at_trx_commit=1 ไม่พบการสูญหายของทรานแซกชันที่ commit แล้ว แม้หลัง process crash และการสูญหายของข้อมูลที่ยังไม่ได้ fsync
  • เมื่อเปลี่ยนเป็น innodb_flush_log_at_trx_commit=0 MySQL จะ fsync เพียงทุกไม่กี่วินาที และพบการสูญหายของข้อมูล

ลักษณะที่แท้จริงของ MySQL Repeatable Read

  • MySQL Repeatable Read ไม่เป็นไปตาม PL-2.99 Repeatable Read
    • แสดง G2-item และ write skew
  • ไม่เป็นไปตาม Snapshot Isolation ด้วย
    • แสดง G-single, read skew และ lost update
  • ไม่เป็นไปตาม cursor stability ด้วย
    • เกิด lost update
  • Read Atomic, Causal Consistency, Consistent View, Prefix Consistency และ Parallel Snapshot Isolation ก็ถูกตัดออก
    • พบการละเมิด internal consistency
  • MySQL Repeatable Read ดูเหมือนจะแข็งแรงกว่า Read Committed อยู่บ้าง
    • ไม่พบ G0 dirty write, G1a aborted read, G1b intermediate read หรือ G1c cyclic information flow
    • repeatability ของการอ่านบางส่วนให้คุณสมบัติที่แข็งแรงกว่า Read Committed
  • อย่างไรก็ตาม ยังไม่ชัดเจนว่า MySQL Repeatable Read เป็น consistency model ใดกันแน่ และไม่มีนิยามคุณสมบัติอย่างเป็นทางการ

ความไม่ตรงกันระหว่างเอกสารกับความเข้าใจของชุมชน

  • ในชุมชน MySQL พฤติกรรมของ Repeatable Read ยังไม่ได้ถูกเข้าใจอย่างเพียงพอ
  • บทความหลายชิ้นเชื่อว่า MySQL Repeatable Read ป้องกัน lost update ได้ แต่บทความอื่นรายงานว่าป้องกันไม่ได้และแนะนำให้ใช้การล็อกแบบชัดเจน
  • แหล่งข้อมูลบนอินเทอร์เน็ตหลายแห่งบอกว่า MySQL Repeatable Read นั้น repeatable จริง แต่การทดสอบของ Jepsen แสดงกรณีที่ไม่เป็นเช่นนั้น
  • เอกสารของ MySQL และ MariaDB ก็อธิบายว่า Repeatable Read อ่าน snapshot เดียวกันภายในทรานแซกชันเดียวกัน
  • ประโยคหนึ่งในเอกสาร consistent read ของ MySQL บอกเป็นนัยถึงพฤติกรรมที่ขัดแย้งกับคำอธิบายนี้ แต่เนื้อหาดังกล่าวถูกฝังอยู่ในเอกสาร

ข้อเสนอแนะ

  • หาก MySQL ยังคงพฤติกรรมปัจจุบันไว้ ควรจัดทำเอกสารให้ชัดเจนว่า “Repeatable Read” ให้ consistency model แบบใดกันแน่
  • ทางเลือกอีกทางคือถือว่าพฤติกรรมปัจจุบันเป็นบั๊กและแก้ไข
  • Jepsen ระบุว่ายินดีต้อนรับหาก MySQL และ vendor อื่นให้คำมั่นว่าจะรองรับ PL-2.99 Repeatable Read
  • ผู้ใช้ที่ต้องการ PL-2.99 หรือ ANSI Repeatable Read ควรระวัง MySQL Repeatable Read
  • ทางเลือกในงานจริงมีดังนี้
    • ใช้ระดับ isolation Serializable ของ MySQL
    • เสริมความแข็งแรงของการอ่านด้วยเทคนิคการล็อก เช่น SELECT ... FOR UPDATE ใน READ COMMITTED

ข้อเสนอแนะสำหรับผู้ใช้ RDS

  • คลัสเตอร์ AWS RDS MySQL แสดง read skew และ G2-item ใน “Serializable”
  • ผู้ใช้ที่พึ่งพา Serializability ควรตั้งค่า slave_preserve_commit_order เป็น ON ใน RDS parameter group
  • มีข้อเสนอว่า AWS ควรเปลี่ยนค่าเริ่มต้น หรืออธิบายการละเมิด Serializability ที่อนุญาตไว้อย่างชัดเจนในเอกสาร known limitations ของ RDS MySQL

งานในอนาคตและคำขอด้านมาตรฐาน

  • MySQL binlog replication ดูเปราะบาง
    • ในการทดสอบ Jepsen แบบ local พบหลายสถานการณ์ที่ replication หยุดทำงาน
    • AWS RDS MySQL replication อาจพังได้โดยสมบูรณ์หลังทดสอบเพียงไม่กี่นาที และสถานการณ์ที่ CREATE DATABASE ซึ่งสำเร็จบน primary ไม่ปรากฏบน secondary ไม่ฟื้นตัวเป็นเวลา 1 ชั่วโมง
  • ไม่ได้สำรวจการโปรโมต secondary เป็น primary หรือ replication topology แบบ ring, star
  • กำลังศึกษาการทดสอบ predicate ที่ทั่วไปกว่านี้เพื่อประเมิน predicate safety
  • นิยามระดับ isolation ของ ANSI SQL ไม่ได้เปลี่ยนไป แม้ผ่านไป 28 ปีหลังจาก Berenson และคณะชี้ให้เห็นความคลุมเครือและความไม่สมบูรณ์ และผ่านการแก้ไข ANSI·ISO มาแล้ว 7 ครั้ง
  • จำเป็นต้องมีนิยามระดับ isolation ที่เป็นทางการและพกพาได้มากขึ้น เพื่อให้ ISO/IEC 9075-2 สามารถครอบคลุมปรากฏการณ์อย่าง internal anomaly, lost update และ dirty write ได้อย่างชัดเจน

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

 
GN⁺ 2023-12-20
ความคิดเห็นบน Hacker News
  • ผมมองมานานแล้วว่า repeatable read เป็นไอเดียที่ไม่ดี แม้จะอิมพลีเมนต์ได้สมบูรณ์แบบก็ตาม
    ต่อให้ทำงานถูกต้องภายในฐานข้อมูล แต่สำหรับคิวรีที่ซับซ้อน การให้เหตุผลก็ยากเกินไป
    ผมคิดว่าระดับการแยกธุรกรรมที่สมเหตุสมผลมีแค่ read committed กับ serializable เท่านั้น
    ควรไปให้สุดทางด้วย serializable เพื่อไม่ให้มีเรื่องประหลาดใจ หรือไม่ก็ใช้ read committed ที่ทำให้ชัดเจนว่า ถ้าต้องการมุมมองที่สอดคล้องกันภายในทรานแซกชัน ก็ต้องล็อกแถวก่อนอ่าน
    read committed คล้ายกับโค้ดมัลติเธรดทั่วไปและการจัดการหน่วยความจำ ทำให้วิศวกรมีสัญชาตญาณกับมันได้ง่าย ส่วน serializable ก็เข้มงวดมากจนยากที่จะทำพลาดแบบไม่คาดคิด
    ระหว่างกลางนั้นเป็นเขตไร้คนอยู่ และสิ่งที่สอดคล้องน้อยกว่า read committed ก็แทบเรียกไม่ได้ว่าเป็นฐานข้อมูลที่ดีอีกต่อไป

    • ผมไม่คิดว่าคนจะให้เหตุผลกับ read committed ได้ดีนัก
      ยิ่งแอปพลิเคชันใหญ่ขึ้น ก็ยิ่งยากมากที่จะเข้าใจทุกกรณีว่าล็อกถูกจับตรงไหนและข้อมูลถูกเข้าถึงอย่างไร
      สำหรับทรานแซกชันอ่าน/เขียน serializable คือโมเดลการแยกธุรกรรมเดียวที่ยังมีเหตุผล และสำหรับทรานแซกชันอ่านอย่างเดียว snapshot isolation ซึ่งจัดการกับสแนปช็อตของฐานข้อมูล ณ จุดเวลาหนึ่ง เป็นโมเดลที่ดี
      โหมดที่ Spanner ให้มาจริง ๆ ก็มีแค่สองแบบนี้: https://cloud.google.com/spanner/docs/transactions
    • read uncommitted ใช้กับสถิติแบบรวมได้พอไหว แต่ถ้าระดับนั้นแล้ว ส่งข้อมูลไหลไป ClickHouse จะดีกว่า
    • คิวรีสแนปช็อต แบบอ่านอย่างเดียวมีประโยชน์มากในระบบจริง
    • ถ้า repeatable read ทำงานได้ถูกต้องจริง ๆ ก็คงไม่จำเป็นต้องล็อกแถว
  • ที่ FOSSDEM 2024 มีทอล์กเปรียบเทียบ ระดับการแยกธุรกรรมและ MVCC ของฐานข้อมูล SQL
    ครอบคลุม Oracle, MySQL, SQL Server, PostgreSQL, YugabyteDB
    https://fosdem.org/2024/schedule/event/fosdem-2024-3600-isol...

    • ผู้บรรยายเป็น developer advocate ที่ทำงานที่ YugabyteDB เลยสงสัยว่าสิ่งนี้เชื่อมโยงกับงานของ Kyle อย่างไร
  • สงสัยว่า append(a) ถูกแมปเป็น ปฏิบัติการ SQL จริง ๆ บนตารางที่กำหนดอย่างไร
    ใช้ฟิลด์ TEXT เหมือนเป็นลิสต์หรือเปล่า?
    ในโหมด repeatable read ของ MySQL ผมเคยเจอว่า SELECT เดียวที่เลือกแถวเดียวส่งคืนผลลัพธ์ที่เป็นไปไม่ได้
    เป็นรูปแบบ SELECT min(value), max(value) FROM table WHERE id = 1; และ id เป็น primary key แต่ min กับ max ออกมาเป็นคนละค่า

    • ใช่ อยู่ที่ https://jepsen.io/analyses/mysql-8.0.34#list-append และมีลิงก์โค้ดด้วย: https://github.com/jepsen-io/mysql/blob/4c239cb5c66a7f1a55fa...
      สำหรับข้อมูล นี่ไม่ใช่ปัญหาที่เฉพาะเจาะจงกับ CONCAT เท่านั้น เหตุผลที่ใช้ CONCAT คือทำให้สามารถให้เหตุผลเกี่ยวกับความผิดปกติได้ใน เวลาเชิงเส้น ไม่ใช่เวลาเชิงเอ็กซ์โปเนนเชียล
      พฤติกรรมชนิดเดียวกันนี้เกิดขึ้นได้กับรีจิสเตอร์อ่าน/เขียนธรรมดาเช่นกัน
  • ชอบที่บทความพูดถึง AWS RDS ด้วย แต่สงสัยว่ามีโฟกัสไปที่ AWS Aurora MySQL ด้วยหรือเปล่า
    สำหรับคนที่ไม่รู้ AWS สร้างแพลตฟอร์มฐานข้อมูลที่เข้ากันได้ในระดับโปรโตคอล โดยทำตัวเหมือนเป็น MySQL หรือ PostgreSQL
    น่าจะน่าสนใจถ้าดูว่า Aurora MySQL มี “คุณลักษณะ” แบบเดียวกับ RDS หรือ MariaDB ไหม

    • Aurora เป็น เอนจิน DB ที่ต่างออกไปโดยสิ้นเชิง ดังนั้นปัญหา concurrency ก็ต่างกัน น่าจะไม่ได้ครอบคลุมในนี้
      ถึงอย่างนั้นก็เป็นเป้าหมายที่น่าสนใจมาก และเพราะ Aurora เป็นฐานข้อมูลที่ใหม่กว่ามาก ผมมีสัญชาตญาณว่าน่าจะมีปัญหาละเอียดอ่อนที่ยังไม่ถูกค้นพบมากกว่า MySQL รุ่นเก่า
    • ผมใช้ MySQL Aurora ค่อนข้างเยอะ และสำหรับกรณีใช้งานของเรา แม้ปริมาณการใช้จะสูงมาก แต่รูปแบบคิวรีเรียบง่าย เลยไม่ค่อยเห็นความต่างใหญ่ ๆ
      แต่ก็มีเรื่องน่ารำคาญใหญ่อยู่เรื่องหนึ่ง
      วิศวกรของ Plaid เขียนบทความสรุปความแตกต่างไว้ดี: https://plaid.com/blog/exploring-performance-differences-bet...
      สำหรับผม ความต่างที่ใหญ่ที่สุดคือคลัสเตอร์ Aurora ใช้ สตอเรจแบบแชร์ ทำให้โมเดลการแยกธุรกรรมต่างออกไปเล็กน้อย
      read committed ทำได้ก็ต่อเมื่อต้องตั้งค่าพารามิเตอร์ทั้งคลัสเตอร์ และ read uncommitted ในมุมผมน่าจะทำไม่ได้
  • เป็นบทความที่น่าสนใจมาก
    แสดงให้เห็นได้ดีว่า “ระบบที่ใช้งานได้จริง” จำนวนมากสามารถถูกสร้างขึ้นบนรากฐานที่มี ความผิดปกติด้านความสอดคล้อง มากมายขนาดนั้นได้อย่างไร

    • ระบบส่วนใหญ่พังในทางปฏิบัติ และเดินต่อได้ด้วยการอ้อมปัญหาผ่านการปรับแก้โดยมนุษย์
  • ส่วนที่บอกว่าแค่ไปยุ่งด้วย 5 นาที การจำลองข้อมูลของ RDS ก็หยุดทำงาน และไม่มีการแจ้งเตือน health check ที่ล้มเหลวด้วยนั้นน่ากังวลอยู่บ้าง

    • รายละเอียดสำคัญมาก และแทบจะแก้ปัญหาจาก screencast อย่างเดียวไม่ได้ แต่จากประสบการณ์ของผม AWS โดยทั่วไปให้ CloudWatch Metrics มาค่อนข้างเหลือเฟือ
      เพียงแต่มีแนวโน้มผลักภาระให้ผู้ใช้ต้องไล่ดู metric กว่า 150 รายการและอ่านเอกสารเพื่อหาว่าอะไรสำคัญ
      อีกทั้งใน <https://docs.aws.amazon.com/AmazonRDS/latest/UserGuide/USER_...> บอกว่ามีเซลล์ตารางในคอนโซลที่แสดงสถานะการจำลองข้อมูล แต่ในคอนโซลผู้ใช้มักต้องเปิดการแสดงผลคอลัมน์นั้นเอง ซึ่งไม่ดี
      AWS พึ่งพาสิ่งที่เรียกว่า “โมเดลความรับผิดชอบร่วมกัน” อยู่มากทีเดียว
    • ผมยืนยันได้ว่าไม่ควรเชื่อ AWS health check ใด ๆ ให้เป็นการแจ้งเตือนลำดับแรกเมื่อระบบล่ม
      ต้องทำเองทั้งหมดจากภายในโฮสต์หรือคอนเทนเนอร์
      ฝ่ายสนับสนุนของ AWS/Rackspace จะพูดแค่ว่า “สิ่งที่ทำงานอยู่ภายในบริการของ AWS เราไม่ได้จัดการ ดังนั้นเป็นปัญหาของลูกค้า”
  • ชอบตรงที่ในปี 2022 Jepsen ว่าจ้าง INESC TEC แห่งมหาวิทยาลัย Porto ให้พัฒนา LazyFS
    เป็นระบบไฟล์ FUSE ที่จำลองการสูญหายของการเขียนที่ยังไม่ได้ fsync นี่เป็นตัวอย่างที่ยอดเยี่ยมของการผลักดันระดับเทคโนโลยีให้ก้าวไปข้างหน้า

  • SELECT ... FOR UPDATE ดูเหมือนจะเป็นคำตอบของปัญหาเหล่านี้
    ถ้าล็อกแถวที่จะอัปเดต จู่ ๆ ทุกอย่างก็ทำงานตามที่โฆษณาไว้ไม่ใช่หรือ?

    • โดยทั่วไป การดำเนินการที่ล็อกแถวมักจะ “ตรึง” ให้ค่ามีอยู่จริง โดยไม่เกี่ยวกับ repeatable read
      ถ้าต้องอัปเดตเรคอร์ดหนึ่งตามข้อมูลของอีกเรคอร์ดหนึ่ง ก็ต้องทำ locking read กับเรคอร์ดนั้น และอาจรวมถึงเรคอร์ดที่จะอัปเดตด้วย
      ถ้าใช้ SQL query เดียวเพื่ออัปเดตเรคอร์ดโดยอิงจากอีกเรคอร์ด MySQL ก็จะล็อกทั้งสองอยู่ดี
      ถ้าต้องอัปเดตบางอย่างโดยอิงจากหลายเป้าหมาย จากประสบการณ์ของผม deadlock เกิดขึ้นได้ง่ายมาก
      ทางที่ดีกว่าคือล็อกอะไรสักอย่าง เช่น เรคอร์ดสำหรับใช้ล็อก แล้วค่อยทำ repeatable read กับข้อมูลที่ต้องการและอัปเดต
      จุดเวลาอ้างอิงของ repeatable read จะยังไม่ถูกกำหนดจนกว่าจะมีการทำ consistent read
      SELECT ... FOR UPDATE ไม่ใช่ consistent read ดังนั้นจึงทำงานได้ดีในสถานการณ์ที่มี concurrency โดยไม่ต้องล็อกเป็นสิบหรือเป็นร้อยแถวด้วย SQL update ทั่วไป
    • ใช่ ถ้ารับได้ที่ประสิทธิภาพจะพังยับเยิน
  • จากประสบการณ์ของผม นักพัฒนาส่วนใหญ่ไม่ได้คำนึงถึง isolation level ตั้งแต่แรก และใช้ค่าเริ่มต้นไปตามนั้น
    พอเกิด race condition ก็แค่ “เอ๊ะ แปลกแฮะ” แล้วก็ปล่อยผ่าน

    • อยากจะโต้แย้งนะ แต่ช่วงแรก ๆ ที่ MongoDB ประสบความสำเร็จพิสูจน์คำพูดนั้นได้ดี
    • นั่นจึงเป็นเหตุผลที่บอกว่า isolation level เริ่มต้นควรเป็น serializable
      [1] https://news.ycombinator.com/item?id=38696421
    • ปัญหา isolation นั้นให้เหตุผลตามได้ยากเกินไป ดังนั้นสิ่งส่วนใหญ่ที่ต่ำกว่าความสอดคล้องแบบ serializable สุดท้ายจะย้อนมาขัดขาหลายทาง
      เพราะฉะนั้นนักพัฒนาส่วนใหญ่ไม่ควรต้องมาคิดเรื่อง isolation level เอง และผมมองว่า MySQL กับฐานข้อมูลบางตัวให้ระดับการรับประกันน้อยเกินไปสำหรับนักพัฒนาทั่วไป
    • จากประสบการณ์ของผม แทบไม่มีนักพัฒนาคนไหนคำนึงถึง consistency เองเลย