Computer >> कंप्यूटर ट्यूटोरियल >  >> प्रोग्रामिंग >> Ruby

डेटाबेस प्रदर्शन को खराब करने वाले Rails इडियम्स से बचना

मुझे याद है जब मैंने पहली बार Rails का ActiveRecord देखा था। यह एक रहस्योद्घाटन था। यह 2005 की बात है और मैं एक PHP ऐप के लिए हाथ से SQL क्वेरी लिख रहा था। अचानक, डेटाबेस का उपयोग एक थकाऊ काम से, आसान और - कहने की हिम्मत करें - मजेदार बन गया।

...फिर मैंने प्रदर्शन के मुद्दों को नोटिस करना शुरू किया।

ActiveRecord खुद धीमा नहीं था। मैंने बस उन क्वेरी पर ध्यान देना बंद कर दिया जो वास्तव में चल रही थीं। और पता चला, Rails CRUD ऐप्स में उपयोग किए जाने वाले कुछ सबसे इडियोमैटिक डेटाबेस क्वेरी डिफ़ॉल्ट रूप से बड़े डेटासेट तक स्केल करने में काफी खराब हैं।

इस लेख में हम तीन सबसे बड़े दोषियों पर चर्चा करने जा रहे हैं। लेकिन पहले, आइए बात करते हैं कि आप कैसे बता सकते हैं कि आपके DB क्वेरी अच्छी तरह से स्केल करने जा रहे हैं।

प्रदर्शन मापना

हर DB क्वेरी परफॉर्मेंट है यदि आपके पास पर्याप्त छोटा डेटासेट है। इसलिए वास्तव में प्रदर्शन का अहसास पाने के लिए, हमें प्रोडक्शन-आकार के डेटाबेस के खिलाफ बेंचमार्क करने की आवश्यकता है। हमारे उदाहरणों में, हम faults नामक एक टेबल का उपयोग करने जा रहे हैं जिसमें लगभग 22,000 रिकॉर्ड हैं।

हम Postgres का उपयोग कर रहे हैं। Postgres में, प्रदर्शन को मापने का तरीका explain का उपयोग करना है। उदाहरण के लिए:

# explain (analyze) select * from faults where id = 1;
QUERY PLAN
--------------------------------------------------------------------------------------------------
Index Scan using faults_pkey on faults (cost=0.29..8.30 rows=1 width=1855)
(actual time=0.556..0.556 rows=0 loops=1)
Index Cond: (id = 1)
Total runtime: 0.626 ms 

यह अनुमानित लागत और वास्तविक समय दोनों को दिखाता है जो क्वेरी को करने में लगा (actual time=0.556..0.556 rows=0 loops=1)

यदि आप अधिक पठनीय फॉर्मेट पसंद करते हैं, तो आप Postgres से YAML में परिणाम प्रिंट करने के लिए कह सकते हैं।

# explain (analyze, format yaml) select * from faults where id = 1;
QUERY PLAN
--------------------------------------
- Plan:
  + Node Type: "Index Scan"
  + Scan Direction: "Forward"
  + Index Name: "faults_pkey"
  + Relation Name: "faults"
  + Alias: "faults"
  + Startup Cost: 0.29
  + Total Cost: 8.30
  + Plan Rows: 1
  + Plan Width: 1855
  + Actual Startup Time: 0.008
  + Actual Total Time: 0.008
  + Actual Rows: 0
  + Actual Loops: 1
  + Index Cond: "(id = 1)"
  + Rows Removed by Index Recheck: 0
+ Triggers:
+ Total Runtime: 0.036
(1 row) 

अभी के लिए हम केवल "Plan Rows" और "Actual Rows" पर ध्यान केंद्रित करने जा रहे हैं।

  • Plan Rows सबसे खराब स्थिति में, DB को आपके क्वेरी का जवाब देने के लिए कितनी पंक्तियों के माध्यम से लूप करना होगा
  • Actual Rows जब इसने क्वेरी निष्पादित की, तो DB ने वास्तव में कितनी पंक्तियों के माध्यम से लूप किया?

