CouchDB दस्तावेज़ मॉडलिंग के लिए सिद्धांत


120

मेरा एक प्रश्न है जिसे मैं पिछले कुछ समय से उत्तर देने की कोशिश कर रहा हूं लेकिन समझ नहीं पा रहा हूं:

आप CouchDB दस्तावेज़ कैसे डिज़ाइन या विभाजित करते हैं?

उदाहरण के लिए एक ब्लॉग पोस्ट लो।

यह करने के लिए अर्ध "संबंधपरक" तरीका कुछ वस्तुओं को बनाने के लिए होगा:

  • पद
  • उपयोगकर्ता
  • टिप्पणी
  • टैग
  • टुकड़ा

यह बहुत अच्छा समझ में आता है। लेकिन मैं एक ही चीज़ को मॉडल करने के लिए काउचडब (सभी कारणों से बहुत बढ़िया है) का उपयोग करने की कोशिश कर रहा हूं।

वहाँ से बाहर अधिकांश ब्लॉग पोस्ट आपको यह करने का एक आसान उदाहरण देते हैं। वे मूल रूप से इसे उसी तरह से विभाजित करते हैं, लेकिन कहते हैं कि आप प्रत्येक दस्तावेज़ में 'मनमानी' गुण जोड़ सकते हैं, जो निश्चित रूप से अच्छा है। तो आपके पास CouchDB में ऐसा कुछ होगा:

  • पोस्ट (टैग और स्निपेट्स के साथ डॉक्टर में "छद्म" मॉडल)
  • टिप्पणी
  • उपयोगकर्ता

कुछ लोग कहेंगे कि आप टिप्पणी और उपयोगकर्ता को वहां फेंक सकते हैं, इसलिए आपके पास यह होगा:


post {
    id: 123412804910820
    title: "My Post"
    body: "Lots of Content"
    html: "<p>Lots of Content</p>"
    author: {
        name: "Lance"
        age: "23"
    }
    tags: ["sample", "post"]
    comments {
        comment {
            id: 93930414809
            body: "Interesting Post"
        } 
        comment {
            id: 19018301989
            body: "I agree"
        }
    }
}

यह बहुत अच्छा लग रहा है और समझने में आसान है। मैं यह भी समझता हूं कि आप अपने सभी पोस्ट दस्तावेज़ों से केवल टिप्पणियों को निकालने वाले विचार कैसे लिख सकते हैं, उन्हें टिप्पणी मॉडल में लाने के लिए, उपयोगकर्ताओं और टैग के साथ भी।

लेकिन फिर मुझे लगता है, "क्यों न सिर्फ एक दस्तावेज़ में मेरी पूरी साइट डाल दी जाए?"


site {
    domain: "www.blog.com"
    owner: "me"
    pages {
        page {
            title: "Blog"
            posts {
                post {
                    id: 123412804910820
                    title: "My Post"
                    body: "Lots of Content"
                    html: "<p>Lots of Content</p>"
                    author: {
                        name: "Lance"
                        age: "23"
                    }
                    tags: ["sample", "post"]
                    comments {
                        comment {
                            id: 93930414809
                            body: "Interesting Post"
                        } 
                        comment {
                            id: 19018301989
                            body: "I agree"
                        }
                    }
                }
                post {
                    id: 18091890192984
                    title: "Second Post"
                    ...
                }
            }
        }
    }
}

आप आसानी से विचार कर सकते हैं कि आप क्या चाहते थे।

फिर मेरे पास सवाल है कि आप दस्तावेज़ को छोटे दस्तावेजों में विभाजित करने के लिए, या दस्तावेजों के बीच "संबंध" बनाने के लिए कब निर्धारित करते हैं?

मुझे लगता है कि यह बहुत अधिक "ऑब्जेक्ट ओरिएंटेड" होगा, और वैल्यू ऑब्जेक्ट्स के लिए मैप करना आसान होगा, अगर इसे इस तरह विभाजित किया गया था:


posts {
    post {
        id: 123412804910820
        title: "My Post"
        body: "Lots of Content"
        html: "<p>Lots of Content</p>"
        author_id: "Lance1231"
        tags: ["sample", "post"]
    }
}
authors {
    author {
        id: "Lance1231"
        name: "Lance"
        age: "23"
    }
}
comments {
    comment {
        id: "comment1"
        body: "Interesting Post"
        post_id: 123412804910820
    } 
    comment {
        id: "comment2"
        body: "I agree"
        post_id: 123412804910820
    }
}

