भविष्य को आकार देने वाली तकनीक पर गहन लेख।

वितरित SQLite: 12 साल बाद 5 मिथकों की परख

बारह साल के वितरित SQLite — Raft, Litestream, Turso, D1 — पर पाँच मान्यताओं की जाँच: consistency, replication और कब Postgres चुनें।

फाइल कैबिनेट का एक घेरा, रोशनी के धागों से जुड़ा हुआ, जिसमें एक लीडर की तरह चमकता है
वितरित SQLite के बारह साल: कई डिज़ाइन, लेकिन एक ही मूल विचार जिसे बार-बार नए सिरे से गढ़ा गया।

वितरित SQLite एक व्यक्ति के वीकेंड प्रोजेक्ट से वेंचर-फंडेड प्रोडक्ट कैटेगरी बन गया, और ज़्यादातर इंजीनियरों को बीच के दशक का पता ही नहीं चला। सितंबर 2014 में Philip O'Toole ने SQLite के सामने एक Raft log लगाया, हर write को क्लस्टर में replicate किया, और कोड को rqlite के नाम से GitHub पर डाल दिया। बारह साल बाद पूरा ecosystem है — dqlite, Litestream, LiteFS, cr-sqlite, libSQL/Turso, Cloudflare D1 — और इसके बारे में लोग जो मानते हैं, उसका ज़्यादातर हिस्सा दिलचस्प तरीकों से गलत है।

मैंने clustered databases production में चलाए हैं और उनकी failure modes को संभाला है, तो आपको थोड़ी परेशानी से बचाता हूँ: वितरित SQLite की बहसें लगभग कभी SQLite के बारे में नहीं होतीं। वे इस बारे में होती हैं कि आप असल में कौन-सा सवाल पूछ रहे हैं — अगर मशीन मर जाए तो? अगर मशीन मर जाए और मैं इंतज़ार नहीं कर सकता तो? या अगर कोई मशीन ही न हो तो? बारह साल के इतिहास के आधार पर यहाँ पाँच मिथक हैं।

मिथक 1: वितरित SQLite एक नया विचार है

replicated SQLite पर आज की लहर लगभग 2021 के आसपास शुरू होती है, जब Litestream और “SQLite is enough” आंदोलन ज़ोर पकड़ने लगे, और Turso व Cloudflare D1 फंडिंग और लॉन्च पोस्ट के साथ आए। इसलिए यह धारणा बन गई कि किसी होशियार व्यक्ति ने हाल ही में देखा कि SQLite को replicate किया जा सकता है। असल में पहला Raft-over-SQLite सिस्टम 2014 में आया था, और वह SQLite का पहला replication भी नहीं था — Expensify 2016 में Bedrock जारी करने से पहले सालों तक अंदरूनी तौर पर Paxos-जैसे protocol से यही कर रहा था, और ActorDB ने उसी दौर में हर Erlang actor को अपना Raft-replicated SQLite database दिया था।

श्रेय से परे यह क्यों मायने रखता है? क्योंकि एक डिज़ाइन जो बारह साल की स्वतंत्र पुनर्रचना में टिका रहे, वह कुछ बताता है। कम से कम आठ अलग-अलग टीमों ने, अलग ज़रूरतों और अलग protocols के साथ, इस समस्या को देखा और एक ही architecture पर पहुँचीं: storage engine के रूप में SQLite, और replication बाहर से जोड़ा हुआ। किसी को इसे रोकने की वजह नहीं मिली। जब आप ऐसा पैटर्न देखें — एक दशक में अभिसरण करती हुई पुनर्रचना — तो आप एक सचमुच अच्छे विचार को देख रहे हैं, किसी hype cycle को नहीं। इसे उन कैटेगरी से तुलना करें जो भड़कती हैं और गायब हो जाती हैं; यह लगातार बढ़ती रही।