यदि "Plan Rows" 1 है, जैसे ऊपर है, तो क्वेरी शायद अच्छी तरह से स्केल करेगी। यदि "Plan Rows" डेटाबेस में पंक्तियों की संख्या के बराबर है, तो इसका मतलब है कि क्वेरी "फुल टेबल स्कैन" करने जा रही है और अच्छी तरह से स्केल नहीं करेगी।

अब जब आप जानते हैं कि क्वेरी प्रदर्शन को कैसे मापना है, तो आइए कुछ सामान्य Rails इडियम्स को देखें और देखें कि वे कैसे ढेर होते हैं।

काउंटिंग

Rails व्यू में इस तरह का कोड देखना वास्तव में आम है:

Total Faults <%= Fault.count %> 

इसके परिणामस्वरूप SQL ऐसा दिखता है:

select count(*) from faults; 

आइए इसे explain में प्लग करें और देखें क्या होता है।

# explain (analyze, format yaml) select count(*) from faults;
QUERY PLAN
--------------------------------------
- Plan:
  + Node Type: "Aggregate"
  + Strategy: "Plain"
  + Startup Cost: 1840.31
  + Total Cost: 1840.32
  + Plan Rows: 1
  + Plan Width: 0
  + Actual Startup Time: 24.477
  + Actual Total Time: 24.477
  + Actual Rows: 1
  + Actual Loops: 1
  + Plans:
  + - Node Type: "Seq Scan"
    + Parent Relationship: "Outer"
    + Relation Name: "faults"
    + Alias: "faults"
    + Startup Cost: 0.00
    + Total Cost: 1784.65
    + Plan Rows: 22265
    + Plan Width: 0
    + Actual Startup Time: 0.311
    + Actual Total Time: 22.839
    + Actual Rows: 22265
    + Actual Loops: 1
  + Triggers:
  + Total Runtime: 24.555
(1 row) 

वाह! हमारा साधारण काउंट क्वेरी 22,265 पंक्तियों पर लूप कर रहा है - पूरी टेबल! Postgres में, काउंट हमेशा पूरे रिकॉर्ड सेट पर लूप करते हैं।

आप क्वेरी में where शर्तें जोड़कर रिकॉर्ड सेट का आकार कम कर सकते हैं। आपकी आवश्यकताओं के आधार पर, आप आकार को इतना कम कर सकते हैं कि प्रदर्शन स्वीकार्य हो।

इस मुद्दे को हल करने का एकमात्र अन्य तरीका अपने काउंट मानों को कैश करना है। Rails आपके लिए यह कर सकता है यदि आप इसे सेट करते हैं:

belongs_to :project, :counter_cache => true 

एक और विकल्प उपलब्ध है जब यह जांचने की बात आती है कि क्वेरी कोई रिकॉर्ड लौटाती है या नहीं। Users.count > 0 के बजाय, Users.exists? आज़माएं। परिणामी क्वेरी बहुत अधिक परफॉर्मेंट है। (पाठक Gerry Shaw को धन्यवाद जिन्होंने इसे मेरे ध्यान में लाया।)

सॉर्टिंग

इंडेक्स पेज। लगभग हर ऐप में कम से कम एक होता है। आप डेटाबेस से नवीनतम 20 रिकॉर्ड खींचते हैं और उन्हें प्रदर्शित करते हैं। इससे सरल क्या हो सकता है?

रिकॉर्ड लोड करने का कोड कुछ इस तरह दिख सकता है:

@faults = Fault.order(created_at: :desc) 

उसके लिए SQL ऐसा दिखता है:

select * from faults order by created_at desc; 

तो आइए इसका विश्लेषण करें:

# explain (analyze, format yaml) select * from faults order by created_at desc;
QUERY PLAN
--------------------------------------
- Plan:
  + Node Type: "Sort"
  + Startup Cost: 39162.46
  + Total Cost: 39218.12
  + Plan Rows: 22265
  + Plan Width: 1855
  + Actual Startup Time: 75.928
  + Actual Total Time: 86.460
  + Actual Rows: 22265
  + Actual Loops: 1
  + Sort Key:
  + - "created_at"
  + Sort Method: "external merge"
  + Sort Space Used: 10752
  + Sort Space Type: "Disk"
  + Plans:
  + - Node Type: "Seq Scan"
    + Parent Relationship: "Outer"
    + Relation Name: "faults"
    + Alias: "faults"
    + Startup Cost: 0.00
    + Total Cost: 1784.65
    + Plan Rows: 22265
    + Plan Width: 1855
    + Actual Startup Time: 0.004
    + Actual Total Time: 4.653
    + Actual Rows: 22265
    + Actual Loops: 1
  + Triggers:
  + Total Runtime: 102.288
