ऐपबॉय मोबाइल ऐप्स के लिए दुनिया का अग्रणी मार्केटिंग ऑटोमेशन प्लेटफॉर्म है। हम अपने ग्राहकों के मोबाइल ऐप में उपयोगकर्ता क्या कर रहे हैं, इस पर नज़र रखते हुए और उन्हें अपने व्यवहार या जनसांख्यिकी के आधार पर ईमेल, पुश नोटिफिकेशन और इन-ऐप संदेशों के लिए उपयोगकर्ताओं को लक्षित करने की अनुमति देकर हर महीने अरबों डेटा पॉइंट एकत्र करते हैं। MongoDB हमारे अधिकांश डेटाबेस स्टैक को शक्ति देता है, और हम ObjectRocket पर कई क्लस्टर में दर्जनों शार्क होस्ट करते हैं।
MongoDB के साथ एक सामान्य प्रदर्शन अनुकूलन रणनीति दस्तावेज़ों में छोटे फ़ील्ड नामों का उपयोग करना है। यानी ऐसा दिखने वाला दस्तावेज़ बनाने के बजाय…
{first_name: "Jon", last_name: "Hyman"}
…छोटे फ़ील्ड नामों का उपयोग करें ताकि दस्तावेज़ ऐसा दिखाई दे…
{fn: "Jon", ln: "Hyman"}
चूंकि MongoDB में कॉलम या पूर्वनिर्धारित स्कीमा की अवधारणा नहीं है, इसलिए यह संरचना लाभप्रद है क्योंकि डेटाबेस में प्रत्येक दस्तावेज़ पर फ़ील्ड नामों की नकल की जाती है। यदि आपके पास दस लाख दस्तावेज़ हैं जिनमें प्रत्येक पर "first_name" फ़ील्ड है, तो आप उस स्ट्रिंग को दस लाख बार संग्रहीत कर रहे हैं। यह प्रति दस्तावेज़ अधिक स्थान की ओर जाता है, जो अंततः प्रभावित करता है कि कितने दस्तावेज़ मेमोरी में फिट हो सकते हैं और, बड़े पैमाने पर, प्रदर्शन को थोड़ा प्रभावित कर सकते हैं, क्योंकि MongoDB को दस्तावेज़ों को मेमोरी में मैप करना होता है क्योंकि यह उन्हें पढ़ता है।
ईवेंट डेटा एकत्र करने के अलावा, ऐपबॉय हमारे ग्राहकों को उनके प्रत्येक उपयोगकर्ता पर "कस्टम विशेषताओं" को संग्रहीत करने देता है। एक उदाहरण के रूप में, एक स्पोर्ट्स ऐप उपयोगकर्ता के "पसंदीदा खिलाड़ी" को स्टोर करना चाहता है, जबकि एक पत्रिका या समाचार पत्र ऐप स्टोर कर सकता है कि ग्राहक "वार्षिक सब्सक्राइबर" है या नहीं। ऐपबॉय में, हमारे पास ऐप के प्रत्येक अंतिम उपयोगकर्ता के लिए एक दस्तावेज़ है जिसे हम ट्रैक करते हैं, और उस पर हम उन कस्टम विशेषताओं को उनके पहले या अंतिम नाम जैसे फ़ील्ड के साथ संग्रहीत करते हैं। स्थान बचाने और प्रदर्शन में सुधार करने के लिए, हम दस्तावेज़ पर संग्रहीत सभी चीज़ों के फ़ील्ड नामों को छोटा करते हैं। उन क्षेत्रों के लिए जिन्हें हम पहले से जानते हैं (जैसे पहला नाम, ईमेल, लिंग, आदि) हम अपना खुद का अलियासिंग कर सकते हैं (उदाहरण के लिए, "एफएन" का अर्थ "पहला नाम") है, लेकिन हम कस्टम विशेषताओं के नामों की भविष्यवाणी नहीं कर सकते हैं जिसे हमारे ग्राहक रिकॉर्ड करेंगे। अगर किसी ग्राहक ने "supercalifragilisticexpialidocious" नामक कस्टम विशेषता बनाने का निर्णय लिया है, तो हम उसे उनके सभी दस्तावेज़ों पर संग्रहीत नहीं करना चाहते हैं।
इसे हल करने के लिए, हम कस्टम विशेषता फ़ील्ड नामों को टोकनाइज़ करते हैं जिसे हम "नाम स्टोर" कहते हैं। प्रभावी रूप से, यह MongoDB में एक दस्तावेज़ है जो "पसंदीदा खिलाड़ी" जैसे मूल्यों को एक अद्वितीय, अनुमानित, बहुत छोटी स्ट्रिंग में मैप करता है। हम केवल MongoDB के परमाणु ऑपरेटरों का उपयोग करके यह नक्शा तैयार कर सकते हैं
नाम स्टोर दस्तावेज़ स्कीमा अत्यंत बुनियादी है:प्रत्येक ग्राहक के लिए एक दस्तावेज़ होता है, और प्रत्येक दस्तावेज़ में केवल "सूची" नामक एक सरणी फ़ील्ड होता है। विचार यह है कि सरणी में कस्टम विशेषताओं के लिए सभी मान होंगे और किसी दिए गए स्ट्रिंग की अनुक्रमणिका इसका टोकन होगा। इसलिए यदि हम "पसंदीदा खिलाड़ी" को एक संक्षिप्त, पूर्वानुमेय फ़ील्ड नाम में अनुवाद करना चाहते हैं, तो हम केवल यह देखने के लिए "सूची" की जांच करते हैं कि यह सरणी में कहां है। यदि यह नहीं है, तो हम सरणी के अंत में तत्व जोड़ने के लिए एक परमाणु धक्का जारी कर सकते हैं, (db.custom_attribute_name_stores.update({_id :X, सूची:{$ne :"पसंदीदा खिलाड़ी"}}, {$ पुश:{सूची:"पसंदीदा खिलाड़ी"}})), दस्तावेज़ को फिर से लोड करें और सूचकांक निर्धारित करें। आदर्श रूप से, हमने $addToSet का उपयोग किया होगा, लेकिन $addToSet आदेश देने की गारंटी नहीं देता है, जबकि $push को डिफ़ॉल्ट रूप से अंत तक जोड़ने के लिए प्रलेखित किया गया है।
तो इस बिंदु पर, हम "पसंदीदा खिलाड़ी" जैसी किसी चीज़ का पूर्णांक मान में अनुवाद कर सकते हैं। मान लें कि मान 1 है। तब हमारा उपयोगकर्ता दस्तावेज़ इस तरह दिखेगा:
{
fn: "Jon",
ln: "Hyman",
custom: {
1: "LeBron James"
}
}
फ़ील्ड नाम छोटे और सुव्यवस्थित हैं! इसका एक बड़ा साइड इफेक्ट यह है कि हमें अपने ग्राहकों के बारे में चिंता करने की ज़रूरत नहीं है कि हम उन पात्रों का उपयोग कर रहे हैं जो MongoDB बिना बच निकले समर्थन नहीं कर सकते, जैसे कि डॉलर के संकेत या अवधि।
अब, आप सोच रहे होंगे कि MongoDB लगातार बढ़ते दस्तावेज़ों के प्रति सावधान करता है और यह कि हमारा नाम संग्रह दस्तावेज़ असीमित रूप से बढ़ सकता है। व्यवहार में, हमने अपने कार्यान्वयन को थोड़ा बढ़ा दिया है ताकि हम प्रति ग्राहक एक से अधिक दस्तावेज़ संग्रहीत कर सकें। यह हमें एक नया दस्तावेज़ बनाने से पहले कितने सरणी तत्वों की अनुमति देता है, इस पर उचित कैप लगाने देता है। सबसे अच्छी बात यह है कि हम अभी भी यह सब परमाणु रूप से केवल MongoDB का उपयोग करके कर सकते हैं! इसे प्राप्त करने के लिए, हम प्रत्येक दस्तावेज़ में "न्यूनतम मूल्य" नामक एक अन्य फ़ील्ड जोड़ते हैं। "न्यूनतम मूल्य" फ़ील्ड यह दर्शाता है कि इसे बनाने से पहले पिछले दस्तावेज़ों में कितने तत्व जोड़े गए हैं। इसलिए यदि हम “कम से कम मूल्य” 100 और [“सीज़न टिकट धारक”, “पसंदीदा खिलाड़ी”] की “सूची” के साथ एक दस्तावेज़ देखते हैं, तो “पसंदीदा खिलाड़ी” के लिए टोकन मान 101 है (हम शून्य का उपयोग कर रहे हैं) आधारित अनुक्रमण)। इस उदाहरण में, हम एक नया दस्तावेज़ बनाने से पहले "सूची" सरणी में केवल 100 मान संग्रहीत कर रहे हैं। अब, सम्मिलित करते समय, हम दस्तावेज़ पर उच्चतम "कम से कम . के साथ संचालित करने के लिए पुश को थोड़ा संशोधित करते हैं value" मान, और यह भी सुनिश्चित करें कि "list.99" मौजूद नहीं है (जिसका अर्थ है कि "सूची" सरणी में अनुक्रमणिका 99 में कुछ भी नहीं है)। यदि उस सूचकांक में कोई तत्व पहले से मौजूद है, तो पुश ऑपरेशन कुछ नहीं करेगा। उस स्थिति में, हम जानते हैं कि हमें सभी दस्तावेज़ों में मौजूद तत्वों की कुल संख्या के बराबर "Least_value" के साथ एक नया नाम स्टोर दस्तावेज़ बनाने की आवश्यकता है। परमाणु $findAndModify का उपयोग करके, हम नया दस्तावेज़ बना सकते हैं यदि यह मौजूद नहीं है, तो इसे वापस लाएं और फिर $push को फिर से प्रयास करें।
यदि हमारे ग्राहक के पास केवल कुछ कस्टम विशेषताएँ हैं, तो सभी नाम स्टोर दस्तावेज़ों को मानों से टोकन में अनुवाद करने के लिए पढ़ना बैंडविड्थ और प्रसंस्करण के मामले में महंगा हो सकता है। हालांकि, चूंकि किसी दिए गए फ़ील्ड का टोकन मान हमेशा एक जैसा होता है, उसकी गणना के बाद, हम अनुवाद को गति देने के लिए टोकन को कैश करते हैं।
हमने अपने एप्लिकेशन के विभिन्न हिस्सों में "नाम स्टोर टोकन" प्रतिमान लागू किया है ताकि एक लचीली स्कीमा का उपयोग जारी रखते हुए फ़ील्ड नाम के आकार में कटौती की जा सके। यह मूल्यों के लिए भी सहायक हो सकता है। मान लें कि एक रेडियो स्टेशन ऐप एक कस्टम विशेषता संग्रहीत करता है जो शीर्ष 50 प्रदर्शन करने वाले कलाकारों की एक सरणी है जिसे उपयोगकर्ता सुनता है। इसमें 50 स्ट्रिंग्स के साथ एक सरणी होने के बजाय, हम रेडियो स्टेशन के नामों को टोकन कर सकते हैं और इसके बजाय उपयोगकर्ता पर 50 पूर्णांकों की एक सरणी संग्रहीत कर सकते हैं। किसी खास कलाकार को पसंद करने वाले उपयोगकर्ताओं से पूछताछ में अब दो टोकन लुकअप शामिल हैं:एक फ़ील्ड नाम के लिए और दूसरा मान के लिए। लेकिन चूंकि हम मूल्य से टोकन में अनुवाद को कैश करते हैं, इसलिए हम किसी भी संख्या में मूल्यों का अनुवाद करते समय कैश में एकल राउंड-ट्रिप बनाए रखने के लिए अपनी कैश परत में मल्टी-गेट का उपयोग कर सकते हैं।
यह अनुकूलन निश्चित रूप से कुछ संकेत और जटिलता जोड़ता है, लेकिन जब आप हमारे जैसे करोड़ों उपयोगकर्ताओं को ऐपबॉय में संग्रहीत करते हैं, तो यह एक सार्थक अनुकूलन है। हमने इस ट्रिक से सैकड़ों गीगाबाइट महंगा एसएसडी स्थान बचाया है।
अधिक सीखना चाहते हैं? मैं 18 सितंबर को सिप्रियानी में रैकस्पेस सॉल्व एनवाईसी सम्मेलन के दौरान एपबॉय में देवोप्स पर चर्चा करूंगा।