... लेकिन फिर यह एक रिलेशनल डेटाबेस की तरह दिखने लगता है। और अक्सर बार मुझे कुछ ऐसी चीज़ विरासत में मिलती है जो "संपूर्ण-साइट-इन-द-डॉक्यूमेंट" की तरह लगती है, इसलिए इसे संबंधों के साथ जोड़ना अधिक कठिन है।

मैंने रिलेशनल डेटाबेस बनाम डॉक्यूमेंट डेटाबेस का उपयोग कैसे / कब किया जाए, इसके बारे में बहुत सारी बातें पढ़ी हैं, इसलिए यहाँ मुख्य मुद्दा नहीं है। मैं और अधिक सोच रहा हूँ, CouchDB में मॉडलिंग डेटा लागू करने के लिए एक अच्छा नियम / सिद्धांत क्या है।

एक अन्य उदाहरण XML फ़ाइलों / डेटा के साथ है। कुछ XML डेटा में 10+ का स्तर गहरा होता है, और मैं उसी क्लाइंट (उदाहरण के लिए पटरियों पर Ajax) का उपयोग करके कल्पना करना चाहूंगा कि मुझे JSON को ActiveRecord, CouchRest, या किसी भी अन्य रिलेशनल मैपर से रेंडर करना होगा। कभी-कभी मुझे बहुत बड़ी XML फाइलें मिलती हैं जो पूरी साइट की संरचना होती हैं, जैसे नीचे दी गई हैं, और मुझे अपने रेल एप्लिकेशन में उपयोग करने के लिए इसे वैल्यू ऑब्जेक्ट्स पर मैप करने की आवश्यकता होगी, इसलिए मुझे डेटा को क्रमबद्ध / डीसरीलाइज़ करने का दूसरा तरीका नहीं लिखना होगा। :


<pages>
    <page>
        <subPages>
            <subPage>
                <images>
                    <image>
                        <url/>
                    </image>
                </images>
            </subPage>
        </subPages>
    </page>
</pages>

तो सामान्य CouchDB प्रश्न हैं:

  1. आप अपने दस्तावेज़ों (रिश्तों आदि) को विभाजित करने के लिए किन नियमों / सिद्धांतों का उपयोग करते हैं?
  2. क्या पूरी साइट को एक दस्तावेज़ में रखना ठीक है?
  3. यदि हां, तो आप मनमाने ढंग से गहराई के स्तर के साथ क्रमांकन / डिसरियलाइज़िंग दस्तावेजों को कैसे संभालते हैं (जैसे ऊपर बड़ा उदाहरण, या xml उदाहरण)?
  4. या क्या आप उन्हें वीओ में नहीं बदलते हैं, क्या आप सिर्फ यह तय करते हैं कि "ये लोग ऑब्जेक्ट-रिलेशनल मैप के लिए बहुत नेस्टेड हैं, इसलिए मैं सिर्फ कच्चे XML / JSON विधियों का उपयोग करके उन्हें एक्सेस करूंगा"?

आपकी मदद के लिए बहुत बहुत धन्यवाद, CouchDB के साथ अपने डेटा को विभाजित करने का मुद्दा मेरे लिए यह कहना मुश्किल है "यह है कि मुझे इसे अभी से कैसे करना चाहिए"। मुझे जल्द ही वहां पहुंचने की उम्मीद है।

मैंने निम्नलिखित साइटों / परियोजनाओं का अध्ययन किया है।

  1. CouchDB में पदानुक्रमित डेटा
  2. काउचडीबी विकी
  3. सोफा - काउचडीबी ऐप
  4. CouchDB निश्चित गाइड
  5. PeepCode CouchDB Screencast
  6. CouchRest
  7. काउचबडी README

... लेकिन उन्होंने अभी भी इस सवाल का जवाब नहीं दिया है।


2
वाह आपने पूरा निबंध यहाँ लिखा है ... :-)
Eero

8
अरे, यह एक अच्छा सवाल है
elmarco

जवाबों:


26

