Jepsen का MySQL 8.0.34 मूल्यांकन
(jepsen.io)- MySQL 8.0.34 का डिफ़ॉल्ट isolation level Repeatable Read एक सामान्य single node पर भी transaction consistency violations दिखाता है, जो ANSI SQL और Adya की PL-2.99 अपेक्षाओं से मेल नहीं खाते
- Elle के list-append checker, targeted workload, और LazyFS को मिलाकर MySQL 8.0.34, MariaDB 10.11.3, binlog replication cluster, और AWS RDS MySQL Multi-AZ DB Cluster की साथ में जाँच की गई
- Kleppmann के 2014 Hermitage परिणामों की तरह G2-item, G-single, और lost update दोबारा पाए गए, साथ ही internal consistency violations, non-repeatable read, और Monotonic Atomic View violations भी देखे गए
- Single MySQL में Read Uncommitted, Read Committed, और Serializable क्रमशः PL-1, PL-2, PL-3 के अनुरूप दिखे, लेकिन AWS RDS MySQL cluster ने Serializable में भी G2-item और G-single दिखाया
- अगर ANSI या PL-2.99 स्तर का Repeatable Read चाहिए, तो सिर्फ MySQL Repeatable Read पर भरोसा करना कठिन है; Serializable या
SELECT... FOR UPDATEजैसे explicit locking की ज़रूरत है
मूल्यांकन का लक्ष्य और दायरा
- MySQL एक व्यापक रूप से उपयोग किया जाने वाला relational database है, और इस विश्लेषण में “MySQL” से आशय डिफ़ॉल्ट storage engine InnoDB का उपयोग करने वाले MySQL से है
- फोकस single-server MySQL पर है, लेकिन binlog replication वाले single-write primary और read-only secondary cluster भी शामिल हैं
- परीक्षण के लक्ष्य ये थे
- MySQL 8.0.34
- MariaDB 10.11.3
- Debian Bookworm
- AWS RDS Cluster का “Multi-AZ DB Cluster” प्रोफ़ाइल
- यह काम बिना किसी पारिश्रमिक के स्वतंत्र रूप से किया गया, और Jepsen ethics policy के अनुसार संचालित हुआ
SQL isolation levels और Repeatable Read का मानदंड
- ANSI SQL, Read Uncommitted, Read Committed, Repeatable Read, और Serializable को P1 dirty read, P2 non-repeatable read, और P3 phantom की अनुमति/मनाही के आधार पर परिभाषित करता है
- 1995 में Berenson आदि ने A Critique of ANSI SQL Isolation Levels में ANSI परिभाषाओं की अस्पष्टता और अपूर्णता की आलोचना की
- P1, P2, P3 की व्याख्या में गुंजाइश है
- P0 dirty write जैसी महत्वपूर्ण घटनाएँ छूट गई हैं
- P3 केवल predicate को प्रभावित करने वाले insert को रोकता है, update या delete को नहीं
- Atul Adya के 1999 के पेपर ने transactions के बीच dependency graph के आधार पर implementation-independent isolation levels परिभाषित किए
- 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 के फ़ॉर्मलिज़्म का उपयोग करके transaction histories और anomalies की पहचान करता है
MySQL documentation और Repeatable Read के बीच टकराव
- MySQL documentation बताती है कि InnoDB SQL:1992 standard के सभी चार isolation levels प्रदान करता है
- डिफ़ॉल्ट isolation level Repeatable Read के बारे में कहा गया है कि एक transaction के भीतर consistent read, पहली read पर बने snapshot को पढ़ता है
- consistent read documentation भी कहती है कि database को पहली read के timepoint के अनुसार देखा जाता है
- लेकिन उसी documentation की एक टिप्पणी कहती है कि snapshot
SELECTपर लागू होता है, पर DML statements पर ज़रूरी नहीं;DELETEयाUPDATEउन rows को छू सकते हैं जिन्हें किसी दूसरे transaction ने commit किया हो - यह टिप्पणी ANSI SQL और MySQL reference manual की उस बात से टकराती है जिसमें
SELECTको भी DML माना जाता है, और इससे भ्रम पैदा होता है कि Repeatable Read में write उन rows को प्रभावित कर सकती है जिन्हें read नहीं देखा जा सकता था
परीक्षण डिज़ाइन
- MySQL के लिए test suite, Jepsen testing library 0.3.4 पर आधारित है
- क्लाइंट
mysql-connector-jJDBC adapter का उपयोग करते हैं - टेस्ट में process pause, crash, network partition, और fsync न हुई disk writes के loss जैसे fault injection शामिल हैं
- लेकिन इस विश्लेषण की लगभग सभी खोजें सामान्य स्थिति वाले single MySQL node पर ही हुईं
-
Elle list-append workload
- मुख्य workload, Elle के list-append checker का उपयोग करता है
- Elle, transactions के बीच write-write, write-read, और read-write dependencies infer करता है, और dependency graph में cycle के आधार पर isolation level violations साबित करता है
- list-append workload, primary key से पहचानी जाने वाली कई lists पर read और append वाले random transactions चलाता है
- list को comma-separated values रखने वाले
textfield में encode किया जाता है, और append को SQLCONCATसे किया जाता है - हाल की improvements से Elle अब यह बेहतर detect करता है
- unread append elements के लिए ww/rw dependency inference
- P4 lost update की explicit detection
- real-time edge और process edge सहित complex cycle search
-
Targeted workload
- Non-repeatable read workload,
peopletable की एक row को target करता है - एक प्रकार के transactions केवल
nameupdate करते हैं, और दूसरा प्रकारnameपढ़ता है,genderupdate करता है, फिरnameदोबारा पढ़ता है - अगर दोनों reads के बीच
nameबदल जाए, तो यह Repeatable Read violation है - Monotonic Atomic View workload दो rows के
valueका उपयोग करता है - writer पहले row 0 का
valueबढ़ाता है, फिर row 1 का - reader row 0 पढ़ता है, row 1 के
noopको update करता है, फिर row 1 और row 0 पढ़ता है - अगर किसी transaction का कुछ प्रभाव देखा गया है, तो उसका पूरा प्रभाव दिखना चाहिए
- Non-repeatable read workload,
-
LazyFS
- LazyFS एक FUSE filesystem है जो fsync न हुई writes के loss को simulate करता है
- MySQL process को मारकर, LazyFS cache को discard करके, फिर MySQL restart करके टेस्ट किया गया
- यह रिपोर्ट LazyFS को शामिल करने वाली पहली public Jepsen report है
MySQL Repeatable Read में पाए गए anomalies
-
G2-item
- Adya का PL-2.99 Repeatable Read, predicate के बिना write-write, write-read, read-write dependency cycles यानी G2-item को रोकता है
- MySQL Repeatable Read, एक सामान्य single node पर भी बार-बार G2-item की अनुमति देता है
- Kleppmann ने 2014 में Hermitage में जो व्यवहार रिपोर्ट किया था, वह MySQL 8.0.34 में भी जारी है
- एक उदाहरण टेस्ट में 40 सेकंड में 214 cycles दिखे
- यह व्यवहार PL-2.99 Repeatable Read में निषिद्ध है, लेकिन ANSI SQL की P2 परिभाषा केवल एक ही row को दो बार पढ़ने के मामले को कवर करती है, इसलिए ANSI परिभाषा में कुछ व्याख्यात्मक गुंजाइश बचती है
-
G-single और read skew
- MySQL Repeatable Read में G-single भी दिखता है
- G-single, write-write, write-read, और read-write edges से बना cycle है, लेकिन read-write edges आपस में adjacent नहीं होते
- Kleppmann द्वारा 2014 में रिपोर्ट किया गया read skew, MySQL 8.0.34 में भी पुष्टि हुआ
- 60-second append test में लगभग 140 transactions प्रति सेकंड पर G-single के 244 और G2-item के 305 मामले दिखे
- append test predicate operations का उपयोग नहीं करता, इसलिए इन सबको Repeatable Read violation के रूप में वर्गीकृत किया गया
-
Lost update
- P4 lost update, G-single का एक विशेष मामला है जिसमें दो transactions एक ही key के एक ही version को पढ़ते हैं और दोनों update करते हैं
- Snapshot Isolation और PL-2.99 Repeatable Read, lost update को रोकते हैं
- MySQL Repeatable Read, एक सामान्य single node पर भी बार-बार lost update की अनुमति देता है
- एक टेस्ट में 9,048 successful transactions में नए checker ने 198 lost update मामलों में शामिल 446 transactions पाए
- इनमें से केवल 47 मामले cycle के रूप में सामने आए
- read-then-write pattern, MySQL Repeatable Read में सुरक्षित नहीं है
- object को पढ़कर memory में modify करके फिर save करने वाले standard ORM pattern में committed changes चुपचाप गायब हो सकते हैं
- उपयोगकर्ताओं को explicit locking स्वयं इस्तेमाल करनी चाहिए
-
Non-repeatable read और internal consistency violations
- MySQL Repeatable Read, एक सामान्य single node पर भी internal consistency violations दिखाता है
- उसी test run में 9,048 committed transactions में से 126 ने internal consistency errors दिखाए
- एक उदाहरण में, एक transaction ने key को
nilपढ़ा, एक value append की, और फिर उसी key को दोबारा पढ़ने पर तीन अन्य values जोड़ी हुई देखीं - दूसरे उदाहरण में, key 1096 को
[1 2 3]पढ़कर7append करने के बाद दोबारा पढ़ने पर[1 2 3 4 5 6 7]दिखा - targeted workload में, एक Repeatable Read transaction ने
nameको"pebble"पढ़ा,genderको"femme"update किया, और फिर वहीnameदोबारा पढ़ने पर"moss"पाया - ऐसा व्यवहार ANSI SQL की non-repeatable read परिभाषा और MySQL documentation के “पहली read पर बना snapshot” वाले वर्णन के विरुद्ध है
-
Monotonic Atomic View violation
- Monotonic Atomic View वह गुण है जिसमें यदि एक transaction दूसरे transaction का कोई प्रभाव देखता है, तो उसे उसके सभी प्रभाव देखने चाहिए
- MySQL Repeatable Read, सामान्य single node पर भी इसे बार-बार तोड़ता है
- workload में writer पहले row 0 बढ़ाता है, फिर row 1
- reader, row 0 में पुराना value
0देखता है, फिर row 1 में writer की increment1देखता है, और फिर row 0 में अब भी0देखता है - यानी row 1 का प्रभाव दिखा, पर row 0 का नहीं; यह non-monotonic read है और सामान्य snapshot behavior से मेल नहीं खाता
AWS RDS MySQL Serializable में anomalies
- AWS RDS MySQL cluster, “Serializable” isolation level में भी Serializability को बार-बार तोड़ता है
- डिफ़ॉल्ट recommended production profile वाले RDS MySQL cluster में append test ने G2-item और G-single anomalies दिखाए
- देखी गई anomalies में ऐसा हुआ कि एक transaction के प्रभाव को देखने वाले transaction की earlier dependency किसी दूसरे transaction से छूट गई
- यह anomaly, G-single और G2-item दोनों के रूप में वर्गीकृत होती है, और Snapshot Isolation, Repeatable Read, तथा Serializability तीनों का उल्लंघन करती है
replica_preserve_commit_orderसे जुड़ी setting एक संदिग्ध कारण बनी हुई है- MySQL 8.0.27 और बाद के versions में
replica_preserve_commit_order=ONडिफ़ॉल्ट है - RDS के default parameters अब भी ऐसी setting चुनते हैं जो
replica_preserve_commit_order=OFFके बराबर है - RDS parameter group में इस setting का पुराना नाम
slave_preserve_commit_orderइस्तेमाल होता है - Local test cluster पर यह setting लागू करने से मिलते-जुलते G-single और G2-item देखे गए
- MySQL 8.0.27 और बाद के versions में
जो सामान्य दिखा और LazyFS के परिणाम
- MySQL 8.0.34 के Read Uncommitted, Read Committed, और Serializable क्रमशः PL-1, PL-2, और PL-3 को संतुष्ट करते दिखे
- यह परिणाम single node और binlog replication वाले छोटे read-only replica cluster दोनों में दिखा
- process pause, crash, और network partition में भी यही परिणाम बने रहे
- LazyFS fault injection को MySQL default settings में कोई समस्या नहीं मिली
innodb_flush_log_at_trx_commit=1डिफ़ॉल्ट पर process crash और fsync न हुई data loss के बाद भी committed transactions का loss नहीं दिखाinnodb_flush_log_at_trx_commit=0करने पर MySQL हर कुछ सेकंड में केवल एक बार fsync कर रहा था, और data loss देखा गया
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 violations देखे गए
- MySQL Repeatable Read, Read Committed से कुछ अधिक मज़बूत दिखता है
- G0 dirty write, G1a aborted read, G1b intermediate read, और G1c cyclic information flow नहीं देखा गया
- कुछ reads की repeatability, Read Committed से अधिक मजबूत गुण देती है
- फिर भी MySQL Repeatable Read वास्तव में कौन-सा consistency model है, यह स्पष्ट नहीं है, और इसकी कोई औपचारिक property definition भी नहीं है
Documentation और community समझ के बीच असंगति
- MySQL community में Repeatable Read के व्यवहार को अभी भी पर्याप्त रूप से नहीं समझा गया है
- कई लेख मानते हैं कि MySQL Repeatable Read, lost update को रोकता है, जबकि अन्य लेख कहते हैं कि यह नहीं रोकता और explicit locking की सलाह देते हैं
- कई इंटरनेट स्रोत कहते हैं कि MySQL Repeatable Read वास्तव में repeatable है, लेकिन Jepsen के tests इसके विपरीत उदाहरण दिखाते हैं
- MySQL और MariaDB documentation भी कहती है कि Repeatable Read, एक ही transaction के भीतर वही snapshot पढ़ता है
- MySQL consistent read documentation का एक वाक्य इस विवरण से टकराने वाले व्यवहार का संकेत देता है, लेकिन वह बात documentation के भीतर दब गई है
सिफ़ारिशें
- अगर MySQL अपना मौजूदा व्यवहार बनाए रखता है, तो उसे स्पष्ट रूप से document करना चाहिए कि “Repeatable Read” वास्तव में कौन-सा consistency model देता है
- दूसरा विकल्प यह है कि मौजूदा व्यवहार को bug माना जाए और ठीक किया जाए
- अगर MySQL और अन्य vendors, PL-2.99 Repeatable Read देने का वादा करते हैं, तो Jepsen ने कहा है कि वह उसका स्वागत करेगा
- जिन उपयोगकर्ताओं को PL-2.99 या ANSI Repeatable Read चाहिए, उन्हें MySQL Repeatable Read से सावधान रहना चाहिए
- व्यावहारिक विकल्प ये हैं
- MySQL का Serializable isolation level उपयोग करना
READ COMMITTEDमेंSELECT ... FOR UPDATEजैसी locking techniques से reads को मज़बूत करना
RDS उपयोगकर्ताओं के लिए सिफ़ारिशें
- AWS RDS MySQL cluster, “Serializable” में read skew और G2-item दिखाता है
- जो उपयोगकर्ता Serializability पर निर्भर हैं, उन्हें RDS parameter group में
slave_preserve_commit_orderकोONकरना चाहिए - सुझाव दिया गया है कि AWS डिफ़ॉल्ट बदले, या RDS MySQL की known limitations documentation में स्वीकार्य Serializability violations को स्पष्ट रूप से बताए
आगे का काम और standardization की मांग
- MySQL binlog replication कमजोर दिखी
- Local Jepsen tests में replication रुक जाने की कई स्थितियाँ देखी गईं
- AWS RDS MySQL replication केवल कुछ मिनटों के testing में पूरी तरह टूट सकती थी, और primary पर सफल
CREATE DATABASEsecondary पर 1 घंटे तक नहीं दिखा
- secondary को primary में promote करना, या ring, star जैसी replication topologies की जाँच नहीं की गई
- predicate safety का मूल्यांकन करने के लिए अधिक सामान्य predicate tests पर काम जारी है
- ANSI SQL isolation level definitions, Berenson आदि द्वारा अस्पष्टता और अपूर्णता की ओर इशारा किए जाने के 28 साल बाद और 7 ANSI·ISO revisions के बाद भी नहीं बदलीं
- ISO/IEC 9075-2 में internal anomaly, lost update, dirty write जैसी घटनाओं को स्पष्ट रूप से कवर करने के लिए अधिक formal और portable isolation level definitions की ज़रूरत है
1 टिप्पणियां
Hacker News की राय
मैं लंबे समय से मानता आया हूँ कि repeatable read, भले ही उसका implementation परफेक्ट हो, एक खराब idea है
database के अंदर यह सही तरह से काम करे तब भी, complex queries में इसके बारे में reasoning करना बहुत मुश्किल हो जाता है
मेरे हिसाब से केवल दो isolation levels समझ में आते हैं: read committed और serializable
या तो आखिर तक serializable अपनाएँ ताकि कोई surprise न हो, या read committed अपनाएँ, जहाँ यह साफ हो कि अगर transaction के अंदर consistent view चाहिए तो पढ़ने से पहले rows को lock करना होगा
read committed आम multithreaded code और memory management के ज्यादा करीब है, इसलिए engineers के लिए इसकी intuition बनाना आसान है; और serializable इतना strict है कि अनपेक्षित गलतियाँ करना मुश्किल होता है
इनके बीच की चीज़ no-man's land है, और read committed से भी कम consistent चीज़ को अब ठीक-ठाक database मानना मुश्किल है
जैसे-जैसे application बड़ा होता है, हर case में कहाँ lock लगते हैं और data कहाँ access होता है, यह समझना बहुत कठिन हो जाता है
read/write transactions के लिए serializable ही समझदारी वाला isolation model है, और read-only transactions के लिए किसी खास समय के database snapshot पर काम करने वाला snapshot isolation अच्छा model है
Spanner जो modes देता है, वे भी असल में यही दो हैं: https://cloud.google.com/spanner/docs/transactions
FOSSDEM 2024 में SQL databases के isolation levels और MVCC की तुलना करने वाली एक presentation है
इसमें Oracle, MySQL, SQL Server, PostgreSQL, YugabyteDB शामिल हैं
https://fosdem.org/2024/schedule/event/fosdem-2024-3600-isol...
जानना चाहूँगा कि append(a) किसी given table के actual SQL operation में कैसे map होता है
क्या TEXT field को list की तरह इस्तेमाल किया जा रहा है?
MySQL repeatable read mode में single row चुनने वाली single SELECT ने भी कभी असंभव result लौटाया था
यह
SELECT min(value), max(value) FROM table WHERE id = 1;जैसा था, औरidprimary key था, लेकिनminऔरmaxअलग-अलग values निकलेध्यान रहे, यह सिर्फ CONCAT से जुड़ी specific समस्या नहीं है। CONCAT इस्तेमाल करने की वजह यह है कि anomaly के बारे में exponential time के बजाय linear time में reasoning की जा सकती है
इसी तरह का व्यवहार साधारण read/write register में भी दिखता है
article और AWS RDS को cover करना अच्छा लगा, लेकिन जानना चाहूँगा कि क्या AWS Aurora MySQL पर भी focus था
जिन्हें नहीं पता, उनके लिए: AWS ने एक protocol-compatible database platform बनाया है जो MySQL या PostgreSQL होने का दिखावा करता है
यह देखना दिलचस्प होगा कि Aurora MySQL में RDS या MariaDB जैसी ही “विशेषताएँ” हैं या नहीं
फिर भी यह बहुत दिलचस्प target है, और चूँकि Aurora काफी नया database है, मेरी intuition है कि पुराने MySQL की तुलना में इसमें अभी तक न खोजे गए subtle issues हो सकते हैं
हालांकि एक बड़ी झुंझलाहट जरूर है
Plaid engineers ने differences को अच्छी तरह summarize करने वाला लेख लिखा है: https://plaid.com/blog/exploring-performance-differences-bet...
मेरे लिए सबसे बड़ा फर्क यह है कि Aurora cluster shared storage इस्तेमाल करता है, इसलिए isolation model थोड़ा अलग है
read committed केवल cluster-wide parameter set करने पर ही संभव है, और read uncommitted मेरे हिसाब से संभव नहीं है
बहुत दिलचस्प लेख है
यह अच्छी तरह दिखाता है कि इतनी सारी consistency anomalies दिखाने वाली नींव पर भी कितने “असल में चलने वाले systems” बनाए जा सकते हैं
5 मिनट में छेड़ते ही RDS replication रुक गया, और failed health check का कोई alert भी नहीं आया—यह हिस्सा थोड़ा चिंताजनक है
हालांकि वह यह बोझ user पर डाल देता है कि 150 से ज़्यादा metrics खंगाले और docs पढ़कर ज़रूरी चीज़ें ढूँढे
साथ ही <https://docs.aws.amazon.com/AmazonRDS/latest/UserGuide/USER_...> में कहा गया है कि replication status दिखाने वाला console table cell होता है, लेकिन console में अक्सर user को वह column display खुद enable करना पड़ता है, जो अच्छा नहीं है
AWS अपने “shared responsibility model” पर काफी ज़्यादा निर्भर करता है
host या container के अंदर से सब कुछ खुद करना होगा
AWS/Rackspace support बस इतना कहता है कि “AWS service के अंदर चलने वाली चीज़ें हम manage नहीं करते, इसलिए यह customer issue है”
2022 में Jepsen ने Porto University के INESC TEC को LazyFS develop करने के लिए commission किया था—यह हिस्सा अच्छा लगा
fsyncन किए गए write loss को simulate करने वाला FUSE filesystem—technology को आगे धकेलने का यह शानदार उदाहरण हैSELECT ... FOR UPDATEइन समस्याओं का जवाब जैसा लगता हैupdate की जाने वाली rows को lock कर दें तो क्या अचानक सब कुछ advertised तरीके से काम नहीं करने लगता?
अगर किसी record को दूसरे record के data के आधार पर update करना हो, तो उस दूसरे record और शायद update किए जाने वाले record पर भी locking read करना होगा
अगर एक single SQL query से किसी दूसरे record के आधार पर record update करें, तो MySQL वैसे भी दोनों को lock कर देता है
अगर कई targets के आधार पर कुछ update करना हो, तो मेरे अनुभव में deadlock बहुत आसानी से हो जाता है
इसके बजाय बेहतर है कि locking के लिए किसी record जैसी चीज़ को lock करें, फिर इच्छित data पर repeatable read करें और update करें
repeatable read का point-in-time तब तक तय नहीं होता जब तक consistent read perform न किया जाए
SELECT ... FOR UPDATEconsistent read नहीं है, इसलिए यह normal SQL update से दर्जनों या सैकड़ों rows lock किए बिना भी concurrency situations में अच्छा काम करता हैमेरे अनुभव में ज्यादातर developers शुरू से ही isolation level पर विचार नहीं करते और default ही इस्तेमाल करते हैं
race condition आए तो “अरे, अजीब है” कहकर आगे बढ़ जाते हैं
[1] https://news.ycombinator.com/item?id=38696421
इसलिए ज्यादातर developers के लिए isolation level पर खुद सोच-विचार न करना ही बेहतर है, और मुझे लगता है कि MySQL और कुछ databases average developer को बहुत कम guarantees देते हैं