(1 row) 

यहां हम देखते हैं कि DB हर बार जब आप यह क्वेरी करते हैं तो सभी 22,265 पंक्तियों को सॉर्ट कर रहा है। अच्छा नहीं!

डिफ़ॉल्ट रूप से, आपके SQL में हर "order by" क्लॉज रिकॉर्ड सेट को वहीं, रियल-टाइम में सॉर्ट करने का कारण बनता है। कोई कैशिंग नहीं। आपको बचाने के लिए कोई जादू नहीं।

समाधान इंडेक्स का उपयोग करना है। इस तरह के सरल मामलों के लिए, created_at कॉलम में एक सॉर्टेड इंडेक्स जोड़ने से क्वेरी काफी तेज हो जाएगी।

अपने Rails माइग्रेशन में आप डाल सकते हैं:

class AddIndexToFaultCreatedAt < ActiveRecord::Migration
  def change
    add_index(:faults, :created_at)
  end
end 

जो निम्नलिखित SQL चलाता है:

CREATE INDEX index_faults_on_created_at ON faults USING btree (created_at); 

वहां बहुत अंत में, (created_at) एक सॉर्ट ऑर्डर निर्दिष्ट करता है। डिफ़ॉल्ट रूप से यह आरोही है।

अब, यदि हम अपने सॉर्ट क्वेरी को फिर से चलाते हैं, तो हम देखते हैं कि इसमें अब सॉर्टिंग चरण शामिल नहीं है। यह बस इंडेक्स से प्री-सॉर्टेड डेटा पढ़ता है।

# explain (analyze, format yaml) select * from faults order by created_at desc;
QUERY PLAN
----------------------------------------------
- Plan:
  + Node Type: "Index Scan"
  + Scan Direction: "Backward"
  + Index Name: "index_faults_on_created_at"
  + Relation Name: "faults"
  + Alias: "faults"
  + Startup Cost: 0.29
  + Total Cost: 5288.04
  + Plan Rows: 22265
  + Plan Width: 1855
  + Actual Startup Time: 0.023
  + Actual Total Time: 8.778
  + Actual Rows: 22265
  + Actual Loops: 1
  + Triggers:
  + Total Runtime: 10.080
(1 row) 

यदि आप एकाधिक कॉलम द्वारा सॉर्ट कर रहे हैं, तो आपको एक इंडेक्स बनाने की आवश्यकता होगी जो एकाधिक कॉलम द्वारा भी सॉर्ट किया गया हो। Rails माइग्रेशन में यह कैसा दिखता है:

add_index(:faults, [:priority, :created_at], order: {priority: :asc, created_at: :desc}) 

जैसे-जैसे आप अधिक जटिल क्वेरी करना शुरू करते हैं, उन्हें explain के माध्यम से चलाना एक अच्छा विचार है। इसे जल्दी और अक्सर करें। आप पा सकते हैं कि क्वेरी में कुछ सरल बदलाव ने Postgres के लिए इंडेक्स का उपयोग करके सॉर्ट करना असंभव बना दिया है।

लिमिट्स और ऑफसेट

हमारे इंडेक्स पेज में हम शायद ही कभी डेटाबेस में हर आइटम शामिल करते हैं। इसके बजाय हम पेजिनेट करते हैं, एक समय में केवल 10 या 30 या 50 आइटम दिखाते हैं। ऐसा करने का सबसे आम तरीका limit और offset को एक साथ उपयोग करना है। Rails में यह ऐसा दिखता है:

Fault.limit(10).offset(100) 

यह SQL उत्पन्न करता है जो ऐसा दिखता है:

select * from faults limit 10 offset 100; 