इस बारे में पहले से ही कुछ बेहतरीन जवाब दिए गए हैं, लेकिन मैं कुछ और हालिया काउचबैंक फीचर्स जोड़ना चाहता हूं, जो कि वियाट्रोपोस द्वारा बताई गई मूल स्थिति के साथ काम करने के लिए विकल्पों के मिश्रण में शामिल हों।

मुख्य बिंदु जिस पर दस्तावेजों को विभाजित करना है, जहां संघर्ष हो सकता है (जैसा कि पहले उल्लेख किया गया है)। आपको कभी भी एक दस्तावेज़ में बड़े पैमाने पर "पेचीदा" दस्तावेजों को एक साथ नहीं रखना चाहिए, क्योंकि आपको पूरी तरह से असंबंधित अपडेट के लिए एक एकल संशोधन पथ मिलेगा (उदाहरण के लिए संपूर्ण साइट दस्तावेज़ में संशोधन जोड़कर टिप्पणी)। विभिन्न, छोटे दस्तावेज़ों के बीच संबंधों या संबंधों को प्रबंधित करना, पहली बार में भ्रमित हो सकता है, लेकिन काउचडीबी एकल प्रतिक्रियाओं में टुकड़े टुकड़े के संयोजन के लिए कई विकल्प प्रदान करता है।

पहला बड़ा एक दृश्य टकराव है। जब आप किसी मैप / कम क्वेरी के परिणामों में कुंजी / मान जोड़े का उत्सर्जन करते हैं, तो कुंजी को UTF-8 कोलेशन ("a" "b" से पहले आता है) के आधार पर सॉर्ट किया जाता है। आप अपने नक्शे से जटिल कुंजियों को भी आउटपुट कर सकते हैं / JSON सरणियों के रूप में कम कर सकते हैं ["a", "b", "c"]:। ऐसा करने से आप सरणी कुंजियों से निर्मित "ट्री" को शामिल कर सकते हैं। ऊपर आपके उदाहरण का उपयोग करते हुए, हम पोस्ट_ड आउटपुट कर सकते हैं, फिर जिस प्रकार की चीज़ हम संदर्भित कर रहे हैं, उसके बाद उसकी आईडी (यदि आवश्यक हो)। यदि हम फिर संदर्भित दस्तावेज़ की आईडी को उस मूल्य में एक वस्तु में आउटपुट करते हैं जो हम लौटाते हैं, तो हम उन दस्तावेज़ों को मानचित्र में शामिल करने / आउटपुट को कम करने के लिए 'शामिल_दो' क्वेरी परम का उपयोग कर सकते हैं:

{"rows":[
  {"key":["123412804910820", "post"], "value":null},
  {"key":["123412804910820", "author", "Lance1231"], "value":{"_id":"Lance1231"}},
  {"key":["123412804910820", "comment", "comment1"], "value":{"_id":"comment1"}},
  {"key":["123412804910820", "comment", "comment2"], "value":{"_id":"comment2"}}
]}

उस 'के साथ एक ही दृश्य का अनुरोध करना; दस्तावेज़ का '_id' जिसमें से पंक्ति को उत्सर्जित किया गया था (इस स्थिति में 'डाक' दस्तावेज़)। कृपया ध्यान दें, इन परिणामों में स्रोत दस्तावेज़ को संदर्भित करने वाला एक 'आईडी' फ़ील्ड शामिल होगा जिसमें से एमिट बनाया गया था। मैंने इसे अंतरिक्ष और पठनीयता के लिए छोड़ दिया।

फिर हम परिणामों को एक पोस्ट के डेटा तक फ़िल्टर करने के लिए 'start_key' और 'end_key' मापदंडों का उपयोग कर सकते हैं:

? start_key = ["123412804910820"] & end_key = ["123412804910820", {}, {}]
या विशेष रूप से एक निश्चित प्रकार के लिए सूची निकालें:
? start_key = ["123412804910820", "टिप्पणी"] & end_key = ["123412804910820", "टिप्पणी", {}]
ये क्वेरी परम संयोजन संभव हैं क्योंकि एक खाली ऑब्जेक्ट (" {}") हमेशा टकराव के निचले भाग पर होता है और अशक्त या "" हमेशा शीर्ष पर होता है।

