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

एक्टिवरिकॉर्ड काउंटर कैश के साथ अपने रेल्स ऐप को बूस्ट करें

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

आइए आर्टिकल्स और रिस्पॉन्सेस के साथ एक क्लासिक ब्लॉग का उदाहरण लें। प्रत्येक आर्टिकल के रिस्पॉन्सेस हो सकते हैं, और हम ब्लॉग के इंडेक्स पेज पर प्रत्येक आर्टिकल के शीर्षक के आगे रिस्पॉन्सेस की संख्या प्रदर्शित करना चाहेंगे ताकि इसकी लोकप्रियता दिखाई जा सके।

# app/controllers/articles_controller.rb
class ArticlesController < ApplicationController
  def index
    @articles = Article.all
  end
  # ...
end

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

<!-- app/views/articles/index.html.erb -->
<h1>Articles</h1>
<% @articles.each do |article| %>
<article>
<h1><%= article.title %></h1>
<p><%= article.description %></p>
<%= article.responses.size %> responses
</article>
<% end %>

व्यू प्रत्येक आर्टिकल पर लूप करता है और उसका शीर्षक, विवरण और प्राप्त रिस्पॉन्सेस की संख्या रेंडर करता है। क्योंकि हम व्यू में article.responses.size कॉल करते हैं, एक्टिवरिकॉर्ड जानता है कि उसे प्रत्येक रिस्पॉन्स के लिए पूरा रिकॉर्ड लोड करने के बजाय एसोसिएशन को गिनने की आवश्यकता है।

टिप: हालांकि #count रिस्पॉन्सेस की संख्या गिनने के लिए अधिक सहज विकल्प लगता है, इस उदाहरण में #size का उपयोग करता है, क्योंकि #count हमेशा COUNT क्वेरी करेगा, जबकि #size क्वेरी को छोड़ देगा अगर रिस्पॉन्सेस पहले से लोड हैं।

Started GET "/articles" for 127.0.0.1 at 2018-06-14 16:25:36 +0200
Processing by ArticlesController#index as HTML
Rendering articles/index.html.erb within layouts/application
Article Load (0.2ms) SELECT "articles".* FROM "articles"
↳ app/views/articles/index.html.erb:3
(0.2ms) SELECT COUNT(*) FROM "responses" WHERE "responses"."article_id" = ? [["article_id", 2]]
↳ app/views/articles/index.html.erb:7
(0.3ms) SELECT COUNT(*) FROM "responses" WHERE "responses"."article_id" = ? [["article_id", 3]]
↳ app/views/articles/index.html.erb:7
(0.1ms) SELECT COUNT(*) FROM "responses" WHERE "responses"."article_id" = ? [["article_id", 4]]
↳ app/views/articles/index.html.erb:7
(0.1ms) SELECT COUNT(*) FROM "responses" WHERE "responses"."article_id" = ? [["article_id", 5]]
↳ app/views/articles/index.html.erb:7
(0.1ms) SELECT COUNT(*) FROM "responses" WHERE "responses"."article_id" = ? [["article_id", 6]]
↳ app/views/articles/index.html.erb:7
(0.1ms) SELECT COUNT(*) FROM "responses" WHERE "responses"."article_id" = ? [["article_id", 7]]
↳ app/views/articles/index.html.erb:7
(0.1ms) SELECT COUNT(*) FROM "responses" WHERE "responses"."article_id" = ? [["article_id", 8]]
↳ app/views/articles/index.html.erb:7
(0.1ms) SELECT COUNT(*) FROM "responses" WHERE "responses"."article_id" = ? [["article_id", 9]]
↳ app/views/articles/index.html.erb:7
(0.1ms) SELECT COUNT(*) FROM "responses" WHERE "responses"."article_id" = ? [["article_id", 10]]
↳ app/views/articles/index.html.erb:7
(0.1ms) SELECT COUNT(*) FROM "responses" WHERE "responses"."article_id" = ? [["article_id", 11]]
↳ app/views/articles/index.html.erb:7
Rendered articles/index.html.erb within layouts/application (23.1ms)
Completed 200 OK in 52ms (Views: 45.7ms | ActiveRecord: 1.6ms) 

