Jepsen’in MySQL 8.0.34 değerlendirmesi
(jepsen.io)- MySQL 8.0.34’ün varsayılan izolasyon seviyesi olan Repeatable Read, tek bir sağlıklı düğümde bile ANSI SQL ve Adya’nın PL-2.99 beklentileriyle uyuşmayan işlem tutarlılığı ihlalleri gösteriyor
- Elle’in list-append denetleyicisi, hedefli workload ve LazyFS birleştirilerek MySQL 8.0.34, MariaDB 10.11.3, binlog replikasyon kümesi ve AWS RDS MySQL Multi-AZ DB Cluster birlikte doğrulandı
- Kleppmann’ın 2014 Hermitage sonuçlarında olduğu gibi G2-item, G-single ve lost update yeniden üretildi; ayrıca iç tutarlılık ihlalleri, non-repeatable read ve Monotonic Atomic View ihlalleri de gözlemlendi
- Tekil MySQL’de Read Uncommitted, Read Committed ve Serializable sırasıyla PL-1, PL-2 ve PL-3 ile uyumlu görünürken AWS RDS MySQL kümesi Serializable’da bile G2-item ve G-single gösterdi
- ANSI veya PL-2.99 düzeyinde Repeatable Read gerekiyorsa yalnızca MySQL Repeatable Read’e güvenmek zor; Serializable ya da
SELECT... FOR UPDATEgibi açık kilitleme gerekiyor
Değerlendirilen sistemler ve kapsam
- MySQL yaygın kullanılan ilişkisel bir veritabanıdır; bu analizde “MySQL”, varsayılan depolama motoru olan InnoDB’yi kullanan MySQL anlamına gelir
- Odak tek sunuculu MySQL olsa da binlog replikasyonu kullanan tek yazılabilir primary ve salt okunur secondary kümeleri de ele alınır
- Test edilen sistemler şunlardır
- MySQL 8.0.34
- MariaDB 10.11.3
- Debian Bookworm
- AWS RDS Cluster için “Multi-AZ DB Cluster” profili
- Çalışma herhangi bir ücret alınmadan bağımsız olarak yürütüldü ve Jepsen ethics policy uyarınca gerçekleştirildi
SQL izolasyon seviyeleri ve Repeatable Read ölçütü
- ANSI SQL; Read Uncommitted, Read Committed, Repeatable Read ve Serializable seviyelerini P1 dirty read, P2 non-repeatable read, P3 phantom olasılığına göre tanımlar
- 1995’te Berenson ve diğerleri, A Critique of ANSI SQL Isolation Levels çalışmasında ANSI tanımlarının muğlaklığını ve eksikliğini eleştirdi
- P1, P2, P3 yoruma açıktır
- P0 dirty write gibi önemli olgular eksiktir
- P3 yalnızca predicate’i etkileyen insert işlemlerini yasaklar, update veya delete işlemlerini ele almaz
- Atul Adya’nın 1999 tarihli makalesi, işlemler arası bağımlılık grafı temelinde uygulamadan bağımsız izolasyon seviyeleri tanımlar
- PL-1, G0 write cycle’ı yasaklar
- PL-2, G0 ve G1’i yasaklar
- PL-2.99, G0, G1 ve G2-item’ı yasaklar ve Repeatable Read’e karşılık gelir
- PL-3, G0, G1 ve G2’yi yasaklar ve Serializable’a karşılık gelir
- Jepsen genelde işlem geçmişlerini ve anomalileri belirlemek için Adya’nın biçimini kullanır
MySQL belgeleri ile Repeatable Read arasındaki çelişki
- MySQL belgeleri, InnoDB’nin SQL:1992 standardındaki dört izolasyon seviyesinin tamamını sunduğunu belirtir
- Varsayılan izolasyon seviyesi olan Repeatable Read, aynı işlem içindeki consistent read’lerin ilk okumada kurulan snapshot’ı okuduğunu açıklar
- consistent read belgesi de veritabanının ilk okuma anındaki timepoint’e göre görüldüğünü belirtir
- Ancak aynı belgedeki bir not, snapshot’ın
SELECTiçin geçerli olduğunu ama DML ifadeleri için her zaman geçerli olmadığını; başka işlemlerin commit ettiği row’laraDELETEveyaUPDATEdokunabileceğini söyler - Bu not, ANSI SQL ve MySQL referans kılavuzunun
SELECT’i de DML olarak görmesiyle çelişir ve Repeatable Read altında yazma işlemlerinin okunamayan row’ları etkileyebileceği konusunda kafa karışıklığı yaratır
Test tasarımı
- MySQL için test paketi, Jepsen testing library 0.3.4 temel alınarak yazıldı
- İstemci
mysql-connector-jJDBC adaptörünü kullanır - Testler process pause, crash, network partition ve fsync edilmemiş disk yazılarının kaybı gibi fault injection içerir
- Ancak bu analizdeki bulguların neredeyse tamamı normal durumdaki tek bir MySQL düğümünde ortaya çıktı
-
Elle list-append workload
- Temel workload, Elle’in list-append checker’ını kullanır
- Elle, işlemler arasındaki write-write, write-read ve read-write bağımlılıklarını çıkarır; bağımlılık grafındaki cycle’larla belirli izolasyon seviyesi ihlallerini kanıtlar
- list-append workload, primary key ile tanımlanan birden çok list üzerinde read ve append işlemlerinden oluşan rastgele transaction’lar yürütür
- list, virgülle ayrılmış değerler içeren bir
textalanı olarak kodlanır; append SQLCONCATile işlenir - Son iyileştirmelerle Elle şunları daha iyi tespit eder
- Okunmamış append element’leri için ww/rw bağımlılık çıkarımı
- P4 lost update’in açık tespiti
- real-time edge ve process edge içeren karmaşık cycle araması
-
Hedefli workload
- Non-repeatable read workload’u,
peopletablosundaki tek bir row’u hedefler - Bir transaction ailesi yalnızca
namealanını update eder; diğer ailenamealanını okur,genderalanını update eder, ardındannamealanını tekrar okur - İki okuma arasında
namedeğişirse bu bir Repeatable Read ihlalidir - Monotonic Atomic View workload’u iki row’un
valuedeğerini kullanır - writer, row 0’ın
valuedeğerini artırdıktan sonra row 1’i artırır - reader row 0’ı okur, row 1’in
noopalanını update eder, ardından row 1 ve row 0’ı okur - Bir transaction’ın etkilerinden bazıları görüldüyse tüm etkileri görülmelidir
- Non-repeatable read workload’u,
-
LazyFS
- LazyFS, fsync edilmemiş yazma kayıplarını simüle eden bir FUSE dosya sistemidir
- MySQL process’i öldürülüp LazyFS cache’i atıldıktan sonra MySQL yeniden başlatılarak test edilir
- Bu rapor, LazyFS’in yer aldığı ilk kamuya açık Jepsen raporudur
MySQL Repeatable Read’de bulunan anomaliler
-
G2-item
- Adya’nın PL-2.99 Repeatable Read’i, predicate içermeyen write-write, write-read, read-write bağımlılık cycle’ı olan G2-item’ı yasaklar
- MySQL Repeatable Read, tek bir sağlıklı düğümde bile G2-item’a tekrar tekrar izin verir
- Kleppmann’ın 2014’te Hermitage ile raporladığı davranış MySQL 8.0.34’te de sürer
- Örnek test 40 saniyede 214 cycle gösterdi
- Bu davranış PL-2.99 Repeatable Read’de yasaktır; ancak ANSI SQL’in P2 tanımı yalnızca aynı row’un iki kez okunmasını ele aldığı için ANSI tanımı açısından yorum payı kalır
-
G-single ve read skew
- MySQL Repeatable Read G-single da gösterir
- G-single, write-write, write-read, read-write edge’lerinden oluşan; ancak read-write edge’lerinin birbirine bitişik olmadığı bir cycle’dır
- Kleppmann’ın 2014’te raporladığı read skew MySQL 8.0.34’te de doğrulandı
- 60 saniyelik append testinde saniyede yaklaşık 140 transaction’da 244 G-single ve 305 G2-item ortaya çıktı
- append testi predicate işlemleri kullanmadığı için hepsi Repeatable Read ihlali olarak sınıflandırılır
-
Lost update
- P4 lost update, iki transaction’ın aynı key’in aynı version’ını okuyup ikisinin de update yaptığı G-single’ın özel bir durumudur
- Snapshot Isolation ve PL-2.99 Repeatable Read, lost update’i yasaklar
- MySQL Repeatable Read, tek bir sağlıklı düğümde bile lost update’e tekrar tekrar izin verir
- Bir testte, 9.048 başarılı transaction içinden yeni checker 198 lost update vakasına karışan 446 transaction buldu
- Bunların yalnızca 47’si cycle olarak ortaya çıktı
- Değeri okuyup sonra yazma deseni MySQL Repeatable Read’de güvenli değildir
- Nesneyi okuyup bellekte değiştirdikten sonra yeniden kaydeden standart ORM deseninde commit edilmiş değişiklikler sessizce kaybolabilir
- Kullanıcılar açık kilitleri kendileri kullanmalıdır
-
Non-repeatable read ve iç tutarlılık ihlali
- MySQL Repeatable Read, tek bir sağlıklı düğümde bile iç tutarlılık ihlali gösterir
- Aynı test run’ında 9.048 commit edilmiş transaction’dan 126’sı iç tutarlılık hatası gösterdi
- Bir örnekte, bir transaction key’i
nilolarak okuyup bir değer append ettikten sonra aynı key’i tekrar okuduğunda, başka üç değerin eklenmiş olduğunu gözlemledi - Başka bir örnekte key 1096
[1 2 3]olarak okunup7append edildikten sonra tekrar okunduğunda[1 2 3 4 5 6 7]gözlemlendi - Hedefli workload’da bir Repeatable Read transaction’ı içinde
name"pebble"olarak okundu,gender"femme"olarak update edildikten sonra aynınametekrar okunduğunda"moss"döndü - Bu davranış ANSI SQL’in non-repeatable read tanımına ve MySQL belgelerindeki “ilk okumada kurulan snapshot” açıklamasına aykırıdır
-
Monotonic Atomic View ihlali
- Monotonic Atomic View, bir transaction’ın herhangi bir etkisini gören transaction’ın o transaction’ın tüm etkilerini görmesi gerektiğini söyleyen bir özelliktir
- MySQL Repeatable Read bunu normal çalışan tek bir düğümde bile tekrar tekrar ihlal eder
- Workload’da writer önce row 0’ı, ardından row 1’i artırır
- reader row 0’da önceki değer
0’ı gördükten sonra row 1’de writer’ın artışı olan1’i görür, ardından row 0’da hâlâ0görür - Bu, row 1’in etkisini görüp row 0’ın etkisini görmeyen monoton olmayan okumadır ve yaygın snapshot davranışıyla uyuşmaz
AWS RDS MySQL Serializable anomalileri
- AWS RDS MySQL kümesi, “Serializable” izolasyon seviyesinde bile Serializability’yi tekrar tekrar ihlal eder
- Varsayılan önerilen production profiline sahip RDS MySQL kümesinde append testi G2-item ve G-single anomalileri gösterdi
- Gözlemlenen anomaliler, bir transaction’ın etkisini gören transaction’ın daha önceki bağımlılıklarını başka bir transaction’ın kaçırması biçimindeydi
- Bu anomali hem G-single hem G2-item olarak sınıflandırılır ve Snapshot Isolation, Repeatable Read ve Serializability’nin hepsini ihlal eder
replica_preserve_commit_orderile ilgili ayarlar şüpheli etken olarak duruyor- MySQL 8.0.27 ve sonrasında
replica_preserve_commit_order=ONvarsayılandır - RDS varsayılan parametreleri hâlâ
replica_preserve_commit_order=OFF’a karşılık gelen ayarı seçer - RDS parameter group’ta bu ayarın eski adı olan
slave_preserve_commit_orderkullanılır - Yerel test kümesine bu ayar uygulandığında benzer G-single ve G2-item gözlemlenir
- MySQL 8.0.27 ve sonrasında
Normal görünen kısımlar ve LazyFS sonuçları
- MySQL 8.0.34’ün Read Uncommitted, Read Committed ve Serializable seviyeleri sırasıyla PL-1, PL-2 ve PL-3’ü karşılıyor gibi görünür
- Bu sonuç hem tek düğümde hem de binlog replikasyonu kullanan küçük salt okunur replica kümelerinde gözlemlendi
- process pause, crash ve network partition durumlarında da bu sonuç korundu
- LazyFS fault injection, MySQL varsayılan ayarlarında sorun bulmadı
- Varsayılan
innodb_flush_log_at_trx_commit=1değerinde process crash ve fsync edilmemiş veri kaybı sonrasında bile commit edilmiş transaction kaybı görülmedi innodb_flush_log_at_trx_commit=0olarak değiştirildiğinde MySQL yalnızca birkaç saniyede bir fsync yaptı ve veri kaybı gözlemlendi
MySQL Repeatable Read’ün gerçek niteliği
- MySQL Repeatable Read, PL-2.99 Repeatable Read’ü karşılamaz
- G2-item ve write skew gösterir
- Snapshot Isolation’ı da karşılamaz
- G-single, read skew ve lost update gösterir
- cursor stability’yi de karşılamaz
- lost update gerçekleşir
- Read Atomic, Causal Consistency, Consistent View, Prefix Consistency ve Parallel Snapshot Isolation da dışlanır
- İç tutarlılık ihlalleri gözlemlendi
- MySQL Repeatable Read, Read Committed’dan biraz daha güçlü görünür
- G0 dirty write, G1a aborted read, G1b intermediate read, G1c cyclic information flow gözlemlenmedi
- Bazı okumaların repeatability’si Read Committed’dan daha güçlü bir özellik sağlar
- Ancak MySQL Repeatable Read’ün tam olarak hangi consistency model olduğu net değildir ve resmi bir özellik tanımı da yoktur
Belgeler ile topluluk anlayışı arasındaki uyumsuzluk
- MySQL topluluğunda Repeatable Read davranışı yeterince anlaşılmış değildir
- Birçok yazı MySQL Repeatable Read’ün lost update’i engellediğine inanır; ancak başka yazılar engellemediğini bildirir ve açık kilit kullanımını önerir
- İnternetteki birçok kaynak MySQL Repeatable Read’ün gerçekten repeatable olduğunu söyler; Jepsen’in testleri ise bunun tersine örnekler gösterir
- MySQL ve MariaDB belgeleri de Repeatable Read’ün aynı transaction içinde aynı snapshot’ı okuduğunu açıklar
- MySQL consistent read belgesindeki bir cümle bu açıklamayla çelişen davranışı ima eder; ancak bu içerik belgenin içinde gömülü kalmıştır
Öneriler
- MySQL mevcut davranışı koruyacaksa “Repeatable Read”ün gerçekte hangi consistency model’ı sunduğunu açıkça belgelemelidir
- Diğer seçenek, mevcut davranışı bir bug olarak ele alıp düzeltmektir
- MySQL ve diğer vendor’lar PL-2.99 Repeatable Read sunmayı taahhüt ederse Jepsen bunu memnuniyetle karşılayacağını belirtir
- PL-2.99 veya ANSI Repeatable Read’e ihtiyaç duyan kullanıcılar MySQL Repeatable Read konusunda dikkatli olmalıdır
- Pratik alternatifler şunlardır
- MySQL’in Serializable izolasyon seviyesini kullanmak
READ COMMITTEDaltındaSELECT ... FOR UPDATEgibi kilitleme teknikleriyle okumayı güçlendirmek
RDS kullanıcıları için öneriler
- AWS RDS MySQL kümesi “Serializable” altında read skew ve G2-item gösterir
- Serializability’ye dayanan kullanıcılar RDS parameter group’ta
slave_preserve_commit_orderdeğeriniONyapmalıdır - AWS’nin varsayılanı değiştirmesi veya RDS MySQL’in known limitations belgesinde izin verilen Serializability ihlallerini açıkça açıklaması önerilir
Gelecek çalışmalar ve standardizasyon çağrısı
- MySQL binlog replication kırılgan göründü
- Yerel Jepsen testlerinde replication’ın durduğu çeşitli durumlar gözlemlendi
- AWS RDS MySQL replication, birkaç dakikalık testle tamamen bozulabiliyordu; primary’de başarılı olan
CREATE DATABASEsecondary’de görünmedi ve bu durum 1 saat boyunca düzelmedi
- secondary’nin primary’ye yükseltilmesi veya ring, star gibi replikasyon topology’leri araştırılmadı
- predicate safety’yi değerlendirmek için daha genel predicate test araştırması sürüyor
- ANSI SQL izolasyon seviyesi tanımları, Berenson ve diğerlerinin muğlaklık ve eksikliklerine dikkat çekmesinden bu yana 28 yıl ve 7 ANSI·ISO revizyonu geçmesine rağmen değişmedi
- ISO/IEC 9075-2’nin iç anomaly, lost update ve dirty write gibi olguları açıkça ele alabilmesi için daha biçimsel ve taşınabilir izolasyon seviyesi tanımlarına ihtiyaç var
1 yorum
Hacker News yorumları
repeatable read’in, uygulaması kusursuz olsa bile kötü bir fikir olduğunu uzun zamandır düşünüyorum
Veritabanı içinde doğru çalışsa bile, karmaşık sorgularda akıl yürütmek fazlasıyla zor
Anlamlı olan yalıtım seviyelerinin yalnızca read committed ve serializable olduğunu düşünüyorum
Ya sürpriz olmasın diye sonuna kadar serializable’a gitmek gerekir ya da transaction içinde tutarlı bir görünüm gerekiyorsa okuma yapmadan önce satırları kilitlemek gerektiğinin açık olduğu read committed’a gitmek gerekir
read committed, genel çok iş parçacıklı koda ve bellek yönetimine daha yakın olduğu için mühendislerin sezgi geliştirmesi daha kolay; serializable ise o kadar katı ki beklenmedik hatalar yaratmak zor
Arası sahipsiz bölge; read committed’dan daha az tutarlı olan şeyleri ise artık düzgün bir veritabanı olarak görmek zor
Uygulama büyüdükçe kilitlerin nerede alındığını ve veriye nerede erişildiğini tüm durumlar için anlamak çok zorlaşıyor
Okuma/yazma transaction’ları için aklı başında tek yalıtım modeli serializable; salt okunur transaction’lar içinse veritabanının belirli bir andaki snapshot’ıyla çalışan snapshot isolation iyi bir model bence
Spanner’ın sunduğu modlar da aslında yalnızca bu ikisi: https://cloud.google.com/spanner/docs/transactions
FOSSDEM 2024’te SQL veritabanlarının yalıtım seviyeleri ve MVCC’sini karşılaştıran bir sunum var
Oracle, MySQL, SQL Server, PostgreSQL ve YugabyteDB’yi ele alıyor
https://fosdem.org/2024/schedule/event/fosdem-2024-3600-isol...
append(a)’nın verilen bir tablodaki gerçek SQL işlemlerine nasıl eşlendiğini merak ediyorum
TEXT alanını liste gibi mi kullanıyor?
MySQL repeatable read modunda tek bir satırı seçen tek bir SELECT’in imkânsız bir sonuç döndürdüğünü de görmüştüm
SELECT min(value), max(value) FROM table WHERE id = 1;biçimindeydi;idprimary key’di amaminvemaxfarklı değerler olarak gelmiştiBu arada bu, CONCAT’a özgü bir sorun değil. CONCAT kullanılmasının nedeni, anomaliyi üstel zamanda değil doğrusal zamanda akıl yürüterek çıkarabilmek
Aynı tür davranış sıradan okuma/yazma register’larında da görülür
Yazıyı ve AWS RDS’yi ele almanız güzel, ama AWS Aurora MySQL’e de odaklanılıp odaklanılmadığını merak ediyorum
Bilmeyenler için: AWS, MySQL veya PostgreSQL gibi davranan protokol uyumlu bir veritabanı platformu geliştirdi
Aurora MySQL’in RDS ya da MariaDB ile aynı “özelliklere” sahip olup olmadığını görmek ilginç olurdu
Yine de çok ilginç bir hedef ve Aurora çok daha yeni bir veritabanı olduğundan, eski MySQL’e kıyasla henüz keşfedilmemiş ince sorunlar olabileceğine dair bir sezgim var
Ancak büyük bir can sıkıcı nokta var
Plaid mühendisleri farkları güzel özetleyen bir yazı yazmış: https://plaid.com/blog/exploring-performance-differences-bet...
Benim için en büyük fark, Aurora cluster’ının paylaşımlı depolama kullanması nedeniyle yalıtım modelinin biraz farklı olması
read committed ancak cluster genelinde parametre ayarlanırsa mümkün; read uncommitted ise bana kalırsa mümkün değil
Çok ilginç bir yazı
Bu kadar çok tutarlılık anomalisi gösteren bir temel üzerinde bile ne kadar çok “gerçekten çalışan sistem” kurulabildiğini iyi gösteriyor
5 dakika içinde kurcalayınca RDS replikasyonunun durması ve başarısız sağlık kontrolü bildiriminin de olmaması kısmı biraz endişe verici
Ancak kullanıcıya 150’den fazla metriği kurcalayıp belgeleri okuyarak önemli olanı bulma yükünü yıkma eğiliminde
Ayrıca <https://docs.aws.amazon.com/AmazonRDS/latest/UserGuide/USER_...> replikasyon durumunu gösteren bir konsol tablo hücresi olduğunu söylüyor, ama konsolda çoğu zaman kullanıcının ilgili sütun gösterimini elle açması gerekiyor; bu iyi değil
AWS’nin “paylaşılan sorumluluk modeli” dediği şeye oldukça fazla yaslanıyor
Her şeyi host ya da container içinden kendiniz yapmalısınız
AWS/Rackspace desteği yalnızca “AWS hizmetinin içinde çalışan şeyleri biz yönetmeyiz, bu müşterinin sorunu” der
2022’de Jepsen’in Porto Üniversitesi INESC TEC’e LazyFS geliştirmesi için iş vermiş olması hoşuma gitti
fsyncedilmemiş yazma kayıplarını simüle eden bir FUSE dosya sistemi; teknolojinin seviyesini ileri taşımanın harika bir örneğiSELECT ... FOR UPDATEbu sorunların cevabı gibi görünüyorGüncellenecek satırları kilitleyince birden her şey reklam edildiği gibi çalışmıyor mu?
Bir kaydı başka bir kaydın verilerine göre güncellemek istiyorsanız, o diğer kayda ve muhtemelen güncellenecek kayda da kilitlemeli okuma yapmanız gerekir
Tek bir SQL sorgusuyla bir kaydı başka bir kayda dayanarak güncellerseniz MySQL zaten ikisini de kilitler
Birden çok hedefe dayanarak bir şeyi güncellemeniz gerekiyorsa, benim deneyimime göre deadlock oluşması çok kolaydır
Bunun yerine kilitleme için kullanılan bir kayıt gibi bir şeyi kilitleyip, sonra istediğiniz veride repeatable read yaparak güncellemek daha iyidir
Repeatable read’in zamanı, tutarlı okuma yapılana kadar belirlenmez
SELECT ... FOR UPDATEtutarlı okuma değildir; bu yüzden normal bir SQL güncellemesiyle onlarca/yüzlerce satırı kilitlemeden de eşzamanlılık durumlarında iyi çalışırBenim deneyimime göre çoğu geliştirici baştan izolasyon seviyesini hiç düşünmez ve varsayılanı olduğu gibi kullanır
Yarış durumu ortaya çıkınca da “ha, tuhafmış” deyip geçer
[1] https://news.ycombinator.com/item?id=38696421
Bu yüzden çoğu geliştiricinin izolasyon seviyelerini bizzat düşünmemesi daha iyi; MySQL ve bazı veritabanlarının ortalama geliştiriciye fazla az garanti sunduğunu düşünüyorum