- हाल ही में Alex Petrov की Database Internals पढ़ते समय DBMS storage engine के implementation तरीके, खासकर B-Tree data structure optimization से जुड़ी बातें सामने आईं
- यूनिवर्सिटी में B-Tree सीखते समय इसे बस “बेहतर BST” की तरह समझा था, इसलिए यह वास्तव में क्यों इस्तेमाल होता है, यह बात स्पष्ट नहीं हुई थी
- disk I/O को ध्यान में रखते हुए बड़े पैमाने के डेटा को स्टोर करने और तेज़ी से खोजने के लिए B-Tree संरचना उपयुक्त है
- यह लेख बताता है कि B-Tree की ज़रूरत क्यों पड़ती है, यह कैसे काम करता है, और वास्तविक implementation में किस तरह के optimization संभव हैं
- एक node में बहुत-सी keys इकट्ठा करके disk access की संख्या कम करना इसकी प्रमुख विशेषताओं में से एक है
डिस्क के कारण उत्पन्न सीमाएँ
- यह मानकर चला गया है कि पूरा डेटा memory में फिट नहीं होता
- disk एक बार में page unit के आधार पर read और write करती है
- memory access की तुलना में disk access बहुत धीमा होता है
- इसलिए data structure में data को page-केंद्रित तरीके से रखना और कम-से-कम disk access के साथ अधिक-से-अधिक key comparisons करना ज़रूरी होता है
- अगर BST को ज्यों-का-त्यों disk में स्टोर किया जाए, तो nodes बिखरे रहने के कारण search count जितनी ही disk accesses बढ़ने की समस्या आती है
- B-Tree एक node में कई keys रखता है, ताकि disk को एक बार पढ़कर कई keys की तुलना की जा सके
Slot page
- disk पर data रखते समय उसे “page” unit में manage किया जाता है
- हर page में header, variable-length data रखने वाले cells, और cell location information स्टोर करने वाला offset pointer array होता है
- slot page संरचना का फायदा यह है कि key size variable होने पर भी rearrangement का बोझ बढ़ाए बिना sorted order बनाए रखा जा सकता है
- key delete या insert करते समय वास्तविक data को हिलाने की बजाय सिर्फ pointers को rearrange करना कहीं अधिक efficient होता है
- उदाहरण के लिए, SQLite ऐसे page structure के भीतर free list रखता है और deleted cell space को reuse करता है
B-Tree lookup
- B-Tree lookup algorithm सरल है:
- root node से शुरू करें
- वर्तमान node की separator key देखकर उस child node को खोजें जिसमें search key होने की संभावना है
- tree को recursively traverse करें
- यदि search key वाला leaf node मिल जाए तो काम पूरा, नहीं तो उसे मौजूद नहीं माना जाता
- internal nodes में actual data की बजाय केवल separator keys हो सकती हैं, और आम तौर पर actual data सिर्फ leaf nodes में स्टोर होता है
- node के भीतर keys को efficiently खोजने के लिए, हर node की keys sorted state में रखी जाती हैं ताकि binary search का उपयोग किया जा सके
Separator key compression
- internal node की separator key के लिए पूरी actual key रखना ज़रूरी नहीं, इतना होना पर्याप्त है कि वह ranges को अलग कर सके
- उदाहरण के लिए 0xAAAAAA और 0xBBBBBB के बीच फ़र्क करने के लिए ज़रूरी नहीं कि पूरा 0xBBBBBB स्टोर किया जाए; एक छोटा prefix भी काफी हो सकता है
- जिन databases में key length बड़ी होती है, वहाँ ऐसा compression storage space में बड़ी बचत दे सकता है
- separator key compression के अलावा prefix और suffix को प्रभावी ढंग से छोटा करने की रणनीतियाँ भी होती हैं
- क्योंकि leaf nodes की संख्या कहीं अधिक होती है, इसलिए कुछ लोगों का मानना है कि prefix compression performance में और भी बड़ा योगदान देता है
Overflow pages
- जब किसी node में इतनी अधिक keys हो जाएँ कि वे एक page में समा न सकें, तब overflow pages का उपयोग किया जाता है
- overflow page में पूरी key जस-की-तस ले जाने की बजाय, node में उसका छोटा prefix छोड़ा जाता है और बाकी हिस्सा अलग स्टोर किया जाता है
- इससे key existence या range query के समय पहले node में मौजूद prefix को देखा जाता है, और केवल ज़रूरत पड़ने पर ही overflow page पढ़ा जाता है
- यानी अतिरिक्त pages allocate करने के बावजूद कुल search cost कम की जाती है
Sibling pointers
- कुछ implementations में nodes के बीच बाएँ और दाएँ sibling nodes के pointers स्टोर किए जाते हैं
- इससे range lookup के दौरान एक leaf node से सीधे sibling node पर जाते हुए लगातार keys को तेज़ी से traverse किया जा सकता है
- अगर sibling pointers न हों, तो बार-बार parent node तक ऊपर जाकर फिर नीचे उतरना पड़ता है, जिससे I/O cost बढ़ती है
- sibling nodes के key ranges आपस में overlap नहीं करते, इसलिए right sibling pointer पर जाने का मतलब “अगली key range” पर पहुँचना सुनिश्चित होता है
B-Tree variants
- B⁺-Tree के अलावा भी कई तरह की variant data structures मौजूद हैं
- WiredTiger जैसे “Lazy B-Tree” और Lazy-Adaptive Tree write operations को buffer करके performance बढ़ाते हैं
- FD-Tree SSD की विशेषताओं के अनुसार डिज़ाइन की गई संरचना है, जो block-level writes जैसी physical constraints को ध्यान में रखती है
- Bw-Tree(Buzzword Tree) उच्च concurrency और in-memory tree access के लिए optimized data structure है
निष्कर्ष
- अमूर्त “B⁺-Tree” की अवधारणा और वास्तविक implementation “SQLite का DB format” के बीच optimization और implementation details के कई अंतर मौजूद हैं
- optimization Big-O complexity को नहीं बदलते, लेकिन वास्तविक वातावरण में database performance और usability पर बड़ा असर डालते हैं
- यहाँ बताए गए बिंदुओं के अलावा भी हर storage system में काफ़ी सूक्ष्म tuning की ज़रूरत पड़ती है
- “बस थोड़ी-सी अतिरिक्त जानकारी चाहिए” जैसी बातों के पीछे implementation complexity और debugging की कठिनाई छिपी होती है
- B-Tree संरचना को अधिक वास्तविक रूप में दिखाने वाले उदाहरण के तौर पर, Designing Data Intensive Applications का B-Tree diagram काफ़ी प्रभावशाली है
1 टिप्पणियां
Hacker News राय
पेज की संरचना header, cell, और offset pointer से बनी होती है, और इसका फ़ायदा यह है कि यह variable-size data स्टोर कर सकती है
यह B-Tree पर एक बेहतरीन लेख है, जिसमें animation भी शामिल हैं
कुछ साल पहले Ibrahim Jaluta के शोध के आधार पर concurrent recovery-capable B-link Tree implement किया था
SQLite disk page explorer बनाया, जिससे B-Tree को बेहतर समझने में मदद मिली
B-link Tree, concurrency, और locking पर चर्चा नहीं है, लेकिन शायद यह ज़रूरत से ज़्यादा जानकारी होती
पुरानी टिप्पणियाँ: Hacker News
शानदार लेख है, जो details के महत्व को अच्छी तरह समझाता है
Golang का उपयोग करके B-Tree implementation पर अच्छा resource है
मैं इस लेख का बहुत बड़ा प्रशंसक हूँ, और लेखक की तरह मेरी समझ भी कुछ हद तक 'धुंधली' थी