यह इसलिए भी मायने रखता है क्योंकि पुराने design फैसले आज भी भार उठा रहे हैं। rqlite ने 2014 में CP system बनने का चुनाव किया — writes leader से होकर जाते हैं, बहुमत को acknowledge करना होता है, और partition में फँसा अल्पमत writes से इनकार करता है — और अपना मन कभी नहीं बदला। उस एक चुनाव ने HTTP API, read consistency levels और snapshotting के व्यवहार को तय किया। जो टीमें consistency को बाद में तय होने वाला configuration toggle मानती हैं, वे आम तौर पर पछताती हैं; वह spec जो आपने मान लिया था और वह जो आपने लागू किया, इनके बीच का अंतर ही वह जगह है जहाँ distributed bugs पनपते हैं।

मिथक 2: SQLite पर Raft आपको सस्ता distributed Postgres दे देता है

यह इस सूची का सबसे महँगा मिथक है। पिच सुनने में लुभावनी लगती है: दुनिया का सबसे ज़्यादा इस्तेमाल होने वाला database लो, consensus जोड़ो, और client-server system के operational बोझ से बच जाओ। असल में आपको एक ऐसा single-writer database मिलता है जहाँ हर write को बहुमत नोड्स तक network round trip चुकाना पड़ता है, और जहाँ SQLite की SQL सतह — कोई stored procedures नहीं, पतला type system, सीमित concurrency primitives — वैसी की वैसी रहती है।

write path ही ईमानदार कीमत है। स्थानीय SQLite commit में कुछ fsyncs लगते हैं। Raft-replicated commit में होता है: leader अपने log में append करता है, entry को followers तक भेजता है, बहुमत के persist करने का इंतज़ार करता है, SQLite पर apply करता है, फिर जवाब देता है। स्वस्थ LAN पर यह शायद एक-दो milliseconds होता है, दस-बीस microseconds के बजाय। Regions के बीच यह प्रकाश की गति और disk का योग है। कम write rate वाले read-heavy workloads — configuration stores, service discovery metadata, छोटे control planes — के लिए यह ठीक है, और rqlite इसी niche के लिए बना था (उसकी उत्पत्ति एक ऐसे clustered system से जुड़ी है जिसे थोड़ी relational storage चाहिए थी, और इसका कोई अच्छा जवाब नहीं था कि उसे कौन-सा node रखे)। एक hot OLTP workload के लिए यह गलत tool है।

और फिर वह operational सच्चाई है जिसे कोई README में नहीं लिखता: leader election अब एक failure mode है जिसकी जिम्मेदारी आपकी है। Split-brain बहुमत voting से रुकता है, हाँ, पर इसका मतलब है कि तीन-node cluster जो दो node खो दे, वह सारे writes से इनकार कर देता है। आपने “सर्वर मर गया, इसलिए database बंद है” को “quorum मर गया, इसलिए database बंद है” से बदल दिया है, और दूसरी स्थिति रात 3 बजे अपने on-call engineer को समझाना ज़्यादा कठिन है। अगर आपने अपने चुने हुए system की Jepsen-style testing के बारे में नहीं सोचा, तो सोचना चाहिए — एक Hacker News commenter ने सटीक सवाल पूछा कि क्या rqlite की Jepsen testing हुई है, क्योंकि जो सिस्टम नहीं हुए, उनमें आम तौर पर सरप्राइज़ छिपे होते हैं। इसी चर्चा में एक अनुभवी engineer ने बताया कि आज के tooling के साथ असली Postgres चलाना इतना आसान हो गया है कि “बस database server इस्तेमाल करो” फिर से एक वैध default है। वह गलत नहीं है।

एक तराज़ू जिस पर एक सादा ब्लॉक और ज़ंजीरों व गियरों के उलझे ढेर को तौला जा रहा है
Consensus आपको failover देता है; कीमत हर एक write पर चुकानी पड़ती है।

मिथक 3: Replication का मतलब consensus है