इन स्थितियों में CouchDB से दूसरा सहायक जोड़ _list फ़ंक्शन है। यह आपको किसी तरह की टेम्प्लेटिंग प्रणाली के माध्यम से उपरोक्त परिणाम चलाने की अनुमति देगा (यदि आप HTML, XML, CSV या जो कुछ भी चाहते हैं), या एक एकीकृत JSON संरचना को आउटपुट करें यदि आप संपूर्ण पोस्ट की सामग्री का अनुरोध करने में सक्षम होना चाहते हैं (सहित लेखक और टिप्पणी डेटा) एक एकल अनुरोध के साथ और एक एकल JSON दस्तावेज़ के रूप में लौटाता है जो आपके क्लाइंट-साइड / UI कोड से मेल खाता है। ऐसा करने से आप पोस्ट के एकीकृत आउटपुट दस्तावेज़ का अनुरोध इस तरह से कर सकते हैं:

/ db / _design / app / _list / posts / unified ?? start_key = ["123412804910820"] & end_key = ["123412804910820", {}, {}] और &_docs = true
आपका _list फ़ंक्शन ("एकीकृत" नाम के इस मामले में) दृश्य मानचित्र के परिणामों को कम / कम करेगा (इस स्थिति में "पोस्ट" नाम) और उन्हें एक जावास्क्रिप्ट फ़ंक्शन के माध्यम से चलाएगा जो आपको सामग्री प्रकार में HTTP प्रतिक्रिया वापस भेज देगा जरूरत (JSON, HTML, आदि)।

इन चीजों को मिलाकर, आप अपने दस्तावेज़ों को अपडेट, संघर्षों और प्रतिकृति के लिए उपयोगी और "सुरक्षित" स्तर पर विभाजित कर सकते हैं, और फिर अनुरोध किए जाने पर उन्हें आवश्यकतानुसार वापस जोड़ सकते हैं।

उम्मीद है की वो मदद करदे।


2
यकीन नहीं होता कि इससे लांस को मदद मिली, लेकिन मैं एक बात जानता हूं; यह निश्चित रूप से मुझे बहुत मदद मिली! यह कमाल का है!
मार्क

17

मुझे पता है कि यह एक पुराना प्रश्न है, लेकिन मुझे इस सटीक समस्या का सबसे अच्छा तरीका जानने की कोशिश में लगा। क्रिस्टोफर लेनज़ ने काउचबडी में मॉडलिंग "जॉइन" के तरीकों के बारे में एक अच्छा ब्लॉग पोस्ट लिखा । मेरा एक रास्ता था: "संबंधित डेटा को गैर-परस्पर विरोधी जोड़ने की अनुमति देने का एकमात्र तरीका यह है कि संबंधित डेटा को अन्य दस्तावेजों में डाल दिया जाए।" तो, सादगी के लिए आप "वियोग" की ओर झुकाव चाहते हैं। लेकिन आप कुछ परिस्थितियों में परस्पर विरोधी लेखन के कारण एक प्राकृतिक बाधा को मार देंगे।

आपके पोस्ट और टिप्पणियों के उदाहरण में, यदि कोई एकल पोस्ट और उसकी सभी टिप्पणियां एक दस्तावेज़ में रहती थीं, तो दो लोग एक ही समय में एक टिप्पणी पोस्ट करने की कोशिश कर रहे हैं (अर्थात दस्तावेज़ के उसी संशोधन के खिलाफ) एक संघर्ष का कारण होगा। यह आपके "पूरी साइट में एकल दस्तावेज़" परिदृश्य में और भी बदतर हो जाएगा।

इसलिए मुझे लगता है कि अंगूठे का नियम "तब तक असामान्य होगा जब तक कि यह दर्द न हो", लेकिन वह बिंदु जहां यह "चोट" करेगा, जहां आपको एक दस्तावेज़ के एक ही संशोधन के खिलाफ पोस्ट किए जाने वाले कई संपादनों की उच्च संभावना है।


दिलचस्प प्रतिक्रिया। इसे ध्यान में रखते हुए, किसी को यह सवाल करना चाहिए कि क्या किसी भी उच्च यातायात साइट पर एक दस्तावेज़ में किसी एक ब्लॉग पोस्ट के लिए सभी टिप्पणियाँ होंगी। अगर मैं इसे सही पढ़ता हूं, तो इसका मतलब है कि हर बार जब आप लोगों को त्वरित उत्तराधिकार में टिप्पणियां जोड़ते हैं, तो आपको संघर्षों को हल करना पड़ सकता है। बेशक, मुझे नहीं पता कि उत्तराधिकार में कितनी जल्दी उन्हें यह ट्रिगर करना होगा।
pc1oad1etter