ब्लॉग के इंडेक्स का अनुरोध करने पर N+1 क्वेरीज़ परिणाम होती हैं, क्योंकि एक्टिवरिकॉर्ड प्रत्येक आर्टिकल के लिए रिस्पॉन्स काउंट को अलग क्वेरी में लेज़ी-लोड करता है।

क्वेरी से COUNT() का उपयोग करना

प्रति आर्टिकल एक अतिरिक्त क्वेरी चलाने से बचने के लिए, हम आर्टिकल्स और रिस्पॉन्सेस टेबल्स को एक साथ जोड़ सकते हैं ताकि एक सिंगल क्वेरी में संबंधित रिस्पॉन्सेस को गिना जा सके।

# app/controllers/articles_controller.rb
class ArticlesController < ApplicationController
  def index
    @articles = Article
      .joins(:responses)
      .select("articles.*", 'COUNT("responses.id") AS responses_count')
      .group('articles.id')
  end
  # ...
end

इस उदाहरण में, हम आर्टिकल्स क्वेरी में रिस्पॉन्सेस को जोड़ते हैं और रिस्पॉन्सेस की संख्या गिनने के लिए COUNT("responses.id") का चयन करते हैं। हम प्रति आर्टिकल रिस्पॉन्सेस गिनने के लिए प्रोडक्ट आईडीज़ द्वारा ग्रुप करेंगे। व्यू में, हमें रिस्पॉन्सेस एसोसिएशन पर size कॉल करने के बजाय responses_count का उपयोग करना होगा।

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

काउंटर कैश

चूंकि ब्लॉग पर आर्टिकल्स (उम्मीद है) अपडेट होने की तुलना में अधिक बार पढ़े जाते हैं, काउंटर कैश इस पेज को क्वेरी करना तेज और सरल बनाने के लिए एक अच्छा ऑप्टिमाइज़ेशन है।

हर बार आर्टिकल्स प्रदर्शित होने पर रिस्पॉन्सेस की संख्या गिनने के बजाय, एक काउंटर कैश एक अलग रिस्पॉन्स काउंटर रखता है जो प्रत्येक आर्टिकल के डेटाबेस रो में स्टोर होता है। काउंटर अपडेट होता है जब भी कोई रिस्पॉन्स जोड़ा या हटाया जाता है।

यह आर्टिकल इंडेक्स को रेंडर करने की अनुमति देता है एक डेटाबेस क्वेरी के साथ, बिना क्वेरी में रिस्पॉन्सेस को जोड़े। इसे सेट करने के लिए, belongs_to रिलेशन में स्विच को counter_cache विकल्प सेट करके फ्लिप करें।

# app/models/response.rb
class Response
  belongs_to :article, counter_cache: true
end

इसके लिए Article मॉडल में responses_count नाम का एक फील्ड चाहिए। counter_cache विकल्प सुनिश्चित करता है कि उस फील्ड में नंबर स्वचालित रूप से अपडेट होता है जब भी कोई रिस्पॉन्स जोड़ा या हटाया जाता है।

टिप: फील्ड नाम को true के बजाय सिम्बल का उपयोग करके counter_cache विकल्प के मान के रूप में ओवरराइड किया जा सकता है।

हम काउंट स्टोर करने के लिए अपने डेटाबेस में एक नया कॉलम बनाते हैं।

$ rails generate migration AddResponsesCountToArticles responses_count:integer
invoke active_record
create db/migrate/20180618093257_add_responses_count_to_articles.rb
$ rake db:migrate
== 20180618093257 AddResponsesCountToArticles: migrating ======================
-- add_column(:articles, :responses_count, :integer) -> 0.0016s
== 20180618093257 AddResponsesCountToArticles: migrated (0.0017s)
============= 

