Django 2.1 - Database access optimization

डेटाबेस का उपयोग अनुकूलन




django

डेटाबेस का उपयोग अनुकूलन

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

पहले प्रोफ़ाइल

सामान्य प्रोग्रामिंग अभ्यास के रूप में, यह बिना कहे चला जाता है। पता करें कि आप क्या प्रश्न कर रहे हैं और वे आपके लिए क्या खर्च कर रहे हैं QuerySet.explain() का उपयोग यह समझने के लिए करें कि आपके डेटाबेस द्वारा कितने विशिष्ट QuerySet निष्पादित किए जाते हैं। आप django-debug-toolbar जैसी बाहरी परियोजना या अपने डेटाबेस पर सीधे नज़र रखने वाले टूल का भी उपयोग कर सकते हैं।

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

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

मानक DB अनुकूलन तकनीकों का उपयोग करें

…समेत:

  • Indexes । यह एक नंबर एक प्राथमिकता है, आपके द्वारा यह निर्धारित करने के बाद कि सूचकांक को क्या जोड़ा जाना चाहिए। Django से Meta.index_together जोड़ने के लिए Field.db_index या Meta.index_together से उपयोग करें। उन क्षेत्रों में अनुक्रमणिका जोड़ने पर विचार करें, जिन्हें आप अक्सर filter() , exclude() , order_by() , इत्यादि का उपयोग करते हुए अनुक्रमित करते हैं क्योंकि अनुक्रमणिका लुकअप को गति देने में मदद कर सकती हैं। ध्यान दें कि सबसे अच्छा सूचकांक निर्धारित करना एक जटिल डेटाबेस-निर्भर विषय है जो आपके विशेष एप्लिकेशन पर निर्भर करेगा। सूचकांक बनाए रखने का ओवरहेड क्वेरी गति में किसी भी लाभ से आगे निकल सकता है।
  • क्षेत्र प्रकारों का उपयुक्त उपयोग।

हम मान लेंगे कि आपने ऊपर स्पष्ट बातें की हैं। इस दस्तावेज़ के बाकी हिस्से पर ध्यान केंद्रित किया गया है कि इस तरह से Django का उपयोग कैसे करें कि आप अनावश्यक काम नहीं कर रहे हैं। यह दस्तावेज़ अन्य अनुकूलन तकनीकों को भी संबोधित नहीं करता है जो सभी महंगे संचालन पर लागू होते हैं, जैसे कि सामान्य प्रयोजन कैशिंग

QuerySet को समझें

QuerySets को समझना सरल कोड के साथ अच्छा प्रदर्शन प्राप्त करने के लिए महत्वपूर्ण है। विशेष रूप से:

QuerySet मूल्यांकन को समझें

प्रदर्शन की समस्याओं से बचने के लिए, यह समझना महत्वपूर्ण है:

कैश्ड विशेषताओं को समझें

पूरे QuerySet कैशिंग के साथ-साथ ORM ऑब्जेक्ट्स पर विशेषताओं के परिणाम का कैशिंग भी है। सामान्य तौर पर, जो विशेषताएँ कॉल करने योग्य नहीं होती हैं उन्हें कैश किया जाएगा। उदाहरण के लिए, उदाहरण के लिए वेबलॉग मॉडल :

>>> entry = Entry.objects.get(id=1)
>>> entry.blog   # Blog object is retrieved at this point
>>> entry.blog   # cached version, no DB access

लेकिन सामान्य तौर पर, कॉल करने योग्य विशेषताएँ हर बार DB लुकअप का कारण बनती हैं:

>>> entry = Entry.objects.get(id=1)
>>> entry.authors.all()   # query performed
>>> entry.authors.all()   # query performed again

टेम्प्लेट कोड पढ़ते समय सावधान रहें - टेम्प्लेट सिस्टम कोष्ठक के उपयोग की अनुमति नहीं देता है, लेकिन उपरोक्त गड़बड़ी को छिपाते हुए, कॉलबेल स्वचालित रूप से कॉल करेगा।

अपने स्वयं के कस्टम गुणों के साथ सावधान रहें - जब आवश्यक हो तो कैशिंग लागू करना आपके ऊपर है, उदाहरण के लिए cached_property डेकोरेटर का उपयोग करना।

