SQLite चलाते हुए सीखी गई कुछ नई बातें
(jvns.ca)- छोटे वेबसाइटों के production environment में भी SQLite इस्तेमाल किया जा सकता है, लेकिन Django ORM के जिम्मे अधिक काम आते ही database की operational complexity महसूस हुई
- 4,000 rows वाली table में FTS5 search में 5 सेकंड लग रहे थे, लेकिन
ANALYZEचलाने के बाद यह लगभग 0.05 सेकंड हो गया; इससे पता चला कि query planner statistics performance पर बड़ा असर डाल सकते हैं - अगर bulk
DELETE5 सेकंड से ज्यादा चले, तो दूसरे write operations भी timeout हो सकते हैं और worker व VM तक बंद हो सकते हैं, इसलिए cleanup jobs को छोटे batches में बांटकर process किया गया - Backup के लिए
VACUUM INTOऔर restic इस्तेमाल किए, लेकिन OOM termination का सामना हुआ; अधिक efficient incremental backup के लिए Litestream भी test किया जा रहा है - Tables को कई SQLite files में अलग किया जा सकता है, और Mess with DNS 2022 से 4 वर्षों तक SQLite पर चला; इसे project के लिए उपयुक्त विकल्प माना गया
Django साइट पर SQLite चलाना
- छोटे production sites में भी SQLite इस्तेमाल किया जा सकता है, इस बारे में कई लेखों को संदर्भ के रूप में लेकर इसे Django site के database के रूप में अपनाया गया
- SQLite भी operational knowledge मांगने वाला complex database है, और इस project में Django ORM के जरिए इसे पहले से अधिक काम सौंपे जा रहे हैं
- कई recommendations के अनुसार शुरुआत से ही WAL mode enable करके operate किया गया
- यह SQLite इस्तेमाल करने वाली चौथी website है
ANALYZE ने search performance कैसे बदली
- 4,000 rows वाली table पर SQLite FTS5 का उपयोग करने वाली full-text search query में 5 सेकंड लग रहे थे
ANALYZEचलाने पर वही query लगभग 0.05 सेकंड में पूरी होने लगी, इसलिए आगे investigation की जरूरत ही नहीं रही- ठीक-ठीक कौन-सी query plan issue थी, यह confirm नहीं हो पाया; अनुमान है कि यह अनजानी quadratic time complexity जैसी स्थिति रही होगी
ANALYZEtable row counts आदि सहित statistics बनाता है, जिससे query planner बेहतर choices कर पाता है- अभी query plans पढ़ने का तरीका सीखा नहीं गया है
Data cleanup और single write constraint
- django-tasks-db के completed tasks जैसे अनावश्यक rows को bulk में delete करते समय cascading problems आईं
- कई rows process करने वाला cleanup command 5 सेकंड से ज्यादा चला
- इस दौरान दूसरे workers के database writes configured 5-second limit से आगे निकलकर timeout हो गए
- Write failure वाले workers crash हुए और VM भी बंद हो गई
DELETEधीमा होने का exact कारण confirm नहीं हुआ; यह संभावना भी देखी जा रही है कि transaction के अंदर बहुत सा Python code चल रहा हो- अभी cleanup work को छोटे batches में split किया गया है ताकि हर database query 5 सेकंड से ज्यादा न चले
- इस अनुभव से यह समझ आया कि Postgres जैसे databases की जरूरत क्यों पड़ती है, जो कई write operations को साथ में handle कर सकते हैं
- आगे ऐसे कामों के दौरान site को down कर scheduled maintenance करने का विकल्प भी consider किया जा रहा है, लेकिन workflow अभी तैयार नहीं है
ORM queries और मौजूदा data size
- अब तक Django ORM से desired queries बनाते समय performance अलग से check नहीं की गई, लेकिन
ANALYZEissue के अलावा ज्यादातर चीजें ठीक से चलीं - Database लगभग 10,000 rows का है और छोटा है; आगे भी इसका छोटा ही रहने की उम्मीद है
SQLite backup method
- SQLite backup के लिए restic और Litestream, दोनों तरीके आजमाए गए
- Backup jobs को आमतौर पर dead man’s switch से monitor किया जाता है, लेकिन actual restore test अभी किया गया प्रतीत नहीं होता
-
restic का इस्तेमाल कर backup
VACUUM INTOसे database की copy बनाई, उसे gzip से compress किया और फिर S3 पर upload किया- restic से backup creation, snapshot check, पुराने backups delete करना और cleanup किया गया
- Backup कभी-कभी OOM से terminate हो जाता था, जिससे lock बच जाता था और
restic unlockभी चलाना पड़ता था
sqlite3 /data/calendar.db "VACUUM INTO '/tmp/calendar.sqlite'" gzip /tmp/calendar.sqlite restic -r s3://s3.amazonaws.com/some_bucket/ unlock restic -r s3://s3.amazonaws.com/some_bucket/ backup /tmp/calendar.sqlite.gz restic -r s3://s3.amazonaws.com/some_bucket/ snapshots restic -r s3://s3.amazonaws.com/some_bucket/ forget -l 1 -H 6 -d 2 -w 2 -m 2 -y 2 restic -r s3://s3.amazonaws.com/some_bucket/ prune -
Litestream का इस्तेमाल कर incremental backup
- restic backup के OOM termination से बचने और incremental backup की efficiency test करने के लिए Litestream इस्तेमाल करना शुरू किया गया
- config file लिखने के बाद नीचे दिए command से replication चलाया गया
litestream replicate -config litestream.yml- Database history को कुछ समय तक रखने के लिए
retention: 400hset किया गया, लेकिन यह इच्छित तरीके से काम कर रहा है या नहीं, अभी confirm नहीं हुआ - Backup AWS पर हो रहा है, लेकिन console में credentials बनाना झंझट भरा है, इसलिए आगे किसी दूसरे S3-compatible service पर move करने का विकल्प भी consider किया जा रहा है
कई SQLite database files इस्तेमाल करना
- मौजूदा project में केवल एक database इस्तेमाल हो रहा है, लेकिन Mess with DNS में जिन tables को साथ रखना जरूरी नहीं था, उन्हें 3 database files में split किया गया
- माना गया कि इस तरह का separation operations में मददगार रहा
- Mess with DNS 2022 से 4 वर्षों तक SQLite पर चला, और उस project में Postgres से SQLite पर जाना अच्छा फैसला था
Operations के दौरान देर से पता चली basic features
- Web project में SQLite पहली बार 2022 में इस्तेमाल किया गया था, लेकिन
ANALYZEके बारे में इस बार पहली बार पता चला - इस्तेमाल की जा रही technology की basic features भी actual operations के दौरान कई वर्षों में लगातार सीखने को मिलती हैं
1 टिप्पणियां
Hacker News की रायें
SQLite का
.expertmode इस्तेमाल करने से query plan पढ़ना सीखने का दिन थोड़ा टाला जा सकता है: https://www.sqlite.org/cli.html#index_recommendations_sqlite...SELECT * FROM x1 WHERE a=? AND b>?;का विश्लेषण करने पर यहCREATE INDEX x1_idx_000123a7 ON x1(a, b);जैसा सुझाया गया index बताता है, और उसे बनाने के बाद दोबारा विश्लेषण करने पर दिखाता है कि नए index की जरूरत नहीं हैPostgres जैसे “असली” databases में भी cleanup jobs को आम तौर पर छोटे batches में बाँटना सुझाया जाता है। बस छोटे scale पर यह बात कम दिखती है कि वह काम inefficient है, इसलिए मूल लेख का approach उम्मीद से ज्यादा सही था
बार-बार commit करने से मदद मिलती है, लेकिन बड़े databases की regular cleanup में partitioning सबसे असरदार रही। सबसे पुराना partition delete करने पर काम लगभग तुरंत खत्म हो जाता है
हालांकि “workers database में लिख नहीं पाते, crash होते हैं और VM बंद हो जाता है” में VM क्यों बंद होता है, यह स्पष्ट नहीं है। यहाँ VM से लगता है कि operating system चलाने वाली virtual machine का मतलब है
UPDATEयाDELETEको batches में चलाना पड़ता था। क्योंकि एक ही query से करने पर updated लाखों rows को सभी replicas पर एक साथ भेजना पड़ता हैEXPLAIN QUERY PLANजैसा दिखता है: https://sqlite.org/eqp.htmlसामान्य
EXPLAINअक्सर जरूरत से ज्यादा verbose bytecode output करता है, लेकिनEXPLAIN QUERY PLANsummarized plan दिखाता हैdatabase संभालने वाले के नाते इसे पढ़ना खीझ पैदा करने वाला था, और मन हुआ कि खुद वजह ढूँढकर ठीक करूँ। अगर table में सिर्फ 10 हजार rows हैं, तो full table scan भी बहुत तेज होना चाहिए, और अगर SQLite उसी physical server पर process के अंदर चल रहा हो तो और भी ज्यादा
दिमाग में आया साफ solution था: “index बनाइए।” slow delete संभवतः ORM users को internal database interactions समझने से पहले अक्सर होने वाली classic N+1 problem लगती है, और उम्मीद है Julia follow-up post लिखेंगी
AWS console में credentials बनाना इतना झंझट वाला था कि कुछ साल पहले मैंने सिर्फ उसी समस्या को हल करने वाला tool बनाया था
uvx s3-credentials create my-existing-s3-bucketयह command सिर्फ उस bucket तक सीमित read-write credentials output करती है।
--read-only,--write-onlyसे permissions को और सीमित किया जा सकता है, या--prefix foo/barसे सिर्फ किसी खास prefix से शुरू होने वाली keys को read और write कराया जा सकता हैRestic और Cloudflare R2 के साथ भी इस्तेमाल करके देखा, और यह बहुत अच्छी तरह काम किया
LLM के दौर में Julia की writing की और भी ज्यादा कद्र होने लगी है। ईमानदार खोज की प्रक्रिया उन auto-generated लेखों का antidote है जो आत्मविश्वास से सब कुछ जानने का दिखावा करते हैं
backup इस तरह चलाते हैं:
OUT="${i}.sql.zst"PART="${OUT}.part"sqlite3 -readonly "${i}" .dump | zstd --fast --rsyncable -v -o "${PART}" -mv "${PART}" "${OUT}"अगर writers WAL इस्तेमाल करें तो यह उन्हें block नहीं करता, और अच्छी compression ratio के साथ sync-friendly dump बनता है। Home Assistant database 1.8GB है, लेकिन compressed dump 286MB है, और अनुमान है कि रोज बनने वाली content का करीब 90% समान रहेगा
यह भी जानना चाहूँगा कि database इतना बड़ा लंबे समय तक रखे गए time-series history की वजह से हुआ या नहीं
VACUUM INTO,.backup,sqlite3_rsync, Litestream भी writers को block नहीं करते।.backupbackup API इस्तेमाल करता है.dumpकी वजह से एक बार lock लग गया था, फिर.backupपर switch कर दिया। फिर भी.partमें लिखकरmvकरने का तरीका साफ-सुथरा हैमुझे लगता है कि लेख में असल सामग्री नहीं है, क्योंकि इसमें बस “और जांच नहीं की”, “सबसे अच्छा अनुमान है”, “शायद दूसरी चीज़ें भी?”, “शायद transaction के अंदर काफी Python code चलता हो” जैसी अभिव्यक्तियां हैं। ठीक से पता लगाए या जांच किए बिना कभी-कभी गलत अनुमान तक लगाए गए हैं
Debian user के तौर पर Linux से जुड़े search results में Ubuntu forums दिखें तो उन्हें न खोलने की वजह भी यही है कि उनमें गलत अनुमान बहुत ज्यादा होते हैं। इसके उलट Arch, Debian से काफी अलग है, लेकिन जानकार लोगों द्वारा लिखी गई Arch Wiki को मैं आम तौर पर देखता हूं
यह लेख SQLite के दुनिया के सबसे बड़े विशेषज्ञ की व्याख्या होने का दिखावा नहीं करता, और शीर्षक से ही “SQLite चलाने के बारे में कुछ बातें सीखना” कहकर अपेक्षाएं साफ कर देता है
Julia के लेखन में यह संदेश लगातार दिखता है कि सब कुछ जानना या जानने का दिखावा करना जरूरी नहीं; आसान तरीकों से समस्या को समझते हुए ज्ञान बनाया जा सकता है। अब तक जो पता चला है उसे जितना हो सके साफ तौर पर साझा करना अच्छा रवैया है
मैंने भी कल एक feature बनाते हुए 2 programming languages, 2 build systems, एक cloud provider, secret management tool, दो languages में फैला जटिल client-server communication framework, version control system, editor और CI tool इस्तेमाल किए। इसमें operating system और runtime versions, database, reverse proxy, cache और domain logic तक में भी गहराई से जाया जा सकता है
सामने आने वाले हर विषय में गहराई तक उतरेंगे तो कोई काम पूरा नहीं होगा, इसलिए लेखक की तरह किस समस्या पर focus करना है, यह चुनना पड़ता है
DELETEवाली समस्या को batch deletion, batches के बीच delay, औरSELECTसेrowidपहले से load करने के तरीके से आसानी से कम किया जा सकता है।SELECTblocking नहीं करताअगर data उसी table में क्रम से जोड़ा गया था, तो file में भी लगभग उसी क्रम में stored होने की संभावना ज्यादा है, इसलिए उसी क्रम या उल्टे क्रम में delete करने से यह तेज हो सकता है। storage medium और दूसरी conditions के हिसाब से result बदल सकता है
SELECTको replica पर भेजा जा सकता था, और row filtering की वजह से index memory pressure database के CPU और buffer cache पर बड़ा बोझ डाल रहा थाअगर partition pruning जैसे तरीकों से गैर-जरूरी data को main processing path से बाहर रखना मुश्किल हो, तो यह बहुत शक्तिशाली strategy है
अभी की परिचित level या काम की जरूरत से database में थोड़ा और गहराई तक जाना अब भी skill बढ़ाने का अच्छा तरीका है। कई web developers database tools के सामने अटक जाते हैं, और मैं भी K8s जैसी कुछ operations technologies में इसी तरह अटकता हूं
SQL कैसे disk से data पढ़ने और लिखने की operations में बदलता है, यह जानना इस intuition के लिए बहुत मददगार है कि कौन सा approach ठीक रहेगा। database का locking model भी साथ में समझना चाहिए
यह जानकारी होने पर Postgres में simple
COUNTतक जल्दी खत्म न हो तो घबराने की संभावना कम हो जाती हैsqlite_stat1औरsqlite_stat4में index value distribution से जुड़ी कई statistics store होती हैं, और query planner इन्हीं के आधार पर index की selectivity और usefulness का अनुमान लगाता हैsqlite_stat1index में records की संख्या और प्रति value औसत records की संख्या ही देता है, और enable करने परsqlite_stat4histogram data भी store करता हैLitestream बहुत दिलचस्प है, और मैं इसे S3 को backend के तौर पर जोड़कर चलाने में सफल रहा। SQLite इस्तेमाल करने वाली अनगिनत apps को file system state पर लगभग निर्भर न रहने वाला बनाया जा सकता है
S3 की state manage करना कहीं आसान है, और backup व synchronization भी provider संभाल देता है