बारह साल का इतिहास जो एक अंतर्दृष्टि सचमुच देता है, वह यह है: “replicated SQLite” कोई एक डिज़ाइन नहीं है, यह एक design space के तीन कोने हैं, और वे एक सांकेतिक क्रम में सामने आए। इन्हें आपस में मिला देना ही वह तरीका है जिससे टीमें backup की समस्या सुलझाने के लिए Raft cluster चलाने लगती हैं।

  • Consensus (CP). rqlite, dqlite. एक leader, मज़बूत consistency, एक असली cluster। जवाब देता है: “अगर मशीन मर जाए और मैं restore के लिए इंतज़ार न कर सकूँ तो?” कीमत: write latency और quorum operations।
  • Streaming (एक writer). Litestream, बाद में LiteFS। कोई consensus नहीं — SQLite WAL object storage पर stream किया जाता है, जिससे एक ही node के लिए लगभग निरंतर disaster recovery मिलता है। जवाब देता है: “अगर मशीन मर जाए तो?” कीमत: डेटा खोने की एक छोटी खिड़की और restore में सेकंड से मिनट तक का समय।
  • Merge (AP). cr-sqlite और local-first आंदोलन। SQLite extension के रूप में CRDTs, बिना leader के कई writers का अलग-अलग होकर merge होना। जवाब देता है: “अगर कोई केंद्रीय मशीन ही न हो तो?” कीमत: eventual consistency और merge semantics जिनके लिए डिज़ाइन करना होगा।

Litestream के लिए Ben Johnson का तर्क सम्मान का हकदार है, क्योंकि यह ज़्यादातर applications के लिए सही था: वे एक मशीन पर चलते हैं और जब वह मरे तो उन्हें recover करने का तरीका चाहिए, cluster नहीं। पहला सवाल — “अगर मशीन मर जाए” — दूसरे सवाल — “और मैं restore का इंतज़ार नहीं कर सकता” — से कहीं ज़्यादा आम है। मैंने टीमों को तीन-node Raft cluster डिप्लॉय करते देखा है एक ऐसे database की रक्षा के लिए जिसकी RTO आवश्यकता थी “लंच से पहले किसी वक्त।” यह reliability engineering नहीं, cargo cult है। दिलचस्प बात यह है कि Litestream के WAL-streaming तरीके ने rqlite को खुद v8 में WAL संभालने की प्रेरणा दी — streaming वाले कोने ने consensus वाले कोने को फिर से प्रभावित किया।

तो किसी tool को चुनने से पहले सवाल लिख लें। सचमुच लिखें। “जब node X मरे, तो हमें N सेकंड में writes फिर से शुरू करने होंगे, और हम आखिरी M सेकंड का committed डेटा खोना स्वीकार करते हैं।” अगर M कुछ सेकंड है और N एक मिनट, तो Litestream-से-S3 आपका जवाब है और वह आपको quorum के लिए कभी नहीं जगाएगा। अगर M शून्य है और N सेकंड, तो आप consensus वाले इलाके में हैं और आप हर write पर उसकी कीमत चुकाएँगे। यह किसी भी threat model जैसा ही अनुशासन है: बचाव खरीदने से पहले विफलता का नाम लें, जैसे आप आपदा का प्रतिरोध करने वाले systems के पास पहले से तय करके जाते हैं कि आप किस आपदा का प्रतिरोध कर रहे हैं।

मिथक 4: प्रबंधित “edge पर SQLite” trade-offs को हटा देता है

इस कैटेगरी का पैसा platforms की ओर गया — Turso ने venture funding जुटाई और Supabase ने उसे अधिग्रहित कर लिया; Cloudflare ने अपने edge network के भीतर D1 भेजा। पिच यह है कि अब कोई और कठिन distributed हिस्से संभालता है। कभी यह सच होता है। कभी यह मार्केटिंग होती है, और छिपा राज़ बाहर आ जाता है: एक Cloudflare developer ने सार्वजनिक रूप से माना कि D1 मूल रूप से एक singleton Durable Object के आसपास का wrapper है जो अपना SQLite database expose करता है, और अक्सर आप सीधे Durable Objects storage इस्तेमाल करके बेहतर रहेंगे। यह consensus के अर्थ में distributed database नहीं है; यह एक platform के चारों ओर एक single SQLite file है। D1 को production में चलाने वाले उपयोगकर्ता भी इसी स्तर की reliability की रिपोर्ट करते हैं।