टेम्पलेट टैग के with प्रयोग करें

QuerySet के कैशिंग व्यवहार का उपयोग करने के लिए, आपको टेम्प्लेट टैग के with उपयोग करने की आवश्यकता हो सकती है।

iterator() उपयोग करें iterator()

जब आपके पास बहुत सी वस्तुएं होती हैं, तो QuerySet के कैशिंग व्यवहार के कारण बड़ी मात्रा में मेमोरी का उपयोग किया जा सकता है। इस मामले में, iterator() मदद कर सकता है।

explain() उपयोग करें explain()

QuerySet.explain() आपको इस बारे में विस्तृत जानकारी देता है कि डेटाबेस किस तरह से उपयोग किए जाने वाले इंडेक्स और QuerySet.explain() सहित किसी क्वेरी को निष्पादित करता है। ये विवरण आपको उन प्रश्नों को खोजने में मदद कर सकते हैं जिन्हें अधिक कुशलता से फिर से लिखा जा सकता है, या उन अनुक्रमणिकाओं की पहचान कर सकते हैं जिन्हें प्रदर्शन में सुधार करने के लिए जोड़ा जा सकता है।

डेटाबेस में काम करें, बजाय पायथन में

उदाहरण के लिए:

यदि ये आपके लिए आवश्यक SQL उत्पन्न करने के लिए पर्याप्त नहीं हैं:

RawSQL उपयोग करें

एक कम पोर्टेबल लेकिन अधिक शक्तिशाली विधि RawSQL अभिव्यक्ति है, जो कुछ एसक्यूएल को क्वेरी में स्पष्ट रूप से जोड़ने की अनुमति देती है। यदि वह अभी भी पर्याप्त शक्तिशाली नहीं है:

कच्ची एसक्यूएल का उपयोग करें

डेटा पुनः प्राप्त करने या मॉडल को आबाद करने के लिए अपने स्वयं के कस्टम SQL लिखें। Django आपके लिए क्या लिख ​​रहा है और वहां से शुरू करें यह django.db.connection.queries लिए django.db.connection.queries का उपयोग करें।

एक अद्वितीय, अनुक्रमित कॉलम का उपयोग करके अलग-अलग वस्तुओं को पुनः प्राप्त करें

व्यक्तिगत वस्तुओं को प्राप्त करने के get() का उपयोग करते समय unique या Field.db_index कॉलम का उपयोग करने के दो कारण हैं। सबसे पहले, अंतर्निहित डेटाबेस इंडेक्स की वजह से क्वेरी जल्दी हो जाएगी। यदि कई ऑब्जेक्ट लुकअप से मेल खाते हैं, तो भी क्वेरी बहुत धीमी गति से चल सकती है; स्तंभ पर एक अद्वितीय बाधा होने की गारंटी देता है ऐसा कभी नहीं होगा।

इसलिए उदाहरण वेबलॉग मॉडल का उपयोग कर:

>>> entry = Entry.objects.get(id=10)

से जल्दी हो जाएगा:

>>> entry = Entry.objects.get(headline="News Item Title")

क्योंकि id को डेटाबेस द्वारा अनुक्रमित किया जाता है और इसे विशिष्ट होने की गारंटी दी जाती है।

निम्नलिखित करना संभावित रूप से काफी धीमा है:

>>> entry = Entry.objects.get(headline__startswith="News")

सबसे पहले, headline को अनुक्रमित नहीं किया गया है, जो अंतर्निहित डेटाबेस को धीमा कर देगा।

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

यदि आप जानते हैं कि आपको इसकी आवश्यकता होगी तो सब कुछ एक ही बार में प्राप्त करें

डेटाबेस को डेटा के एक 'सेट' के विभिन्न भागों के लिए कई बार हिट करना, जो कि आपको सभी भागों की आवश्यकता होगी, सामान्य तौर पर, इसे एक क्वेरी में प्राप्त करने की तुलना में कम कुशल। यह विशेष रूप से महत्वपूर्ण है यदि आपके पास एक क्वेरी है जिसे एक लूप में निष्पादित किया गया है, और इसलिए कई डेटाबेस क्वेरी कर सकते हैं, जब केवल एक की आवश्यकता थी। इसलिए:

  • प्रबंधकों और डिफ़ॉल्ट प्रबंधकों में जहां उपयुक्त हो। जागरूक रहें जब आपका प्रबंधक है और इसका उपयोग नहीं किया गया है; कभी-कभी यह मुश्किल होता है इसलिए धारणा न करें।
  • कोड या अन्य परतों को देखने में, संभवतः जहां आवश्यक हो वहां prefetch_related_objects() का उपयोग करना।

