Evaluasi Jepsen terhadap MySQL 8.0.34
(jepsen.io)- Repeatable Read, level isolasi bawaan MySQL 8.0.34, menunjukkan pelanggaran konsistensi transaksi yang tidak sesuai dengan ekspektasi ANSI SQL dan PL-2.99 dari Adya, bahkan pada satu node sehat
- Dengan menggabungkan list-append checker Elle, targeted workload, dan LazyFS, pengujian memverifikasi MySQL 8.0.34, MariaDB 10.11.3, klaster replikasi binlog, serta AWS RDS MySQL Multi-AZ DB Cluster
- Seperti hasil Hermitage Kleppmann pada 2014, G2-item, G-single, dan lost update berhasil direproduksi; pelanggaran konsistensi internal, non-repeatable read, serta pelanggaran Monotonic Atomic View juga diamati
- Read Uncommitted, Read Committed, dan Serializable pada MySQL tunggal tampak masing-masing sesuai dengan PL-1, PL-2, dan PL-3, tetapi klaster AWS RDS MySQL menunjukkan G2-item dan G-single bahkan pada Serializable
- Jika membutuhkan Repeatable Read pada level ANSI atau PL-2.99, sulit mengandalkan MySQL Repeatable Read saja; diperlukan Serializable atau locking eksplisit seperti
SELECT... FOR UPDATE
Target dan cakupan evaluasi
- MySQL adalah basis data relasional yang banyak digunakan, dan dalam analisis ini “MySQL” berarti MySQL yang menggunakan InnoDB sebagai storage engine bawaan
- Fokusnya adalah MySQL server tunggal, tetapi juga mencakup klaster dengan primary penulis tunggal dan secondary read-only yang menggunakan replikasi binlog
- Target pengujian adalah sebagai berikut
- MySQL 8.0.34
- MariaDB 10.11.3
- Debian Bookworm
- Profil “Multi-AZ DB Cluster” pada AWS RDS Cluster
- Pekerjaan dilakukan secara independen tanpa kompensasi, dan mengikuti kebijakan etika Jepsen
Level isolasi SQL dan kriteria Repeatable Read
- ANSI SQL mendefinisikan Read Uncommitted, Read Committed, Repeatable Read, dan Serializable berdasarkan kemungkinan P1 dirty read, P2 non-repeatable read, dan P3 phantom
- Pada 1995, Berenson dkk. dalam A Critique of ANSI SQL Isolation Levels mengkritik ambiguitas dan ketidaklengkapan definisi ANSI
- P1, P2, dan P3 dapat ditafsirkan dengan berbagai cara
- Fenomena penting seperti P0 dirty write tidak tercakup
- P3 hanya melarang insert yang memengaruhi predicate, tetapi tidak membahas update atau delete
- Makalah Atul Adya tahun 1999 mendefinisikan level isolasi yang independen dari implementasi berdasarkan graf dependensi antartransaksi
- PL-1 melarang G0 write cycle
- PL-2 melarang G0 dan G1
- PL-2.99 melarang G0, G1, dan G2-item, serta berpadanan dengan Repeatable Read
- PL-3 melarang G0, G1, dan G2, serta berpadanan dengan Serializable
- Jepsen umumnya menggunakan formalitas Adya untuk menilai riwayat transaksi dan anomali
Konflik antara dokumentasi MySQL dan Repeatable Read
- Dokumentasi MySQL menjelaskan bahwa InnoDB menyediakan keempat level isolasi standar SQL:1992
- Repeatable Read, level isolasi bawaan, dijelaskan bahwa consistent read dalam transaksi yang sama membaca snapshot yang ditetapkan pada pembacaan pertama
- Dokumentasi consistent read juga menjelaskan bahwa database dilihat berdasarkan timepoint pada pembacaan pertama
- Namun catatan dalam dokumen yang sama mengatakan bahwa snapshot berlaku untuk
SELECT, tetapi tidak selalu berlaku untuk pernyataan DML, danDELETEatauUPDATEdapat menyentuh row yang sudah di-commit oleh transaksi lain - Catatan ini bertentangan dengan ANSI SQL dan manual referensi MySQL yang juga menganggap
SELECTsebagai DML, serta menimbulkan kebingungan bahwa pada Repeatable Read, operasi tulis dapat memengaruhi row yang sebelumnya tidak dapat dibaca
Desain pengujian
- Test suite untuk MySQL ditulis berdasarkan Jepsen testing library 0.3.4
- Klien menggunakan adaptor JDBC
mysql-connector-j - Pengujian mencakup fault injection seperti process pause, crash, network partition, dan hilangnya penulisan disk yang belum di-fsync
- Namun hampir semua temuan dalam analisis ini terjadi pada satu node MySQL dalam kondisi sehat
-
Elle list-append workload
- Workload utama menggunakan list-append checker dari Elle
- Elle menyimpulkan dependensi write-write, write-read, dan read-write antartransaksi, lalu membuktikan pelanggaran level isolasi tertentu melalui cycle pada graf dependensi
- List-append workload menjalankan transaksi acak yang terdiri dari read dan append terhadap beberapa list yang diidentifikasi dengan primary key
- List dienkode sebagai field
textberisi nilai yang dipisahkan koma, dan append diproses dengan SQLCONCAT - Dengan perbaikan terbaru, Elle lebih baik dalam mendeteksi hal-hal berikut
- Inferensi dependensi ww/rw terhadap append element yang belum terbaca
- Deteksi eksplisit P4 lost update
- Pencarian cycle kompleks yang mencakup real-time edge dan process edge
-
Targeted workload
- Non-repeatable read workload menargetkan satu row pada tabel
people - Satu rangkaian transaksi hanya meng-update
name, sedangkan rangkaian lain membacaname, meng-updategender, lalu membacanamelagi - Jika
nameberubah di antara dua pembacaan, itu adalah pelanggaran Repeatable Read - Monotonic Atomic View workload menggunakan
valuedari dua row - Writer menaikkan
valuerow 0, lalu menaikkan row 1 - Reader membaca row 0, meng-update
nooprow 1, lalu membaca row 1 dan row 0 - Jika sebagian efek dari sebuah transaksi terlihat, semua efeknya juga harus terlihat
- Non-repeatable read workload menargetkan satu row pada tabel
-
LazyFS
- LazyFS adalah filesystem FUSE yang menyimulasikan hilangnya penulisan yang belum di-fsync
- Pengujian dilakukan dengan mematikan process MySQL, membuang cache LazyFS, lalu memulai ulang MySQL
- Laporan ini adalah laporan Jepsen publik pertama yang mencakup LazyFS
Anomali yang ditemukan pada MySQL Repeatable Read
-
G2-item
- PL-2.99 Repeatable Read dari Adya melarang G2-item, yaitu cycle dependensi write-write, write-read, dan read-write yang tidak melibatkan predicate
- MySQL Repeatable Read berulang kali mengizinkan G2-item, bahkan pada satu node sehat
- Perilaku yang dilaporkan Kleppmann pada 2014 di Hermitage masih terjadi di MySQL 8.0.34
- Contoh pengujian menunjukkan 214 cycle selama 40 detik
- Perilaku ini dilarang dalam PL-2.99 Repeatable Read, tetapi karena definisi P2 ANSI SQL hanya membahas kasus membaca row yang sama dua kali, masih ada ruang interpretasi menurut definisi ANSI
-
G-single dan read skew
- MySQL Repeatable Read juga menunjukkan G-single
- G-single adalah cycle yang terdiri dari edge write-write, write-read, dan read-write, tetapi edge read-write tidak saling bersebelahan
- Read skew yang dilaporkan Kleppmann pada 2014 dikonfirmasi kembali di MySQL 8.0.34
- Dalam pengujian append 60 detik, pada sekitar 140 transaksi per detik, muncul 244 G-single dan 305 G2-item
- Karena pengujian append tidak menggunakan operasi predicate, semuanya diklasifikasikan sebagai pelanggaran Repeatable Read
-
Lost update
- P4 lost update adalah kasus khusus G-single ketika dua transaksi membaca version yang sama dari key yang sama dan keduanya meng-update
- Snapshot Isolation dan PL-2.99 Repeatable Read melarang lost update
- MySQL Repeatable Read berulang kali mengizinkan lost update, bahkan pada satu node sehat
- Dalam satu pengujian, dari 9.048 transaksi sukses, checker baru menemukan 446 transaksi yang terlibat dalam 198 kasus lost update
- Dari kasus-kasus ini, hanya 47 yang terlihat sebagai cycle
- Pola membaca nilai lalu menulisnya tidak aman pada MySQL Repeatable Read
- Dalam pola ORM standar yang membaca objek, memodifikasinya di memori, lalu menyimpannya kembali, perubahan yang sudah di-commit dapat hilang secara diam-diam
- Pengguna harus menggunakan locking eksplisit sendiri
-
Non-repeatable read dan pelanggaran konsistensi internal
- MySQL Repeatable Read menunjukkan pelanggaran konsistensi internal, bahkan pada satu node sehat
- Dalam run pengujian yang sama, 126 dari 9.048 transaksi commit menunjukkan kesalahan konsistensi internal
- Dalam contoh, sebuah transaksi membaca key sebagai
nil, menambahkan satu nilai, lalu saat membaca lagi key yang sama, mengamati tiga nilai lain sudah ditambahkan - Dalam contoh lain, key 1096 dibaca sebagai
[1 2 3], lalu7di-append, dan saat dibaca lagi terlihat[1 2 3 4 5 6 7] - Dalam targeted workload, di dalam satu transaksi Repeatable Read,
namedibaca sebagai"pebble",genderdi-update menjadi"femme", lalu saatnameyang sama dibaca lagi, hasilnya"moss" - Perilaku seperti ini bertentangan dengan definisi non-repeatable read ANSI SQL dan penjelasan dokumentasi MySQL bahwa snapshot “ditetapkan pada pembacaan pertama”
-
Pelanggaran Monotonic Atomic View
- Monotonic Atomic View adalah sifat bahwa transaksi yang melihat salah satu efek dari sebuah transaksi harus melihat semua efek transaksi tersebut
- MySQL Repeatable Read berulang kali melanggarnya, bahkan pada satu node normal
- Dalam workload, writer menaikkan row 0 lalu menaikkan row 1
- Reader melihat nilai sebelumnya
0pada row 0, kemudian melihat kenaikan writer1pada row 1, lalu kembali melihat0pada row 0 - Ini adalah pembacaan non-monotonik: efek pada row 1 terlihat, tetapi efek pada row 0 tidak terlihat, sehingga tidak sesuai dengan perilaku snapshot yang umum
Anomali pada AWS RDS MySQL Serializable
- Klaster AWS RDS MySQL berulang kali melanggar Serializability bahkan pada level isolasi “Serializable”
- Pada klaster RDS MySQL dengan profil production yang direkomendasikan secara default, pengujian append menunjukkan anomali G2-item dan G-single
- Anomali yang diamati berbentuk transaksi lain melewatkan dependensi sebelumnya dari suatu transaksi yang efeknya telah dilihat
- Anomali ini diklasifikasikan sebagai G-single sekaligus G2-item, dan melanggar Snapshot Isolation, Repeatable Read, serta Serializability
- Pengaturan terkait
replica_preserve_commit_ordertetap menjadi faktor yang dicurigai- MySQL 8.0.27 ke atas menjadikan
replica_preserve_commit_order=ONsebagai default - Parameter bawaan RDS masih memilih pengaturan yang setara dengan
replica_preserve_commit_order=OFF - RDS parameter group menggunakan nama lama pengaturan ini, yaitu
slave_preserve_commit_order - Jika pengaturan ini diterapkan pada klaster pengujian lokal, G-single dan G2-item serupa teramati
- MySQL 8.0.27 ke atas menjadikan
Bagian yang tampak normal dan hasil LazyFS
- Read Uncommitted, Read Committed, dan Serializable pada MySQL 8.0.34 tampak masing-masing memenuhi PL-1, PL-2, dan PL-3
- Hasil ini diamati baik pada node tunggal maupun pada klaster kecil dengan read-only replica yang menggunakan replikasi binlog
- Hasil tersebut tetap bertahan pada process pause, crash, dan network partition
- Fault injection LazyFS tidak menemukan masalah pada konfigurasi bawaan MySQL
- Dengan nilai default
innodb_flush_log_at_trx_commit=1, tidak tampak kehilangan transaksi yang sudah di-commit bahkan setelah process crash dan hilangnya data yang belum di-fsync - Jika diubah menjadi
innodb_flush_log_at_trx_commit=0, MySQL hanya melakukan fsync sekali setiap beberapa detik, dan kehilangan data teramati
Karakter sebenarnya dari MySQL Repeatable Read
- MySQL Repeatable Read tidak memenuhi PL-2.99 Repeatable Read
- Menunjukkan G2-item dan write skew
- Tidak memenuhi Snapshot Isolation
- Menunjukkan G-single, read skew, dan lost update
- Tidak memenuhi cursor stability
- Lost update terjadi
- Read Atomic, Causal Consistency, Consistent View, Prefix Consistency, dan Parallel Snapshot Isolation juga dikesampingkan
- Pelanggaran konsistensi internal diamati
- MySQL Repeatable Read tampak sedikit lebih kuat daripada Read Committed
- G0 dirty write, G1a aborted read, G1b intermediate read, dan G1c cyclic information flow tidak diamati
- Repeatability pada sebagian pembacaan memberikan sifat yang lebih kuat daripada Read Committed
- Namun tidak jelas secara persis model consistency apa yang diberikan MySQL Repeatable Read, dan tidak ada definisi sifat yang formal
Ketidaksesuaian antara dokumentasi dan pemahaman komunitas
- Di komunitas MySQL, perilaku Repeatable Read belum dipahami dengan cukup baik
- Beberapa tulisan meyakini bahwa MySQL Repeatable Read mencegah lost update, tetapi tulisan lain menyatakan bahwa ia tidak mencegahnya dan menyarankan penggunaan locking eksplisit
- Banyak materi internet mengatakan bahwa MySQL Repeatable Read benar-benar repeatable, tetapi pengujian Jepsen menunjukkan kasus yang tidak demikian
- Dokumentasi MySQL dan MariaDB juga menjelaskan bahwa Repeatable Read membaca snapshot yang sama dalam transaksi yang sama
- Satu kalimat dalam dokumentasi consistent read MySQL mengisyaratkan perilaku yang bertentangan dengan penjelasan ini, tetapi konten tersebut tersembunyi di dalam dokumentasi
Rekomendasi
- Jika MySQL mempertahankan perilaku saat ini, ia harus mendokumentasikan dengan jelas model consistency apa yang sebenarnya diberikan oleh “Repeatable Read”
- Pilihan lain adalah memperlakukan perilaku saat ini sebagai bug dan memperbaikinya
- Jepsen menyatakan akan menyambut baik jika MySQL dan vendor lain berkomitmen menyediakan PL-2.99 Repeatable Read
- Pengguna yang membutuhkan PL-2.99 atau ANSI Repeatable Read harus berhati-hati dengan MySQL Repeatable Read
- Alternatif praktis adalah sebagai berikut
- Menggunakan level isolasi Serializable pada MySQL
- Memperkuat pembacaan dengan teknik locking seperti
SELECT ... FOR UPDATEpadaREAD COMMITTED
Rekomendasi untuk pengguna RDS
- Klaster AWS RDS MySQL menunjukkan read skew dan G2-item pada “Serializable”
- Pengguna yang bergantung pada Serializability harus mengatur
slave_preserve_commit_ordermenjadiONdi RDS parameter group - Ada usulan agar AWS mengubah default, atau menjelaskan secara eksplisit pelanggaran Serializability yang diperbolehkan dalam dokumentasi known limitations untuk RDS MySQL
Pekerjaan mendatang dan permintaan standardisasi
- MySQL binlog replication tampak rapuh
- Dalam pengujian Jepsen lokal, ditemukan beberapa situasi ketika replication berhenti
- AWS RDS MySQL replication dapat sepenuhnya rusak hanya dengan beberapa menit pengujian, dan kondisi ketika
CREATE DATABASEyang sukses di primary tidak muncul di secondary tidak pulih selama 1 jam
- Promosi secondary menjadi primary maupun topology replikasi seperti ring dan star tidak dieksplorasi
- Penelitian mengenai predicate test yang lebih umum untuk mengevaluasi predicate safety sedang berlangsung
- Definisi level isolasi ANSI SQL belum berubah meskipun 28 tahun telah berlalu sejak Berenson dkk. menunjukkan ambiguitas dan ketidaklengkapannya, serta sudah ada 7 revisi ANSI·ISO
- Dibutuhkan definisi level isolasi yang lebih formal dan portabel agar ISO/IEC 9075-2 dapat menangani secara jelas fenomena seperti internal anomaly, lost update, dan dirty write
1 komentar
Opini Hacker News
Saya sudah lama menganggap repeatable read sebagai ide buruk meskipun implementasinya sempurna
Sekalipun bekerja dengan benar di dalam database, pada kueri yang kompleks penalarannya terlalu rumit
Menurut saya hanya ada dua tingkat isolasi yang masuk akal: read committed dan serializable
Entah sekalian memakai serializable sampai akhir agar tidak ada kejutan, atau memakai read committed yang jelas bahwa jika membutuhkan tampilan yang konsisten di dalam transaksi, baris harus dikunci sebelum dibaca
read committed lebih dekat dengan kode multithreaded umum dan manajemen memori, sehingga engineer lebih mudah memiliki intuisi; sedangkan serializable begitu ketat sehingga sulit menimbulkan kesalahan tak terduga
Yang di tengah adalah wilayah tak bertuan, dan apa pun yang kurang konsisten daripada read committed sulit dianggap sebagai database yang layak
Semakin besar aplikasi, semakin sulit memahami semua kemungkinan tentang di mana lock diambil dan data diakses
Untuk transaksi baca/tulis, hanya serializable yang merupakan model isolasi yang waras; untuk transaksi read-only, snapshot isolation, yang menangani snapshot database pada titik waktu tertentu, adalah model yang baik menurut saya
Mode yang disediakan Spanner pun pada dasarnya hanya dua ini: https://cloud.google.com/spanner/docs/transactions
Di FOSSDEM 2024 ada presentasi yang membandingkan tingkat isolasi dan MVCC pada database SQL
Mencakup Oracle, MySQL, SQL Server, PostgreSQL, YugabyteDB
https://fosdem.org/2024/schedule/event/fosdem-2024-3600-isol...
Saya penasaran append(a) dipetakan seperti apa ke operasi SQL nyata pada tabel yang diberikan
Apakah field TEXT dipakai seperti list?
Di mode repeatable read MySQL, saya juga pernah melihat satu SELECT yang memilih satu baris mengembalikan hasil yang mustahil
Bentuknya
SELECT min(value), max(value) FROM table WHERE id = 1;, danidadalah primary key, tetapimindanmaxkeluar sebagai nilai yang berbedaSebagai catatan, ini bukan masalah yang khusus hanya pada CONCAT. Alasan memakai CONCAT adalah karena anomali bisa dinalar dalam waktu linear, bukan waktu eksponensial
Perilaku sejenis juga muncul pada register baca/tulis biasa
Artikelnya bagus dan saya senang AWS RDS ikut dibahas, tetapi saya penasaran apakah AWS Aurora MySQL juga menjadi fokus
Bagi yang belum tahu, AWS membuat platform database yang kompatibel protokol dan berpura-pura sebagai MySQL atau PostgreSQL
Akan menarik melihat apakah Aurora MySQL memiliki “fitur” yang sama seperti RDS atau MariaDB
Meski begitu, itu target yang sangat menarik, dan karena Aurora adalah database yang jauh lebih baru, saya punya firasat ada masalah halus yang belum ditemukan dibandingkan MySQL yang lebih lama
Namun ada satu hal yang sangat menjengkelkan
Para engineer Plaid menulis artikel yang merangkum perbedaannya dengan baik: https://plaid.com/blog/exploring-performance-differences-bet...
Perbedaan terbesar bagi saya adalah karena cluster Aurora memakai shared storage, model isolasinya sedikit berbeda
read committed hanya bisa digunakan dengan menetapkan parameter di seluruh cluster, dan read uncommitted menurut saya tidak mungkin
Artikel yang sangat menarik
Ini menunjukkan dengan baik betapa banyak “sistem yang benar-benar berjalan” bisa dibangun di atas fondasi yang memperlihatkan begitu banyak anomali konsistensi
Bagian yang agak mengkhawatirkan adalah ketika disentuh dalam 5 menit, replikasi RDS berhenti, dan tidak ada notifikasi health check yang gagal
Namun mereka cenderung melempar beban kepada pengguna untuk menyisir lebih dari 150 metrik dan membaca dokumentasi agar menemukan yang penting
Selain itu, <https://docs.aws.amazon.com/AmazonRDS/latest/UserGuide/USER_...> mengatakan ada sel tabel konsol yang menampilkan status replikasi, tetapi di konsol pengguna sering harus mengaktifkan sendiri tampilan kolom tersebut, dan ini kurang baik
AWS cukup banyak mengandalkan apa yang mereka sebut “model tanggung jawab bersama”
Semuanya harus dilakukan sendiri dari dalam host atau container
Dukungan AWS/Rackspace hanya akan berkata, “apa yang berjalan di dalam layanan AWS tidak kami kelola, jadi itu masalah pelanggan”
Saya suka bagian bahwa pada 2022 Jepsen menugaskan INESC TEC Universitas Porto untuk mengembangkan LazyFS
Sebuah filesystem FUSE yang menyimulasikan hilangnya write yang belum di-
fsync; ini contoh bagus yang mendorong tingkat teknis ke depanSELECT ... FOR UPDATEtampak seperti jawaban untuk masalah-masalah iniKalau baris yang akan di-update dikunci, bukankah tiba-tiba semuanya bekerja seperti yang diiklankan?
Jika ingin memperbarui suatu record berdasarkan data dari record lain, Anda perlu melakukan locking read pada record lain itu, dan mungkin juga pada record yang akan diperbarui
Jika memperbarui record berdasarkan record lain dengan satu kueri SQL, MySQL pada akhirnya akan mengunci keduanya
Jika perlu memperbarui sesuatu berdasarkan banyak target, dari pengalaman saya deadlock sangat mudah terjadi
Sebagai gantinya, lebih baik mengunci sesuatu seperti record khusus untuk locking, lalu melakukan repeatable read pada data yang diinginkan dan memperbaruinya
Titik waktu repeatable read belum ditentukan sampai Anda melakukan consistent read
SELECT ... FOR UPDATEbukan consistent read, jadi ia bekerja baik dalam situasi konkurensi tanpa perlu mengunci puluhan atau ratusan baris dengan update SQL biasaDari pengalaman saya, sebagian besar developer sejak awal tidak mempertimbangkan isolation level dan memakai default apa adanya
Ketika muncul race condition, mereka hanya berkata “eh, aneh ya” lalu lanjut
[1] https://news.ycombinator.com/item?id=38696421
Jadi sebagian besar developer sebaiknya tidak perlu memikirkan isolation level sendiri, dan menurut saya MySQL serta beberapa database lain memberikan tingkat jaminan yang terlalu sedikit bagi developer rata-rata