इसका मतलब यह नहीं कि प्रबंधित विकल्प खराब हैं। इसका मतलब है कि trade-offs गायब नहीं हुए — वे एक API के पीछे चले गए हैं जहाँ आप उन्हें जाँच नहीं सकते। जब आप rqlite खुद चलाते हैं, तो आप जानते हैं कि CP का मतलब क्या है: तीन node, एक leader, बहुमत की acks, और partition में फँसा अल्पमत writes से इनकार करता है। जब आप “edge पर SQLite” खरीदते हैं, तो एक black box से वही सवाल पूछने होंगे: writer कहाँ है, partition में क्या होता है, मेरी वास्तविक durability खिड़की कितनी है? अगर विक्रेता का जवाब अस्पष्ट है, तो आपकी incident review काम की नहीं होगी।

Replication as a service consistency के सवाल को मिटाता नहीं है। वह बस जवाब को किसी और के infrastructure में डाल देता है, जहाँ आप उसका ऑडिट नहीं कर सकते।

ऐतिहासिक पैटर्न को आत्मसात करना उपयोगी है: open-source systems (rqlite, dqlite, Litestream) ने आठ साल तक इस विचार पर काम किया, इससे पहले कि कोई उसे बेचता, और पूंजी उस ओर गई जिसने विचार को बिल वाली सेवा में बदला। infrastructure का यह आम भाग्य है। खरीदना अक्सर सही फैसला होता है — पर खरीदते समय आँखें खुली रखें कि आपको तीन कोनों में से कौन-सा मिल रहा है।

एक स्नो ग्लोब जिसके भीतर गर्म केबिन है, और वह कोहरे भरे खेत में server racks के बीच रखा है
प्रबंधित edge SQLite: एक आरामदेह single database, जिसे ऐसे platform में लपेटा गया है जिसके भीतर आप देख नहीं सकते।

मिथक 5: मज़बूत consistency backups पर मुफ़्त अपग्रेड है

लोग चुनाव को backups (कमज़ोर, सस्ता) बनाम consensus (मज़बूत, महँगा पर हर हाल में बेहतर) के रूप में देखते हैं। यह सच नहीं है। Raft के ज़रिए मज़बूत consistency उसी उत्पाद का बेहतर संस्करण नहीं, बल्कि एक अलग उत्पाद है, और यह ऐसे लगातार टैक्स लगाती है जो backups कभी नहीं लगाते।

पहला टैक्स: throughput। दोनों का इस्तेमाल करने वाले एक practitioner ने बताया कि rqlite “एक से ज़्यादा जगह stored” की गारंटी देता है, पर एक-node database से कहीं धीमा है — यह consensus round trip है, और यह कभी नहीं जाता। दूसरा टैक्स: सूक्ष्म semantics। अगर SQL statements भेजकर replicate करते हैं, तो determinism की समस्याएँ विरासत में मिलती हैं — random() या datetime('now') जैसे functions नोड्स के बीच अलग हो सकते हैं, जब तक layer उन्हें दोबारा न लिखे, और इसीलिए कुछ implementations SQLite SQL का सिर्फ़ एक हिस्सा सपोर्ट करते हैं। इसके बजाय WAL स्तर पर replicate करें तो यह समस्या टल जाती है, पर आप SQLite के on-disk format और page layout से जुड़ जाते हैं। कोई भी layer placement बिना समझौते के नहीं आती। तीसरा टैक्स: distributed transactions सचमुच कठिन हैं। एक engineer ने एक डिज़ाइन का विकास बताया जिसमें transaction का हर statement एक अलग Raft command था — लगभग सही, पर धीमा — और फिर वह डिज़ाइन बदला गया जहाँ शुरू करने वाला node पूरा transaction स्थानीय रूप से चलाता है, उसे rollback करता है, और पूरे batch के साथ एक single commit command भेजता है। यह वह सबक है जो आप इन systems को चलाकर ही सीखते हैं।

