- Bluesky atproto के PDS refactoring PR #1705 में PDS को single-tenant SQLite datastore इस्तेमाल करने के लिए बदला गया है, और हर user के repo तथा private account state को उसकी अपनी SQLite file में स्टोर करने के लिए परिवर्तन किया गया है
- user DB को
/${dbDirectory}/${sha256Hex(did).slice(0,2)}/${did}path structure में स्टोर किया जाता है, और हर repo की signing key को उसी SQLite file के साथ रखा जाता है - मौजूदा user data access abstraction को ActorStore से बदला गया है, और क्योंकि SQLite concurrent transaction को support नहीं करता, write operation के लिए store और transaction को स्पष्ट रूप से जोड़ना पड़ता है
- खुले DB file handles और signing keys को LRUCache से manage किया जाता है; अधिकतम 30k खुले file handles और 30k keys memory में रखे जाते हैं, और cache से DB हटने पर file handle बंद कर दिया जाता है
- service state management के लिए अलग 3 SQLite DB जोड़े गए हैं, जो WAL mode में चलते हैं ताकि concurrent read और streaming replication संभव हो सके; PDS distribution में Litestream या इसी तरह के tool को शामिल करने की योजना है
PR के मुख्य बदलाव
- PR #1705, PDS को single-tenant SQLite datastore आधारित संरचना में refactor करता है
- हर user की अपनी dedicated SQLite file होती है, जिसमें उस user का repo और private account state स्टोर होता है
- user DB, DID hash का उपयोग करने वाले hierarchical path में स्टोर किया जाता है
- path format:
/${dbDirectory}/${sha256Hex(did).slice(0,2)}/${did}
- path format:
- हर repo की repo signing key को SQLite file वाली ही location पर स्टोर किया जाता है
ActorStore और transaction model
- user data access abstraction को पुराने “services” से बदलकर ActorStore किया गया है
- ActorStore का मुख्य अंतर यह है कि read और write के लिए classes अलग हैं
- क्योंकि SQLite concurrent transaction को support नहीं करता, write operation करने के लिए store और transaction को स्पष्ट रूप से जोड़ना होता है
- commit log में reader और transactor का rework, actor store transaction race handling, और store interface cleanup जैसी चीजें शामिल हैं
cache और file handle management
- signing keys और database के लिए LRUCache बनाए रखा जाता है
- configured limits इस प्रकार हैं
- खुले file handles की अधिकतम संख्या 30k
- memory में रखी जाने वाली keys की अधिकतम संख्या 30k
- जब database cache से बाहर हो जाता है, तो file handle बंद कर दिया जाता है
- संबंधित commits में
actor store in lru cache,fix open handlesशामिल हैं
service state के लिए 3 SQLite DB
- per-user DB के अलावा, service state management के लिए 3 अलग SQLite databases जोड़े गए हैं
- service DB: account information, invite codes, refresh token आदि को manage करता है
- did cache DB: DID resolution caching के लिए केवल एक single table शामिल करता है
- sequencer DB: एक service के सभी repo updates के order को manage करने के लिए केवल एक single table शामिल करता है
- हर SQLite file WAL mode में चलती है
- WAL mode का उद्देश्य concurrent read और streaming replication को संभव बनाना है
- PDS distribution में Litestream या इसी तरह के tool को शामिल करने की योजना है
review और merge status
- यह PR कुल 143 commits से बना था और
pds-sqlite-refactorbranch सेpds-v2branch में merge हुआ - merge date 1 नवंबर 2023 थी, और merge commit
8449cebहै - reviewer devinivy ने कई notes और comments छोड़ने के बाद बदलावों को approve किया
- devinivy ने इस refactoring को “कई शानदार simplifications” वाला और कुल मिलाकर ज्यादा सुव्यवस्थित बताया
- merge के बाद
pds-sqlite-refactorbranch हटा दी गई
बाद का सवाल
- 28 फ़रवरी 2025 को, npetrangelo ने इस PR में हुए बदलावों के पैमाने को देखते हुए पुराने Postgres architecture और इस PR में लाई गई SQLite architecture के बीच trade-offs का सारांश मांगा
- दिए गए मूल पाठ में इस सवाल पर Bluesky की ओर से कोई जवाब शामिल नहीं है
1 टिप्पणियां
Hacker News की रायें
SQLite मुझे पसंद है, लेकिन हर tenant के लिए अलग schema या database रखने का तरीका आम तौर पर काफी मुश्किलों भरा होता है
Shared instance में row-level security (RLS) इस्तेमाल करने पर migration fail होने पर पूरा rollback संभव है, लेकिन per-tenant schema में अगर अनपेक्षित data की वजह से data migration fail हो जाए, तो कारण मिलने तक users अलग-अलग schema versions पर अटके रह जाते हैं
Sharding के scale तक पहुंचने पर वैसे भी कुछ ऐसा हो सकता है, लेकिन उससे पहले तक single database सबसे आसान है, और बाद में data merge करना या resource ownership को atomically move करना भी पड़ सकता है
मैं इस configuration के खिलाफ नहीं हूं और इसके use cases हैं, लेकिन हमारी company में हम per-tenant schema से पूरी रफ्तार से बाहर निकल रहे हैं। सही investment न किया जाए तो समस्याएं बहुत ज्यादा हैं, और जब idea पहली बार आता है तो आम तौर पर लोग उसके लिए तैयार नहीं होते
मजेदार बात यह है कि करीब 10 साल पहले app per-tenant SQLite से शुरू हुआ, फिर PostgreSQL के per-tenant schema पर गया, और अब RLS वाले single schema की तरफ जा रहा है—यानी पूरी तरह उलटी दिशा में
Production में बहुत बड़े database संभालने के अनुभव से कहूं तो मैं फिर कभी ऐसा नहीं करना चाहूंगा
Load पर्याप्त बड़ा हो जाए तो हर बदलाव जोखिम भरा हो जाता है, क्योंकि performance के हर extreme case को पूरी तरह test नहीं किया जा सकता
Free tier user द्वारा बिना index वाले code path को खोजकर production बिगाड़ देना भी एक आम pattern है
Data migration fail होने से कुछ users का अलग schema version पर रह जाना शायद कोई बड़ी समस्या न हो
अगर service इतनी बड़ी और complex है, तो आम तौर पर schema upgrade staged तरीके से किया जाता है: 1. code को future schema के compatible बनाना, 2. data migrate करना, 3. पुराने schema support को हटाना
इसलिए आम तौर पर phase 1 और 2 के बीच वाली state में लंबे समय तक चलना भी safe होना चाहिए। बेशक नए bugs अपवाद हैं, लेकिन operations के नजरिये से, जब तक ऐसी प्रक्रिया अपनाई जाती है, migration की intermediate state में लौटने वाला system भी मुझे ठीक लगता है
अगर product customers 100 से कम हैं, तो हर user का अलग schema version पर होना उल्टा अच्छा भी हो सकता है
हर customer का upgrade schedule और requirements अलग हो सकते हैं, और मैं ऐसे businesses भी जानता हूं जो कुछ customers के लिए custom work करते हैं, जिससे practically वही code भी नहीं चलता
आखिरकार यह business structure पर निर्भर करता है
निष्पक्ष रूप से कहें तो 10 साल पहले RLS अभी मौजूद नहीं था। यह PostgreSQL 9.5 में 2016 में आया था
https://blog.turso.tech/introducing-embedded-replicas-deploy...
https://electric-sql.com/
“SQLite concurrent transactions support नहीं करता” से क्या मतलब है, समझ नहीं आ रहा
जब तक
.dbfile को UNC या NFS जैसे file share के जरिए access नहीं किया जा रहा, मेरी जानकारी में यह support करता है: https://www.sqlite.org/wal.htmlमैंने इसे same machine के कई threads/processes से database पढ़ने और update करने के लिए इस्तेमाल किया है, और अगर consistent view चाहिए या transaction लंबे समय तक hold नहीं करना चाहते, तो sqlite backup API से snapshot भी संभव है
हो सकता है मैं कुछ miss कर रहा हूं, और मैंने कई साल से SQLite को छुआ नहीं है, इसलिए पक्का नहीं हूं
नहीं, ऐसा नहीं था। मेरी गलतफहमी थी। असल में यह multiple reads, single write के ज्यादा करीब है
लगता है मैं अब तक यही मानकर चल रहा था और पर्याप्त बारीकी से check नहीं किया। हालांकि SQLite से बने ज्यादातर databases में writes से ज्यादा reads थे
सुधार कर रहा हूं
इंतजार करेंगे तो hctree [1] stable हो जाएगा, और पारंपरिक backend mechanism तथा नए implement किए गए concurrency-support backend में से चुनने का विकल्प मिलेगा
[1] https://sqlite.org/hctree/doc/hctree/doc/hctree/index.html
Documentation के मुताबिक writer सिर्फ WAL file के अंत में नया content append करता है, इसलिए reads और writes साथ-साथ संभव हैं, लेकिन WAL file केवल एक है, इसलिए एक समय में लिख सकने वाला writer सिर्फ एक होता है
Original post में शायद यही मतलब था कि update operations sequentially execute होने चाहिए
Traffic कम हो तो यह काम करता है, लेकिन transaction बड़े हों या concurrent writes की संख्या बढ़े तो WAL on करने पर भी किसी point पर database locked समस्या आती है
Application level पर कुछ हद तक workaround किया जा सकता है, लेकिन आम तौर पर अगर आप उस point पर पहुंच गए हैं तो दूसरा database backend गंभीरता से consider करना चाहिए
कम से कम पिछली बार जब मैंने check किया था, इसका मतलब शायद यह है कि row-level locking नहीं है, और table-level locking भी बहुत limited है
Documentation के हिसाब से writer अभी भी पूरे database पर lock लेता है
दिलचस्प है, और 1 user और 1 database को 1:1 रखने की strategy मुझे पसंद है
हालांकि मुझे यह जानना है कि users के बीच aggregation वाली data needs को कैसे handle करते हैं। अगर मैं किसी दूसरे user को subscribe करता हूं और वह post करता है, तो मेरा database नए post से कैसे update होता है; या फिर क्या यह सिर्फ profile data या follow relationships जैसे persistent data के लिए है और feed जैसा interaction data अलग तरीके से handle होता है
यह भी अच्छा है कि “connection pooling” बस open handles की संख्या को LRU cache से limit करने जैसा है। हर DB connection single-threaded है, इसलिए concurrency को connection level पर नहीं बल्कि tenancy level पर handle किया जाता है—यह भी दिलचस्प है
इसके ऊपर database-level rate limiting आसानी से जोड़कर किसी खास user के abuse को भी रोका जा सकता है
यह भी जानना चाहूंगा कि क्या arbitrary number of databases के लिए Litestream configure करने का कोई आसान तरीका है
Server पर SQLite/Litestream adoption बढ़ता देखना हमेशा अच्छा लगता है। हम भी नई apps बनाते समय इसका इस्तेमाल कर रहे हैं
SQLite + Litestream tenant database के लिए बेहतर choice है, और महंगे cloud managed databases की तुलना में S3/R2 पर replicate और backup करना काफी सस्ता है [1]
Azure के SQLServer की तुलना में 3900% तक सस्ता
[1] https://docs.servicestack.net/ormlite/litestream
3900% सस्ता होने का मतलब क्या है, समझ नहीं आया
मेरी पिछली fintech नौकरी में कंपनी ग्राहक खातों को encrypted sqlite3 files के रूप में blob storage में सेव करती थी, और access pattern के लिए यह काफी अच्छी तरह फिट बैठता था
ऊपर-ऊपर से यह सबसे खराब और डरावने विकल्पों के मिश्रण जैसा दिखता है
काश कोई वास्तविक आंकड़ों के साथ इसके फायदे समझाते हुए और संभावित कमियों का विश्लेषण करते हुए एक अच्छा लेख लिखे। ठीक से सीखने पर यह वाकई दिलचस्प विषय हो सकता है
ऊपर-ऊपर से, खासकर अगर मानें कि हम ऐसा distributed system बना रहे हैं जिसे कई ऐसे users run और deploy करेंगे जो professional system administrators नहीं हैं, तो यह काफी reasonable choice लगती है
यहां लक्ष्य भी शायद यही होना चाहिए, और मुझे लगता है कि किसी अतिरिक्त database या दूसरे server को setup, configure और manage करने की जरूरत से बचना design goal होगा
काश Bluesky को बेहतर जानने वाला कोई समझा दे कि SQLite में कौन-सा data stored होता है और कौन-सा नहीं
मैं मानकर चल रहा हूं कि users के बीच messages जैसी चीजें तो नहीं होंगी
email के बारे में सोचिए। अगर आप email भेजते हैं और पांच लोगों को CC में डालते हैं, तो सात लोग अपने-अपने email server पर उसी email की copy store करते हैं
यानी ऐसा structure नहीं है जिसमें एक central database में एक email हो और बाकी लोग उसे reference करें
relational database sharding भी मूल रूप से इसी तरह काम करती है
इस तरह की data denormalization application के scale होने पर, खासकर write के मुकाबले read ratio ज्यादा वाले many-to-many applications में, लगभग जरूरी हो जाती है
अगर write के मुकाबले read कम हैं, तो single master और कई slave relational databases वाला structure भी हैरान करने वाली मात्रा में requests और data handle कर सकता है
अभी Bluesky वास्तव में लगभग एकमात्र PDS को खुद host कर रहा है, लेकिन अंतिम लक्ष्य यह है कि हर end user का अपना PDS हो
Inrupt/SOLID इस concept को “pod” कहते हैं
असल में कल ही दूसरा production PDS onboard किया गया, इसलिए progress हो रही है
इसमें केवल public messages हैं जो पूरी दुनिया में broadcast होते हैं
direct messages की कोई योजना है या नहीं, यह मैंने अलग से investigate नहीं किया
users को sha256 hash करके दो-अक्षर वाले target directories में बांटने की वजह क्या होगी?
md5 तो काफी तेज है और वही समस्या हल नहीं करता क्या?
“unsafe hash क्यों इस्तेमाल किया” जैसे सवाल का जवाब नहीं देना पड़ता, और security issue की एक पूरी category की संभावना को हटाना या कम करना ज्यादा valuable है
या फिर मेरी तरह वे भी कंपनी के security tools में दबे हुए हों, और md5 के हर इस्तेमाल के लिए अलग exception नहीं बनाना चाहते हों
अगर secure hash की जरूरत नहीं है, तो बहुत सारे तेज non-cryptographic hashes मौजूद हैं
क्या Bluesky अब भी invite-only है?
backend और abuse prevention के लिहाज से system को scale करते समय growth को limit करने का तरीका है
developers के लिए dedicated waitlist है, और access काफी जल्दी मिल सकता है: https://atproto.com/blog/call-for-developers