क्योंकि रिस्पॉन्सेस की संख्या अब आर्टिकल्स टेबल में कैश्ड है, हमें आर्टिकल्स क्वेरी में रिस्पॉन्सेस को जोड़ने की आवश्यकता नहीं है। हम कंट्रोलर में सभी आर्टिकल्स लाने के लिए Article.all का उपयोग करेंगे।

# app/controllers/articles_controller.rb
class ArticlesController < ApplicationController
  def index
    @articles = Article.all
  end
  # ...
end

हमें व्यू बदलने की आवश्यकता नहीं है, क्योंकि रेल्स #size मेथड के लिए काउंटर कैश का उपयोग करना समझता है।

<!-- app/views/articles/index.html.erb -->
<h1>Articles</h1>
<% @articles.each do |article| %>
<article>
<h1><%= article.title %></h1>
<p><%= article.description %></p>
<%= article.responses.size %> responses
</article>
<% end %>

फिर से हमारे इंडेक्स का अनुरोध करने पर, हम एक क्वेरी निष्पादित होते हुए देख सकते हैं। क्योंकि प्रत्येक आर्टिकल को उसके रिस्पॉन्सेस की संख्या पता है, उसे रिस्पॉन्सेस टेबल को बिल्कुल भी क्वेरी करने की आवश्यकता नहीं है।

Started GET "/articles" for 127.0.0.1 at 2018-06-14 17:15:23 +0200
Processing by ArticlesController#index as HTML
Rendering articles/index.html.erb within layouts/application
Article Load (0.2ms) SELECT "articles".* FROM "articles"
↳ app/views/articles/index.html.erb:3
Rendered articles/index.html.erb within layouts/application (3.5ms)
Completed 200 OK in 42ms (Views: 36.5ms | ActiveRecord: 0.2ms) 

स्कोप्ड एसोसिएशन्स के लिए काउंटर कैश

एक्टिवरिकॉर्ड के काउंटर कैश कॉलबैक्स केवल रिकॉर्ड्स बनाते या नष्ट करते समय फायर होते हैं, इसलिए स्कोप्ड एसोसिएशन पर काउंटर कैश जोड़ने से काम नहीं करेगा। उन्नत मामलों के लिए, जैसे केवल *प्रकाशित* रिस्पॉन्सेस की संख्या गिनना, counter_culture जेम देखें।

काउंटर कैश को भरना

काउंटर कैश से पहले के आर्टिकल्स के लिए, काउंटर आउट ऑफ सिंक होगा, क्योंकि यह डिफ़ॉल्ट रूप से 0 है। हम किसी ऑब्जेक्ट के लिए काउंटर को .reset_counters मेथड का उपयोग करके "रीसेट" कर सकते हैं और ऑब्जेक्ट की आईडी और वह रिलेशन पास कर सकते हैं जिसके लिए काउंटर अपडेट किया जाना चाहिए।

Article.reset_counters(article.id, :responses)

यह सुनिश्चित करने के लिए कि यह डेप्लॉय करते समय प्रोडक्शन पर चलता है, हम इसे पिछली माइग्रेशन में कॉलम जोड़ने के ठीक बाद चलने वाली माइग्रेशन में डालेंगे।

$ rails generate migration PopulateArticleResponsesCount --force
invoke active_record
create db/migrate/20180618093443_populate_article_responses_count.rb 

माइग्रेशन में, हम प्रत्येक आर्टिकल के लिए Article.reset_counters कॉल करेंगे, आर्टिकल्स की आईडीज़ और एसोसिएशन के नाम के रूप में :responses पास करेंगे।

# db/migrate/20180618093443_populate_article_responses_count.rb
class PopulateArticleResponsesCount < ActiveRecord::Migration[5.2]
  def up
    Article.find_each do |article|
      Article.reset_counters(article.id, :responses)
    end
  end
end

यह माइग्रेशन डेटाबेस में सभी आर्टिकल्स के काउंट्स को अपडेट करती है जिसमें काउंटर कैश से पहले मौजूद आर्टिकल्स शामिल हैं।

कॉलबैक्स

