प्रसंग
यह प्रश्न SQL और NoSQL डेटाबेस सिस्टम दोनों में अनुक्रमित के निम्न-स्तरीय कार्यान्वयन विवरण से संबंधित है। सूचकांक की वास्तविक संरचना (बी + ट्री, हैश, एसएसटीबल, आदि) अप्रासंगिक है क्योंकि यह प्रश्न उन सभी कार्यान्वयनों में से किसी एक नोड के अंदर संग्रहीत कुंजी से संबंधित है ।
पृष्ठभूमि
SQL (जैसे MySQL) और NoSQL (CouchDB, MongoDB, आदि) डेटाबेस में, जब आप किसी कॉलम या JSON डॉक्यूमेंट फील्ड ऑफ़ डेटा पर एक इंडेक्स का निर्माण करते हैं, तो आप वास्तव में जो डेटाबेस बनाने का कारण बनते हैं, वह अनिवार्य रूप से सभी की क्रमबद्ध सूची बनाता है फ़ाइल के साथ वे मान मुख्य डेटा फ़ाइल में ऑफसेट होते हैं, जहाँ उस मान से संबंधित रिकॉर्ड रहता है।
(सादगी की खातिर, मैं हाथ से अन्य विशिष्ट विवरणों के गूढ़ विवरण को मिटा सकता हूं)
सरल क्लासिक एसक्यूएल उदाहरण
एक मानक एसक्यूएल टेबल पर विचार करें जिसमें एक साधारण 32-बिट इंट प्राथमिक कुंजी है जिसे हम एक इंडेक्स बनाते हैं, हम पूर्णांक की-डिस्क के एक इंडेक्स के साथ समाप्त हो जाएंगे, जिसे सॉर्ट किया गया है और डेटा फ़ाइल में 64-बिट ऑफ़सेट के साथ संबद्ध है जहां रिकॉर्ड रहता है, उदाहरण के लिए:
id | offset
--------------
1 | 1375
2 | 1413
3 | 1786
सूचकांक में कुंजियों का ऑन-डिस्क प्रतिनिधित्व कुछ इस तरह दिखता है:
[4-bytes][8-bytes] --> 12 bytes for each indexed value
फाइलसिस्टम और डेटाबेस सिस्टम के साथ डिस्क I / O को अनुकूलित करने के बारे में अंगूठे के मानक नियमों के अनुसार, चलिए आपको डिस्क पर 4KB ब्लॉक में कुंजियों को संग्रहीत करने का मतलब है:
4096 bytes / 12 bytes per key = 341 keys per block
सूचकांक (बी + ट्री, हैश, सॉर्ट की गई सूची, आदि) की समग्र संरचना को अनदेखा करते हुए हम 341 कुंजी के ब्लॉक को एक बार में मेमोरी में पढ़ते हैं और आवश्यकतानुसार डिस्क पर वापस भेजते हैं।
उदाहरण क्वेरी
पिछले अनुभाग से जानकारी का उपयोग करते हुए, मान लें कि "id = 2" के लिए एक क्वेरी आती है, क्लासिक DB इंडेक्स लुकअप निम्नानुसार है:
- सूचकांक की जड़ पढ़ें (इस मामले में, 1 ब्लॉक)
- बाइनरी-कुंजी को खोजने के लिए सॉर्ट किए गए ब्लॉक को खोजें
- मान से डेटा फ़ाइल ऑफ़सेट प्राप्त करें
- ऑफसेट का उपयोग करके डेटा फ़ाइल में रिकॉर्ड देखें
- कॉलर को डेटा लौटाएं
प्रश्न सेटअप ...
ठीक है, यहाँ है जहाँ सवाल एक साथ आता है ...
चरण # 2 सबसे महत्वपूर्ण हिस्सा है जो इन प्रश्नों को O (logn) समय में निष्पादित करने की अनुमति देता है ... सूचना को क्रमबद्ध किया जाना चाहिए, लेकिन आपको त्वरित-क्रमबद्ध तरीके से सूची का पता लगाने में सक्षम होना चाहिए ... और विशेष रूप से, आपको उस स्थिति में सूचकांक कुंजी मूल्य में पढ़ने के लिए वसीयत में अच्छी तरह से परिभाषित ऑफसेट पर कूदने में सक्षम होना चाहिए।
ब्लॉक में पढ़ने के बाद, आपको तुरंत 170 वें स्थान पर कूदने में सक्षम होना चाहिए, मुख्य मूल्य पढ़ें और देखें कि क्या आप जिस जीटी या एलटी की तलाश कर रहे हैं, वह स्थिति (और इसी तरह और इसी तरह ...)
एकमात्र तरीका है कि आप ब्लॉक में डेटा के चारों ओर कूदने में सक्षम होंगे जैसे कि यदि मुख्य मान आकार सभी अच्छी तरह से परिभाषित किए गए थे, जैसे हमारे उदाहरण ऊपर (4-बाइट्स फिर 8-बाइट्स प्रति कुंजी)।
सवाल
ठीक है, तो यहाँ है जहाँ मैं कुशल सूचकांक डिजाइन के साथ फंस रहा हूँ ... SQL डेटाबेस में varchar स्तंभों के लिए या अधिक विशेष रूप से, CouchDB या NoSQL जैसे दस्तावेज़ डेटाबेस में पूरी तरह से मुक्त-फ़ॉर्म फ़ील्ड, जहाँ आप जिस भी क्षेत्र को अनुक्रमित करना चाहते हैं, वह कोई भी हो सकता है लंबाई आप उन प्रमुख मूल्यों को कैसे लागू करते हैं जो सूचकांक संरचना के ब्लॉक के अंदर हैं जो आप अपने सूचकांक का निर्माण करते हैं?
उदाहरण के लिए, मान लें कि आप CouchDB में एक ID के लिए एक अनुक्रमिक काउंटर का उपयोग करते हैं और आप ट्वीट्स को अनुक्रमित कर रहे हैं ... आपके पास कुछ महीने बाद "मान" "1" से "100,000,000,000" तक जाएंगे।
मान लीजिए कि आप 1 दिन डेटाबेस पर इंडेक्स बनाते हैं, जब डेटाबेस में केवल 4 ट्वीट होते हैं, CouchDB इंडेक्स ब्लॉकों के अंदर प्रमुख मूल्यों के लिए निम्न निर्माण का उपयोग करने के लिए लुभा सकता है:
[1-byte][8-bytes] <-- 9 bytes
4096 / 9 = 455 keys per block
कुछ बिंदु पर यह टूट जाता है और आपको इंडेक्स में अपने प्रमुख मूल्य को संग्रहीत करने के लिए बाइट्स की एक चर संख्या की आवश्यकता होती है।
यदि आप एक "कलरवेज़_मैसेज" या कुछ और जैसे वास्तव में परिवर्तनीय-लंबाई क्षेत्र को अनुक्रमित करने का निर्णय लेते हैं, तो यह बिंदु और भी अधिक आकर्षक है।
कुंजी की स्वयं पूरी तरह से परिवर्तनशील लंबाई होने के साथ, और डेटाबेस को कुछ "अधिकतम कुंजी आकार" का अनुमान लगाने का कोई तरीका नहीं है जब सूचकांक बनाया और अद्यतन किया जाता है, तो ये कुंजी वास्तव में इन डेटाबेस में सूचकांकों के खंडों का प्रतिनिधित्व करने वाले ब्लॉकों के अंदर कैसे संग्रहीत होती हैं। ?
जाहिर है अगर आपकी चाबियां परिवर्तनशील आकार की हैं और आप कुंजियों के ब्लॉक में पढ़ते हैं, तो न केवल आपको पता है कि ब्लॉक में वास्तव में कितनी चाबियां हैं , लेकिन आपको पता नहीं है कि बाइनरी करने के लिए सूची के बीच में कैसे कूदें उन पर खोज करें।
यह वह जगह है जहाँ मैं सब मिल रहा है।
क्लासिक एसक्यूएल डेटाबेस (जैसे बूल, इंट, चार, इत्यादि) में स्थिर-टाइप किए गए फ़ील्ड के साथ, मैं समझता हूं कि इंडेक्स प्रमुख लंबाई को पूर्व-परिभाषित कर सकता है और उससे चिपका रह सकता है ... लेकिन दस्तावेज़ डेटा स्टोर की इस दुनिया में, मैं हूं हैरान हैं कि वे डिस्क पर इस डेटा को कैसे कुशलता से मॉडलिंग कर रहे हैं ताकि यह अभी भी O (logn) समय में स्कैन किया जा सके और यहां किसी भी स्पष्टीकरण की सराहना की जा सके।
कृपया मुझे बताएं कि क्या किसी स्पष्टीकरण की आवश्यकता है!
अपडेट (ग्रेग का जवाब)
कृपया मेरी टिप्पणियों को ग्रेग के उत्तर से जुड़े देखें। एक सप्ताह के अनुसंधान के बाद मुझे लगता है कि वह वास्तव में एक शानदार सरल और प्रदर्शनकारी सुझाव पर लड़खड़ा गया है कि इन-प्रैक्टिस को लागू करना आसान है और उपयोग करना आसान है जबकि बड़े प्रदर्शन जीत प्रदान करने से बचते हैं, जिन प्रमुख मूल्यों की आप परवाह नहीं करते हैं।
मैंने 3 अलग-अलग DBMS कार्यान्वयन (CouchDB, kivaloo और InnoDB) में देखा है और उनमें से सभी ने अपने निष्पादन वातावरण (erlang / C) के अंदर मूल्यों की खोज करने से पहले पूरे ब्लॉक को आंतरिक डेटा संरचना में deserializing द्वारा इस मुद्दे को संभालते हैं।
यह मुझे लगता है कि ग्रेग के सुझाव के बारे में बहुत शानदार है; 2048 के एक सामान्य ब्लॉक का आकार सामान्य रूप से 50 या उससे कम होगा, जिसके परिणामस्वरूप बहुत कम संख्या में ब्लॉक को पढ़ने की आवश्यकता होगी।
अद्यतन (संभावित सुझाव ग्रेग के सुझाव के लिए)
अपने आप से इस संवाद को सर्वोत्तम रूप से जारी रखने के लिए, मुझे निम्नलिखित बातों का एहसास हुआ ...
यदि हर "ब्लॉक" ऑफसेट डेटा के साथ होता है, तो आप ब्लॉक आकार को बाद में सड़क के नीचे कॉन्फ़िगरेशन में समायोजित करने की अनुमति नहीं दे सकते हैं क्योंकि आप डेटा को पढ़ने में समाप्त हो सकते हैं जो हेडर के साथ सही तरीके से शुरू नहीं हुआ था या ब्लॉक नहीं था जिसमें कई हेडर थे।
यदि आप विशाल कुंजी मानों को अनुक्रमित कर रहे हैं (कहते हैं कि कोई व्यक्ति चार (8192) या बूँद (8192) के स्तंभ को अनुक्रमणित करने का प्रयास कर रहा है) तो संभव है कि कुंजियाँ एक खंड में फिट न हों और दो खंडों के पार बहने की आवश्यकता हो । इसका मतलब है कि आपके पहले ब्लॉक में एक ऑफसेट हेडर होगा और दूसरा ब्लॉक तुरंत प्रमुख डेटा के साथ शुरू होगा।
इस सब का समाधान एक निश्चित डेटाबेस ब्लॉक आकार है, जो समायोज्य नहीं है और इसके चारों ओर हेडर ब्लॉक डेटा संरचनाएं विकसित कर रहा है ... उदाहरण के लिए, आप सभी ब्लॉक आकार को 4KB (आमतौर पर सबसे अधिक अनुकूलतम) को ठीक करते हैं और एक बहुत छोटा लिखते हैं ब्लॉक हेडर जिसमें शुरुआत में "ब्लॉक टाइप" शामिल है। यदि इसका सामान्य ब्लॉक है, तो ब्लॉक हेडर के तुरंत बाद ऑफसेट हेडर होना चाहिए। यदि इसका "अतिप्रवाह" प्रकार है, तो ब्लॉक हेडर कच्चे कुंजी डेटा के तुरंत बाद है।
अद्यतन (संभावित भयानक अप साइड)
ब्लॉक को बाइट्स की एक श्रृंखला के रूप में पढ़ा जाता है और ऑफसेट डिकोड हो जाता है; तकनीकी रूप से आप बस उस कुंजी को एनकोड कर सकते हैं जिसे आप कच्चे बाइट्स के लिए खोज रहे हैं और फिर बाइट स्ट्रीम पर सीधी तुलना करते हैं।
एक बार जिस कुंजी को आप ढूंढ रहे हैं वह मिल जाने के बाद, पॉइंटर को डिकोड किया जा सकता है और उसका अनुसरण किया जा सकता है।
ग्रेग के विचार का एक और भयानक पक्ष-प्रभाव! यहां सीपीयू टाइम ऑप्टिमाइजेशन की क्षमता काफी बड़ी है कि एक निश्चित ब्लॉक साइज सेट करना इस सब को हासिल करने के लिए इसके लायक हो सकता है।