1
उस स्थिति में जहां टिप्पणियां काउच में डॉक्टर का हिस्सा होती हैं, साथ-साथ टिप्पणी पोस्ट संभव संघर्ष कर सकती हैं क्योंकि आपके सभी टिप्पणियों के साथ आपके संस्करण की गुंजाइश "पोस्ट" है। उस स्थिति में जहां आपकी प्रत्येक वस्तु दस्तावेज़ों का संग्रह है, ये बस दो नए 'टिप्पणी' दस्तावेज़ बन जाएंगे, जिनके लिंक पोस्ट पर होंगे और टकराव की कोई चिंता नहीं होगी। मैं यह भी कहना चाहता हूं कि "ऑब्जेक्ट ओरिएंटेड" डॉक डिज़ाइन पर बिल्डिंग व्यूज़ सीधे आगे हैं - आप उदाहरण के लिए किसी पोस्ट की कुंजी में पास होते हैं, फिर उस पोस्ट के लिए, किसी विधि द्वारा छांटे गए सभी टिप्पणियों को छोड़ दें।
रियाद कल्ला

16

किताब कहते हैं, अगर मैं सही ढंग से याद करते हैं, जब तक "यह दर्द होता है" denormalize है, जबकि आवृत्ति जिसके साथ अपने दस्तावेज़ों से अद्यतन किया जा सकता है ध्यान में रखते हुए।

  1. आप अपने दस्तावेज़ों (रिश्तों आदि) को विभाजित करने के लिए किन नियमों / सिद्धांतों का उपयोग करते हैं?

अंगूठे के एक नियम के रूप में, मैं उस प्रश्न के मद के बारे में एक पृष्ठ प्रदर्शित करने के लिए आवश्यक सभी डेटा शामिल करता हूं। दूसरे शब्दों में, सब कुछ आप कागज के एक वास्तविक दुनिया के टुकड़े पर प्रिंट करेंगे जिसे आप किसी को सौंप देंगे। जैसे एक स्टॉक उद्धरण दस्तावेज़ में संख्या के अलावा कंपनी का नाम, एक्सचेंज, मुद्रा शामिल होगा; एक अनुबंध दस्तावेज में समकक्षों के नाम और पते, तारीखों और हस्ताक्षरकर्ताओं की सभी जानकारी शामिल होगी। लेकिन अलग-अलग तारीखों के स्टॉक कोट्स अलग-अलग दस्तावेज़ बनाते हैं, अलग-अलग अनुबंध अलग-अलग दस्तावेज़ बनाते हैं।

  1. क्या पूरी साइट को एक दस्तावेज़ में रखना ठीक है?

नहीं, यह मूर्खतापूर्ण होगा, क्योंकि:

  • आपको प्रत्येक अद्यतन पर पूरी साइट (दस्तावेज़) पढ़ना और लिखना होगा, और यह बहुत ही अक्षम है;
  • आप किसी भी दृश्य कैशिंग से लाभान्वित नहीं होंगे।

3
मेरे साथ इसमें शामिल होने के लिए धन्यवाद। मुझे "प्रश्न में आइटम के बारे में पृष्ठ प्रदर्शित करने के लिए आवश्यक सभी डेटा शामिल हैं" का विचार मिलता है, लेकिन इसे लागू करना अभी भी बहुत मुश्किल है। एक "पृष्ठ" टिप्पणियों का एक पृष्ठ हो सकता है, उपयोगकर्ताओं का एक पृष्ठ, पोस्ट का एक पृष्ठ, या टिप्पणियाँ और पोस्ट का एक पृष्ठ, आदि। आप उन्हें मुख्य रूप से कैसे विभाजित करेंगे? आप अपने अनुबंध को उपयोगकर्ताओं के साथ भी प्रदर्शित कर सकते हैं। मुझे 'फॉर्म-जैसे' दस्तावेज मिलते हैं, जो उन्हें अलग रखने के लिए समझ में आता है।
लांस पोलार्ड

6