अब यदि हम explain चलाते हैं, तो हम कुछ अजीब देखते हैं। स्कैन की गई पंक्तियों की संख्या 110 है, जो लिमिट प्लस ऑफसेट के बराबर है।

# explain (analyze, format yaml) select * from faults limit 10 offset 100;
QUERY PLAN
--------------------------------------
- Plan:
  + Node Type: "Limit"
  + ...
  Plans:
  + - Node Type: "Seq Scan"
    + Actual Rows: 110
    + ... 

यदि आप ऑफसेट को 10,000 में बदलते हैं तो आप देखेंगे कि स्कैन की गई पंक्तियों की संख्या 10010 हो जाती है, और क्वेरी 64x धीमी हो जाती है।

# explain (analyze, format yaml) select * from faults limit 10 offset 10000;
QUERY PLAN
--------------------------------------
- Plan:
  + Node Type: "Limit"
  + ...
  Plans:
  + - Node Type: "Seq Scan"
    + Actual Rows: 10010
    + ... 

यह एक परेशान करने वाले निष्कर्ष की ओर ले जाता है: जब पेजिनेट करते हैं, तो बाद के पेज पहले के पेज की तुलना में लोड होने में धीमे होते हैं। यदि हम ऊपर दिए गए उदाहरण में प्रति पेज 100 आइटम मानते हैं, तो पेज 100, पेज 1 की तुलना में 13x धीमा है।

तो आप क्या करते हैं?

स्पष्ट रूप से, मुझे एक सही समाधान खोजने में सक्षम नहीं हुआ है। मैं यह देखने से शुरू करूंगा कि क्या मैं डेटासेट का आकार कम कर सकता हूं ताकि मुझे शुरू करने के लिए 100s या 1000s पेज न हों।

यदि आप अपने रिकॉर्ड सेट को कम करने में असमर्थ हैं, तो आपका सबसे अच्छा दांव ऑफसेट/लिमिट को where क्लॉज से बदलना हो सकता है।

# आप डेट रेंज का उपयोग कर सकते हैं
Fault.where("created_at > ? and created_at < ?", 100.days.ago, 101.days.ago)
# ...या यहां तक कि एक आईडी रेंज
Fault.where("id > ? and id < ?", 100, 200) 

निष्कर्ष

मुझे उम्मीद है कि इस लेख ने आपको आश्वस्त किया है कि आपको वास्तव में Postgres के explain फंक्शन का लाभ उठाना चाहिए ताकि आपके DB क्वेरी के साथ संभावित प्रदर्शन समस्याओं को ढूंढा जा सके। यहां तक कि सबसे सरल क्वेरी भी प्रमुख प्रदर्शन समस्याएं पैदा कर सकती हैं, इसलिए जांच करना लाभदायक है। :)

  1. Rails प्रदर्शन में महारत: अपने ऐप्स का निदान और त्वरण करने का सिद्ध दृष्टिकोण Rails प्रदर्शन में महारत: अपने ऐप्स का निदान और त्वरण करने का सिद्ध दृष्टिकोण

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

  1. Excel डेटाबेस को स्वचालित रूप से अपडेट करें: 4 सिद्ध तरीके Excel डेटाबेस को स्वचालित रूप से अपडेट करें: 4 सिद्ध तरीके

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

  1. गूगल क्रोम की संशोधित एक्सटेंशन नीति लोकप्रिय एड ब्लॉकर्स को खतरे में डाल सकती है गूगल क्रोम की संशोधित एक्सटेंशन नीति लोकप्रिय एड ब्लॉकर्स को खतरे में डाल सकती है

    कौन से एक्सटेंशन प्रभावित होंगे? गूगल क्रोम ब्राउज़र अनुभव को बढ़ाने की योजना बना रहा है। हालांकि, इसने विभिन्न एक्सटेंशन डेवलपर्स को उत्तेजित किया है। यदि 실행 किया जाता है तो यह योजना क्रोम के प्रदर्शन और सुरक्षा में सुधार करेगी लेकिन उन एक्सटेंशन को भी अपंग कर देगी, जो ऑनलाइन ब्राउज़िंग करते समय वि