उन चीजों को पुनः प्राप्त न करें जिनकी आपको आवश्यकता नहीं है

QuerySet.values() और values_list() उपयोग करें

जब आप केवल एक dict या मूल्यों की list चाहते हैं, और ORM मॉडल ऑब्जेक्ट की आवश्यकता नहीं है, तो values() का उचित उपयोग करें values() । ये टेम्प्लेट कोड में मॉडल ऑब्जेक्ट्स को बदलने के लिए उपयोगी हो सकते हैं - जब तक आपके द्वारा सप्लाई की जाने वाली डक्ट्स में वही विशेषताएं होती हैं जो टेम्प्लेट में उपयोग की जाती हैं, आप ठीक हैं।

QuerySet.defer() और only() उपयोग करें

defer() और only() उपयोग करें यदि डेटाबेस कॉलम हैं जिन्हें आप जानते हैं कि उन्हें लोड करने से बचने के लिए आपको (या अधिकांश मामलों में) की आवश्यकता नहीं होगी। ध्यान दें कि यदि आप उनका उपयोग करते हैं, तो ओआरएम को एक अलग क्वेरी में जाना होगा और उन्हें प्राप्त करना होगा, यदि आप इसे अनुचित तरीके से उपयोग करते हैं तो यह एक निराशाकरण बना देगा।

इसके अलावा, ध्यान रखें कि आस्थगित क्षेत्रों के साथ एक मॉडल का निर्माण करते समय Django के अंदर कुछ (छोटे अतिरिक्त) ओवरहेड होते हैं। प्रोफाइलिंग के बिना फ़ील्ड्स को हटाने में बहुत अधिक आक्रामक न हों क्योंकि डेटाबेस को परिणामों में एकल पंक्ति के लिए डिस्क से अधिकांश गैर-पाठ, गैर- VARCHAR डेटा पढ़ना पड़ता है, भले ही यह केवल कुछ कॉलम का उपयोग करके समाप्त हो। जब आप बहुत सारे टेक्स्ट डेटा लोड करने से बच सकते हैं या उन क्षेत्रों के लिए, जो पाइथन को वापस बदलने में बहुत अधिक प्रक्रिया कर सकते हैं, तो defer() और only() विधियाँ सर्वाधिक उपयोगी हैं। हमेशा की तरह, पहले प्रोफ़ाइल, फिर अनुकूलन करें।

QuerySet.count() उपयोग करें QuerySet.count()

... अगर आप केवल len(queryset) करने के बजाय गिनती चाहते हैं।

QuerySet.exists() उपयोग करें QuerySet.exists()

... यदि आप केवल यह पता लगाना चाहते हैं कि if queryset बजाय कम से कम एक परिणाम मौजूद है।

परंतु:

count() उपयोग न करें count() और exists()

यदि आपको QuerySet से अन्य डेटा की आवश्यकता है, तो इसका मूल्यांकन करें।

उदाहरण के लिए, एक ई-मेल मॉडल जो एक body विशेषता और उपयोगकर्ता के लिए कई-से-कई संबंध है, को मानते हुए, निम्न टेम्पलेट कोड इष्टतम है:

{% if display_inbox %}
  {% with emails=user.emails.all %}
    {% if emails %}
      <p>You have {{ emails|length }} email(s)</p>
      {% for email in emails %}
        <p>{{ email.body }}</p>
      {% endfor %}
    {% else %}
      <p>No messages today.</p>
    {% endif %}
  {% endwith %}
{% endif %}