क्योंकि काउंटर कैश काउंटर्स को अपडेट करने के लिए कॉलबैक्स का उपयोग करते हैं, सीधे SQL कमांड निष्पादित करने वाले मेथड्स (जैसे #destroy के बजाय #delete का उपयोग करते समय) काउंटर्स को अपडेट नहीं करेंगे।

ऐसी स्थितियों में जहां किसी कारण से ऐसा होता है, समय-समय पर काउंट्स को सिंक में रखने के लिए एक Rake टास्क या बैकग्राउंड जॉब जोड़ना समझदारी हो सकती है।

namespace :counters do
  task update: :environment do
    Article.find_each do |article|
      Article.reset_counters(article.id, :responses)
    end
  end
end

कैश्ड काउंटर्स

क्वेरी में संबंधित ऑब्जेक्ट्स को गिनकर N+1 क्वेरीज़ को रोकना मदद कर सकता है, लेकिन अधिकांश एप्लिकेशन्स के लिए काउंटर्स कैश करना काउंटर्स दिखाने का एक और भी तेज़ तरीका है। एक्टिवरिकॉर्ड के बिल्ट-इन कैश्ड काउंटर्स बहुत मददगार हो सकते हैं, और काउंटर_कल्चर जैसे विकल्पों का उपयोग अधिक विस्तृत आवश्यकताओं के लिए किया जा सकता है।

एक्टिवरिकॉर्ड के काउंटर कैश के बारे में कोई प्रश्न? कृपया @AppSignal पर हमें बताने में संकोच न करें। बेशक, हमें यह जानकर खुशी होगी कि आपको यह लेख कैसा लगा, या यदि आपके पास कोई अन्य विषय है जिसके बारे में आप अधिक जानना चाहेंगे।

  1. Angular को Rails 5 के साथ इंटीग्रेट करना: एक सहज फुल-स्टैक दृष्टिकोण Angular को Rails 5 के साथ इंटीग्रेट करना: एक सहज फुल-स्टैक दृष्टिकोण

    आपने पहले कहानी सुनी है। आपके पास पहले से ही एक एप्लिकेशन है जो आपके विकेंद्रीकृत और पूरी तरह से काम करने वाले बैक-एंड APIs और किसी सामान्य टूलसेट के साथ बने फ्रंट-एंड पर चल रहा है। अब, आप Angular पर आगे बढ़ना चाहते हैं। या, शायद आप बस अपने रेल्स प्रोजेक्ट्स के साथ Angular को इंटीग्रेट करने का एक तर

  1. एज कैशिंग के साथ Redis में 5ms वैश्विक लेटेंसी प्राप्त करें एज कैशिंग के साथ Redis में 5ms वैश्विक लेटेंसी प्राप्त करें

    जब डेटाबेस और क्लाइंट एक ही क्षेत्र में हों तो Redis के साथ 1ms लेटेंसी प्राप्त करना आसान है। लेकिन यदि आप चाहते हैं कि क्लाइंट विश्व स्तर पर वितरित हों तो लेटेंसी 100ms से अधिक हो जाती है। हमने इस समस्या को दूर करने के लिए एज कैशिंग विकसित की है। एज कैशिंग एज कैशिंग के साथ, REST प्रतिक्रियाएं दुनि

  1. अपने मॉडल सरल बनाएं: स्पष्ट, पठनीय विशेषताओं के लिए ActiveRecord Enums का उपयोग अपने मॉडल सरल बनाएं: स्पष्ट, पठनीय विशेषताओं के लिए ActiveRecord Enums का उपयोग

    एक प्रश्न की कल्पना करें जो या तो लंबित, स्वीकृत, या चिह्नित हो सकता है। या एक फोन नंबर जो घर, कार्यालय, मोबाइल, या फैक्स हो (अगर यह 1982 है)। कुछ मॉडल इस तरह के डेटा की मांग करते हैं। एक विशेषता जो केवल कुछ अलग-अलग मानों में से एक हो सकती है। और मानों का वह सेट लगभग कभी नहीं बदलता। यह एक ऐसी स्थि