यहाँ write path के अंतर का एक छोटा उदाहरण है, rqlite के HTTP API का उपयोग करके:

# Local SQLite: one process, one fsync, microseconds.
sqlite3 app.db "INSERT INTO kv VALUES ('flag', 'on')"
# rqlite: this HTTP call hides a Raft round trip —
# leader log append, replication to a majority, then apply.
curl -XPOST 'localhost:4001/db/execute?pretty' \
-H 'Content-Type: application/json' \
-d '["INSERT INTO kv VALUES (?, ?)", "flag", "on"]'
# The guarantee changed: the write now survives the loss
# of any single node. So did the latency budget.

और ध्यान दें कि आपको क्या नहीं मिला: read scaling। Raft log को replicate करता है ताकि followers आगे आ सकें; यह डिफ़ॉल्ट रूप से followers को strongly consistent reads देने वाला नहीं बनाता (rqlite read-consistency levels इसलिए देता है क्योंकि यह एक trade-off है)। अगर आप “distributed” की ओर इस उम्मीद में आए थे कि read capacity बढ़ेगी, तो आपको read replicas चाहिए थे, consensus नहीं।

बारह साल Raft on SQLite से वास्तव में क्या सिखाते हैं

इस कैटेगरी का इतिहास infrastructure विचारों के परिपक्व होने का असामान्य रूप से साफ़ प्रमाण है। पहले कोई एक ठोस ज़रूरत के लिए सिद्धांत-सम्मत चीज़ बनाता है (rqlite, एक असली job की असली clustering समस्या से)। फिर दूसरे उसे अपनी packaging बाधाओं के हिसाब से दोबारा बनाते हैं — Canonical का dqlite एक library है जिसे आप embed करते हैं, क्योंकि Canonical को अपने products के भीतर database चाहिए था, जबकि rqlite एक server है जिसे आप किसी भी product के साथ चलाते हैं। फिर कोई तर्क देता है कि आम स्थिति सिद्धांत-सम्मत समाधान की मान्यता से छोटी है (Litestream), सही साबित होता है, और पूरा क्षेत्र फिर से संतुलित होता है। अंत में platforms उसे लपेटकर बेचते हैं। अगर आप infrastructure में पर्याप्त समय तक काम करते हैं तो यह फ़िल्म फिर से देखेंगे — LLM agents भी अभी इसी तरह distributed systems के सबक फिर से सीख रहे हैं।

सब कुछ के बाद मेरी सीधी-सादी सिफ़ारिश: object storage पर WAL streaming के साथ single-node SQLite से शुरू करें। यह देखकर हैरानी होती है कि यह कितनी दूर तक ले जाता है, और आपकी failure कहानी एक restore है, consensus postmortem नहीं। Raft-आधारित system पर तभी जाएँ जब आप एक वाक्य में बता सकें कि कौन-सी availability आवश्यकता streaming पूरी नहीं कर सकती — और उसके साथ आने वाला write-latency और quorum operations का बिल स्वीकार करें। जब आपको उसकी SQL सतह या multi-writer throughput चाहिए, तब Postgres की ओर बढ़ें। और अगर इनमें से कोई भी फिट न हो, तो व्यापक embeddable-database दुनिया में भी विकल्प हैं, embeddable graph databases से लेकर local-first CRDT कोने तक।

मिथकों का सार: वितरित SQLite नया नहीं है, सस्ता Postgres नहीं है, एक डिज़ाइन नहीं है, प्रबंधित wrapper से सुरक्षित नहीं होता, और backups पर मुफ़्त अपग्रेड नहीं है। यह बारह साल पुराना, अच्छी तरह मैप किया गया design space है जिसमें तीन कोने हैं, और हर कोना अलग सवाल के लिए सही है। जो इंजीनियर जलते हैं, वे वही हैं जिन्होंने सवाल कभी लिखा ही नहीं।