यह इष्टतम है क्योंकि:

  1. चूंकि QuerySets आलसी हैं, इसलिए यह कोई डेटाबेस क्वेरी नहीं करता है यदि 'display_inbox' गलत है।
  2. इसके with उपयोग का अर्थ है कि हम user.emails.all को बाद के उपयोग के लिए एक चर में संग्रहीत करते हैं, जिससे इसके कैश को फिर से उपयोग करने की अनुमति मिलती है।
  3. लाइन {% if emails %} कारण बनता है QuerySet.__bool__() कहा जाता है, जो user.emails.all() क्वेरी को डेटाबेस पर चलाने का कारण बनता है, और कम से कम पहली पंक्ति को ORM ऑब्जेक्ट में बदल दिया जाता है । यदि कोई परिणाम नहीं हैं, तो यह गलत हो जाएगा, अन्यथा सही।
  4. {{ emails|length }} का उपयोग QuerySet.__len__() , बाकी कैश को दूसरी क्वेरी किए बिना भरना।
  5. लूप के लिए पहले से भरे कैश पर पुनरावृति होती है।

कुल मिलाकर, यह कोड या तो एक या शून्य डेटाबेस क्वेरी करता है। केवल जानबूझकर किया गया अनुकूलन टैग के with उपयोग किया जाता है। किसी भी बिंदु पर QuerySet.exists() या QuerySet.count() से अतिरिक्त प्रश्न उत्पन्न होंगे।

QuerySet.update() और delete() उपयोग करें

ऑब्जेक्ट्स का एक लोड प्राप्त करने के बजाय, कुछ मान सेट करें, और उन्हें अलग-अलग सहेजें, QuerySet.update() माध्यम से एक बल्क SQL UPDATE स्टेटमेंट का उपयोग करें। इसी तरह, जहां संभव हो थोक हटाएं

हालाँकि, ध्यान दें कि ये बल्क अपडेट विधियां व्यक्तिगत उदाहरणों के save() या delete() तरीकों को नहीं कह सकती हैं, जिसका अर्थ है कि इन तरीकों के लिए आपके द्वारा जोड़े गए किसी भी कस्टम व्यवहार को निष्पादित नहीं किया जाएगा, जिसमें सामान्य डेटाबेस ऑब्जेक्ट signals से संचालित कुछ भी शामिल signals

सीधे विदेशी प्रमुख मूल्यों का उपयोग करें

यदि आपको केवल एक विदेशी कुंजी मूल्य की आवश्यकता है, तो उस विदेशी कुंजी मूल्य का उपयोग करें जो आपके पास पहले से मौजूद वस्तु पर है, न कि पूरे संबंधित वस्तु को प्राप्त करने और इसकी प्राथमिक कुंजी लेने के लिए। यानी करो:

entry.blog_id

के बजाय:

entry.blog.id

अगर आपको परवाह नहीं है तो परिणाम न दें

आदेश देना मुफ्त नहीं है; प्रत्येक क्षेत्र द्वारा ऑर्डर करने के लिए एक ऑपरेशन है जिसे डेटाबेस को करना चाहिए। यदि किसी मॉडल में डिफॉल्ट ऑर्डरिंग ( Meta.ordering ) है और आपको इसकी आवश्यकता नहीं है, तो इसे कोई पैरामीटर के साथ QuerySet order_by() कहकर एक QuerySet पर हटा दें।

अपने डेटाबेस में एक इंडेक्स जोड़ने से ऑर्डरिंग प्रदर्शन में सुधार करने में मदद मिल सकती है।

थोक में डालें

ऑब्जेक्ट बनाते समय, जहाँ संभव हो, SQL प्रश्नों की संख्या को कम करने के लिए bulk_create() पद्धति का उपयोग करें। उदाहरण के लिए:

Entry.objects.bulk_create([
    Entry(headline='This is a test'),
    Entry(headline='This is only a test'),
])

… के लिए बेहतर है:

Entry.objects.create(headline='This is a test')
Entry.objects.create(headline='This is only a test')

ध्यान दें कि bulk_create() कई नंबर हैं, इसलिए सुनिश्चित करें कि यह आपके उपयोग के मामले के लिए उपयुक्त है।

यह भी कई ManyToManyFields पर लागू होता है, इसलिए:

my_band.members.add(me, my_friend)

… के लिए बेहतर है:

my_band.members.add(me)
my_band.members.add(my_friend)

... जहां Bands और Artists बीच कई-कई संबंध हैं।