- SQLite इंडेक्स वास्तव में डिस्क और मेमोरी में कैसे व्यवस्थित होते हैं, यह देखने के लिए B-Tree संरचना का विश्लेषण किया गया और इंडेक्स डेटा को डंप करके विज़ुअलाइज़ किया गया
- इंडेक्स Page और Cell इकाइयों से बने होते हैं; Page में दायाँ child link और Cell डेटा होता है, जबकि Cell में इंडेक्स डेटा, rowId, और बायाँ child link होता है
sqlite3_analyzerकेवल Page size, entries की संख्या, B-tree depth, और उपयोग किए गए Pages जैसी जानकारी देता है, इसलिए SQLite source में debug functions जोड़े गए- प्रयोगों में records की संख्या, ASC/DESC, expression-based index, NULL सहित UNIQUE, Partial Index, multi-column, और text·REAL·integer+text संयोजनों की तुलना की गई
- 1,000,000 records पर, insert से पहले इंडेक्स बनाने पर 3,342 Pages बने, जबकि insert के बाद बनाने पर 2,930 Pages बने; और VACUUM या REINDEX के बाद भी यह 2,930 Pages तक घट गया
SQLite इंडेक्स को सीधे देखने की वजह
- यह प्रयोग इंडेक्स की बुनियादी संरचना से आगे बढ़कर वास्तविक data structure, algorithm, और disk storage format को समझने के लिए किया गया
- लक्ष्य यह देखना था कि DBMS इंडेक्स को डिस्क और मेमोरी में कैसे स्टोर करता है, और खोज के दौरान उन तक कैसे पहुँचता है
- प्रयोग के लिए SQLite चुनने के कारण ये थे
- यह browser, mobile apps, और operating systems में व्यापक रूप से इस्तेमाल होने वाला DBMS है
- अलग server के बिना केवल client application से इसे debug करना आसान है
- MySQL या PostgreSQL की तुलना में इसका codebase छोटा है, लेकिन इंडेक्स के लिए मिलती-जुलती data structures का उपयोग करता है
- यह open source है
Page और Cell से बना B-Tree
- SQLite documentation के अनुसार इंडेक्स B-Tree संरचना में स्टोर होते हैं
- SQLite में Node के समकक्ष इकाई Page है
- Page, Cell डेटा स्टोर करता है
- Page में दाएँ child Page का link होता है
- Cell में इंडेक्स डेटा, rowId, और बाएँ child Page का link शामिल होता है
- SQLite table की हर row के पास सामान्यतः एक unique rowId होता है, जो explicit primary key न होने पर primary key जैसा काम करता है
- हर Page का आकार fixed होता है, और उसका size range 512~65,536 bytes है
- Page और Cell headers child links स्टोर करने के लिए 4 bytes का उपयोग करते हैं
- child Page number जानने के लिए header को अलग से
get4byte(...)function से पढ़ना पड़ता है
- child Page number जानने के लिए header को अलग से
- SQLite की internal structures के उदाहरण इस प्रकार हैं
MemPage: इसमें Page numberpgno, Cells की संख्याnCell, Cell index areaaCellIdx, और Page data disk image pointeraDataआदि शामिल हैंCellInfo: इसमें payload की शुरुआत को दिखाने वालाpPayloadआदि शामिल है
sqlite3_analyzer की सीमाएँ और debug functions
- sqlite3_analyzer से इंडेक्स की सामान्य जानकारी देखी जा सकती है
- उदाहरण output में Page size
4096, entries की संख्या1000, B-tree depth2, और उपयोग किए गए Pages4जैसी जानकारी शामिल होती है
- उदाहरण output में Page size
- लेकिन यह tool इंडेक्स के अंदर के Cell और payload को सीधे देखने के लिए केवल overview information देता है
- कई हफ्तों के प्रयोगों के बाद इंडेक्स analysis के लिए functions लिखे गए
- कोड:
sqlite.patch sqlite3DebugGetMemoryPayload(Mem *mem)sqlite3DebugGetCellPayloadAndRowId(BtCursor *pCur, MemPage * pPage, int cellIndex)sqlite3DebugBtreeIndexDump(BtCursor *pCur, int pageNumber)
- कोड:
- ये functions चुने गए इंडेक्स की सामग्री पढ़कर STDOUT पर प्रिंट करते हैं
- flow है
SQL query -> selected index -> stdout - output में Page number, right child Page number, Cell number, left child Page number, payload, और rowId शामिल होते हैं
- flow है
- Docker से experiment environment चलाया जा सकता है
docker run -it --rm -v "$PWD":/app/data --platform linux/x86_64 mrsuh/sqlite-index bashsh bin/dump-index.sh database.sqlite "SELECT * FROM table INDEXED BY index WHERE column=1" dump.txt
विज़ुअलाइज़ेशन तरीके में बदलाव
- शुरुआत में इंडेक्स संरचना को विज़ुअलाइज़ करने के लिए d3-org-tree का उपयोग किया गया
- जैसे-जैसे tree गहरा हुआ और हर level पर Pages बढ़े, Pages के बीच spacing समायोजित करना मुश्किल हो गया, जिससे image बहुत बड़ी और पढ़ने में कठिन हो गई
- JavaScript और CSS से इसे समायोजित करने की कोशिश की गई, लेकिन ठीक से काम नहीं किया, इसलिए कुछ समय के लिए text-based structure display पर स्विच किया गया
- text output में कुल Pages, कुल Cells, level-wise Pages·Cells की संख्या, Page जानकारी, और Cell जानकारी के साथ payload दिखाया जाता है
- बाद में PHP के ImageMagick extension का उपयोग करके design और spacing पर अधिक सूक्ष्म नियंत्रण वाली image rendering बनाई गई
- अंतिम image में ये जानकारी शामिल होती है
- ऊपर बाईं ओर इंडेक्स की general information
- हर level पर कुल Pages और Cells की संख्या
- हर Page पर Page number, right child link, first Cell और last Cell की जानकारी
- हर level में केवल कुछ Pages दिखाए जाते हैं, जिनमें पहला और आखिरी Page शामिल होता है
- root Page पहले level पर स्थित होता है
- dump से image बनाने का command यह है
php bin/console app:render-index --dumpIndexPath=dump.txt --outputImagePath=image.webp
records की संख्या से बदलता इंडेक्स का रूप
column1 INT NOT NULLtable परcolumn1 ASCइंडेक्स बनाकर और records की संख्या बदलकर संरचना देखी गई- 1 record वाला इंडेक्स 1 level, 1 Page, 1 Cell से बना होता है
- 1,000 records वाला इंडेक्स भी इसी तरह बनाकर विज़ुअलाइज़ किया गया
- 1,000,000 records वाले इंडेक्स की संरचना यह है
- 3 levels
- 2,930 Pages
- 1,000,000 Cells
- क्योंकि डेटा क्रम से जोड़ा गया था, इसलिए
rowId = 1होने परcolumn1 = 1है
sort direction और expression indexes
- समान डेटा पर
idx_ascऔरidx_descबनाकर ASC/DESC indexes की तुलना की गई - ASC इंडेक्स default sort order ASC होने के कारण पहले वाले इंडेक्स जैसा ही है
rowId=1,000,000,column1=1,000,000,payload=1,000,000वाला item सबसे दाएँ Page के आखिरी Cell में हैrowId=1,column1=1,payload=1वाला item सबसे बाएँ Page के पहले Cell में है
- DESC इंडेक्स में व्यवस्था उलटी है
rowId=1,column1=1,payload=1वाला item सबसे दाएँ Page के आखिरी Cell में हैrowId=1,000,000,column1=1,000,000,payload=1,000,000वाला item सबसे बाएँ Page के पहले Cell में है
- expression-based index expression द्वारा बनाई गई string को स्टोर करता है
- उदाहरण में JSON text से
$.timestampनिकाला गया, फिरstrftime('%Y-%m-%d %H:%M:%S', ..., 'unixepoch')से बदलकर ASC इंडेक्स बनाया गया - इससे अधिक जटिल expressions भी उपयोग किए जा सकते हैं, और इंडेक्स में केवल उनका result स्टोर होता है
- उदाहरण में JSON text से
NULL, Partial Index, multi-column
- SQLite NULL values सहित UNIQUE index को support करता है
- उदाहरण में
1, कईNULL, और1000000values डालकरCREATE UNIQUE INDEX idx ON table_test (column1 ASC)चलाया गया - विज़ुअलाइज़ किया गया इंडेक्स ऐसा दिखता है मानो केवल non-NULL values स्टोर कर रहा हो
- उदाहरण में
WHERE column1 IS NOT NULLशर्त वाला Partial Index NULL values को filter करता है- इस इंडेक्स में केवल एक Page शामिल है
- यह पिछले UNIQUE उदाहरण की तुलना में तेज़ search तक ले जाता है
- multi-column index Cell के अंदर सभी field data को क्रम से स्टोर करता है
- उदाहरण
(column1 ASC, column2 ASC)इंडेक्स का है - visualization में fields को colon
:से अलग दिखाया गया है
- उदाहरण
इंडेक्स बनाने का समय और rebuild का असर
- डेटा डालने से पहले इंडेक्स बनाने और सारा डेटा डालने के बाद इंडेक्स बनाने की तुलना की गई
- नया डेटा जुड़ने पर tree को खुद rebalance होना पड़ता है
- मौजूदा डेटा पर इंडेक्स को एक बार में बनाना कहीं अधिक efficient हो सकता है
- दोनों इंडेक्स देखने में समान लगते हैं, लेकिन कम Pages वाला दूसरा इंडेक्स तेज़ हो सकता है
- 1,000,000 Cells के आधार पर तुलना का परिणाम यह है
| वर्गीकरण | Total Pages | Total Cells |
|---|---|---|
| insert से पहले बनाया | 3342 | 1000000 |
| insert के बाद बनाया | 2930 | 1000000 |
- इसी तरह का optimization VACUUM या REINDEX से भी किया जा सकता है
VACUUMडेटा के साथ इंडेक्स और tables को फिर से बनाता हैREINDEX idxकेवल इंडेक्स को फिर से बनाता है
- उदाहरण में दोनों commands ने Page count को 3342 से 2930 तक घटा दिया
data type के अनुसार इंडेक्स storage
- text data में छोटे strings सीधे इंडेक्स Cell में स्टोर हो जाते हैं, लेकिन लंबे text को अलग से स्टोर करना पड़ता है
- उदाहरण में
text-1सेtext-1000000तक values डालकरcolumn1 ASCइंडेक्स बनाया गया - वास्तविक strings के सीधे इंडेक्स में स्टोर होने का रूप देखा जा सकता है
- उदाहरण में
- REAL data को भी इंडेक्स में स्टोर करके विज़ुअलाइज़ किया गया
- उदाहरण में
1.14,2.14, ...,1000000.14values का उपयोग किया गया
- उदाहरण में
- integer और text को साथ रखने वाला composite index भी देखा गया
- उदाहरण में
(column1 INT, column2 TEXT)table पर(column1 ASC, column2 ASC)इंडेक्स बनाया गया - integer और string, इंडेक्स बनाते समय दिए गए क्रम में उसी Cell में साथ स्टोर होते हैं
- उदाहरण में
दोबारा प्रयोग करने का तरीका और आगे का काम
- यह प्रयोग दिखाता है कि SQLite इंडेक्स कैसे संरचित होते हैं, record data मेमोरी में कैसे स्टोर होता है, और B-Tree डेटा को कैसे व्यवस्थित और access करता है
- visualization का उपयोग अलग-अलग इंडेक्स का analysis और comparison करने के लिए किया गया
- सभी उदाहरण निम्न command से दोबारा चलाए जा सकते हैं
docker run -it --rm -v "$PWD":/app/data --platform linux/x86_64 mrsuh/sqlite-index bashsh bin/test-index.sh
- कोड और उदाहरण
mrsuh/sqlite-indexमें हैं - अगला काम index-based search visualization और कुछ SQL queries की पड़ताल है
1 टिप्पणियां
Hacker News टिप्पणियाँ
कहा गया कि SQLite table की हर row में डिफ़ॉल्ट रूप से एक unique rowId होता है, और अगर explicit primary key न हो तो वह primary key की तरह काम करता है, लेकिन असल में primary key होने पर भी rowid का इस्तेमाल होता है
WITHOUT ROWIDtable के primary key index को visualize करके देखना अच्छा रहेगा। ऐसे index खास तौर पर दिलचस्प होते हैंभले ही दो index एक जैसे दिखें, दूसरे index में page count कम है इसका तुरंत यह मतलब नहीं कि वह तेज़ होगा। अहम चीज़ tree की height है, और उसके बाद यह कि index में value ढूँढने के बाद बाकी data अलग table (rowid) से पढ़ना पड़ता है या
WITHOUT ROWIDकी तरह data वहीं मौजूद होता है। खासकरwhere 50 <= col <= 100जैसी range query में फर्क बड़ा होता हैINTEGER PRIMARY KEYबनाते हैं तो SQLite इसके बजाय उसी का इस्तेमाल करता है [1][1]: https://sqlite.org/rowidtable.html
SQLite अपने लगभग हर processing तरीके में काफी अलग है, और खासकर query processing में तो और भी
SQLite performance की बजाय simplicity को प्राथमिकता देता है, इसलिए मैंने जिन दूसरे databases के साथ काम किया है उनसे अलग तरीके से चीज़ें implement करता है। SQLite दूसरे databases से मुकाबला करने के बजाय persistent storage के लिए JSON/XML files से मुकाबला करता है। इसलिए SQLite की implementation देखकर यह जानने को बहुत कुछ नहीं मिलता कि असली databases वही काम कैसे करते हैं
इसका मतलब है कि requirements काफी अलग हैं, लेकिन इसका उपयोग सिर्फ JSON/XML file replacement तक सीमित नहीं है
Website इतनी पढ़ने लायक है कि सच में पढ़ने का मन करता है
“indexes” verb “to index” का third-person singular present form भी है और “index” का plural noun form भी। वहीं “indices” पारंपरिक plural form है, और खासकर math/science context में ज्यादा इस्तेमाल होता है
सामान्य English में “indexes” आम है, लेकिन tech field में linguistic precision के लिए indices को प्राथमिकता देने के मामले मिलते हैं। ऐसे context में “indices” इस्तेमाल करने से indexing action और index के plural form में फर्क साफ होता है, जिससे clarity बढ़ती है
Finland में मैंने देखा है कि plural के लिए “time series” और singular के लिए “time serie” इस्तेमाल किया जाता है
सभी प्रमुख relational database management systems indexes शब्द का इस्तेमाल करते हैं
अच्छा होगा अगर यह भी देखा जाए कि PostgreSQL वही काम कैसे करता है। तुलना करके सीखने लायक बहुत कुछ होगा
कम मेहनत में अलग-अलग layouts देखने के लिए yEd के लिए TGF output करवाना भी अच्छा रहेगा