मुझे लगता है कि जेक की प्रतिक्रिया नाखून काउचडीबी के साथ काम करने के सबसे महत्वपूर्ण पहलुओं में से एक है जो आपको स्कॉपिंग निर्णय लेने में मदद कर सकती है: संघर्ष।

उस स्थिति में जहां आपके पास पोस्ट की एक सरणी संपत्ति के रूप में टिप्पणियां हैं, और आपके पास बस एक 'पोस्ट' DB है जिसमें विशाल 'पोस्ट' दस्तावेजों का एक गुच्छा है, क्योंकि जेक और अन्य ने सही ढंग से कहा है कि आप एक परिदृश्य की कल्पना कर सकते हैं। वास्तव में लोकप्रिय ब्लॉग पोस्ट जहां दो उपयोगकर्ता एक साथ पोस्ट दस्तावेज़ में संपादन प्रस्तुत करते हैं, जिसके परिणामस्वरूप उस दस्तावेज़ के लिए टकराव और संस्करण संघर्ष होता है।

ASIDE: जैसा कि यह लेख बताता है , भी विचार करें कि हर बार जब आप उस दस्तावेज़ का अनुरोध / अपडेट कर रहे हैं, तो आपको दस्तावेज़ को उसकी संपूर्णता में प्राप्त करना / निर्धारित करना है, इसलिए एक बड़े पैमाने पर दस्तावेज़ों को पास करना जो या तो पूरी साइट का प्रतिनिधित्व करते हैं या बहुत से पोस्ट का। इस पर टिप्पणियों से आप एक ऐसी समस्या बन सकते हैं जिससे आप बचना चाहेंगे।

उस स्थिति में जहां पोस्ट को टिप्पणियों से अलग-अलग रूप दिया जाता है और दो लोग किसी कहानी पर टिप्पणी प्रस्तुत करते हैं, वे बस उस डीबी में दो "टिप्पणी" दस्तावेज बन जाते हैं, जिसमें कोई समस्या नहीं होती है; "टिप्पणी" db में दो नई टिप्पणियाँ जोड़ने के लिए सिर्फ दो PUT ऑपरेशन।

फिर उन विचारों को लिखने के लिए जो आपको किसी पोस्ट के लिए टिप्पणियां वापस देते हैं, आप पोस्टआईडी में पास हो जाते हैं और फिर उन सभी टिप्पणियों को छोड़ देते हैं जो उस पोस्ट पोस्ट आईडी को संदर्भित करते हैं, जो कुछ तार्किक क्रम में क्रमबद्ध हैं। हो सकता है कि आप मूल पोस्ट को इंगित करने के लिए 'टिप्पणियों' के दृश्य की कुंजी के रूप में [पोस्टिड, बाययूजनेम] जैसे कुछ भी पास करते हैं और आप कैसे चाहते हैं कि परिणाम हल हो या उन पंक्तियों के साथ कुछ।

MongoDB दस्तावेज़ों को थोड़ा अलग तरीके से संभालता है, जिससे अनुक्रमणिका को दस्तावेज़ के उप-तत्वों पर बनाया जा सकता है, इसलिए आप MongoDB मेलिंग सूची पर एक ही सवाल देख सकते हैं और कोई कह सकता है कि "टिप्पणियों को मूल पोस्ट की संपत्ति बनाएं"।

मानगो की राइट लॉकिंग और सिंगल-मास्टर प्रकृति के कारण, टिप्पणियों को जोड़ने वाले दो लोगों के परस्पर विरोधी संशोधन मुद्दे पर वसंत नहीं होगा और सामग्री की क्वेरी-क्षमता, जैसा कि उल्लेख किया गया है, उप के कारण बहुत खराब रूप से प्रभावित नहीं हुई है अनुक्रमित।

यह कहा जा रहा है, यदि आपके उप-तत्वों में से कोई भी DB में बहुत बड़ा होने वाला है (10 टिप्पणियों के हजारों कहो), मेरा मानना ​​है कि यह उन अलग-अलग तत्वों को बनाने के लिए दोनों शिविरों की सिफारिश है; मैंने निश्चित रूप से देखा है कि मानगो के मामले में कुछ ऊपरी सीमाएं हैं जैसे कि एक दस्तावेज और उसके अधीनताएं कितनी बड़ी हो सकती हैं।


बहुत मददगार। धन्यवाद
रे Suelzer
हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.