कैशिंग का वर्णन करने का एक सामान्य तरीका कुछ कोड के परिणाम को संग्रहीत करना है ताकि हम इसे बाद में जल्दी से प्राप्त कर सकें। कुछ मामलों में, इसका मतलब है कि बाद में इसे फिर से गणना करने की आवश्यकता से बचने के लिए एक गणना मूल्य संग्रहित करना। हालांकि, हम हार्ड ड्राइव से पढ़ने या नेटवर्क अनुरोध करने से बचने के लिए डेटा को केवल मेमोरी में रखकर, बिना कोई गणना किए, कैश कर सकते हैं।
यह बाद वाला रूप विशेष रूप से ActiveRecord के लिए प्रासंगिक है, जहां डेटाबेस अक्सर एक अलग सर्वर पर चलता है। इस प्रकार, सभी अनुरोधों में नेटवर्क-ट्रैफ़िक ओवरहेड होता है, न कि क्वेरी के दोबारा किए जाने पर डेटाबेस सर्वर पर रखे गए लोड का उल्लेख करने के लिए।
सौभाग्य से, रेल डेवलपर्स के लिए, ActiveRecord पहले से ही हमारे लिए बहुत कुछ संभालता है, शायद हमारे बिना भी इसके बारे में जागरूक नहीं है। यह उत्पादकता के लिए अच्छा है, लेकिन कभी-कभी, यह जानना महत्वपूर्ण है कि पर्दे के पीछे क्या कैश किया जा रहा है। उदाहरण के लिए, जब आप जानते हैं (या उम्मीद करते हैं) एक मूल्य किसी अन्य प्रक्रिया द्वारा बदला जा रहा है, या आपके पास सबसे अद्यतित मूल्य होना चाहिए। ऐसे मामलों में, ActiveRecord डेटा को कैश न किए गए पढ़ने के लिए बाध्य करने के लिए कुछ 'एस्केप हैच' प्रदान करता है।
ActiveRecord का आलसी मूल्यांकन
ActiveRecord का आलसी मूल्यांकन प्रति कैशिंग नहीं है, लेकिन हम बाद में कोड उदाहरणों में इसका सामना करेंगे, इसलिए हम एक संक्षिप्त अवलोकन प्रदान करेंगे। जब आप एक ActiveRecord क्वेरी बनाते हैं, तो कई मामलों में, कोड डेटाबेस को तत्काल कॉल जारी नहीं करता है। यही वह है जो हमें कई .where
. को श्रृंखलाबद्ध करने की अनुमति देता है हर बार डेटाबेस को हिट किए बिना क्लॉज:
@posts = Post.where(published: true)
# no DB hit yet
@posts = @posts.where(publied_at: Date.today)
# still nothing
@posts.count
# SELECT COUNT(*) FROM "posts" WHERE...
इसके कुछ अपवाद हैं। उदाहरण के लिए, .find
. का उपयोग करते समय , .find_by
, .pluck
, .to_a
, या .first
, अतिरिक्त खंडों को श्रृंखलाबद्ध करना असंभव है। नीचे दिए गए अधिकांश उदाहरणों में, मैं .to_a
. का उपयोग करूंगा DB कॉल को बाध्य करने के एक सरल तरीके के रूप में।
ध्यान दें कि यदि आप रेल कंसोल में इसका प्रयोग कर रहे हैं, तो आपको 'इको' मोड को बंद करना होगा। अन्यथा, कंसोल (या तो irb या pry) कॉल करता है .inspect
ऑब्जेक्ट पर एक बार जब आप 'एंटर' दबाते हैं, जो एक डीबी क्वेरी को मजबूर करता है। इको मोड को अक्षम करने के लिए, आप निम्न कोड का उपयोग कर सकते हैं:
conf.echo = false # for irb
pry_instance.config.print = proc {} # for pry
ActiveRecord संबंध
ActiveRecord के अंतर्निर्मित कैशिंग का पहला भाग जिसे हम देखेंगे वह संबंध है। उदाहरण के लिए, हमारे पास एक विशिष्ट User-Posts
. है संबंध:
# app/models/user.rb
class User < ApplicationRecord
has_many :posts
end
# app/models/post.rb
class Post < ApplicationRecord
belongs_to :user
end
यह हमें आसान user.posts
. देता है और post.user
संबंधित रिकॉर्ड खोजने के लिए डेटाबेस क्वेरी करने के तरीके। मान लें कि हम इन्हें नियंत्रक में उपयोग कर रहे हैं और देखें:
# app/controllers/posts_controller.rb
class PostsController < ApplicationController
def index
@user = User.find(params[:user_id])
@posts = @user.posts
end
...
# app/views/posts/index.html.erb
...
<%= render 'shared/sidebar' %>
<% @posts.each do |post| %>
<%= render post %>
<% end %>
# app/views/shared/_sidebar.html.erb
...
<% @posts.each do |post| %>
<li><%= post.title %></li>
<% end %>
हमारे पास एक बुनियादी index
है कार्रवाई जो @user.posts
. को पकड़ लेती है . पिछले खंड की तरह, इस बिंदु पर डेटाबेस क्वेरी नहीं चलाई गई है। रेल तब हमारे index
. को रेंडर करती है देखें, जो बदले में, साइडबार प्रस्तुत करता है। साइडबार @posts.each ...
. पर कॉल करता है , और इस बिंदु पर, ActiveRecord डेटा प्राप्त करने के लिए डेटाबेस क्वेरी को बंद कर देता है।
फिर हम अपने शेष index
. पर वापस आ जाते हैं टेम्प्लेट, जहां हमारे पास दूसरा है @posts.each
; हालाँकि, इस बार, कोई डेटाबेस कॉल नहीं है। क्या हो रहा है कि ActiveRecord हमारे लिए इन सभी पोस्ट को कैशिंग कर रहा है और डेटाबेस से दोबारा पढ़ने की कोशिश करने से परेशान नहीं है।
एस्केप हैच
ऐसे समय होते हैं जब हम ActiveRecord को संबंधित रिकॉर्ड फिर से लाने के लिए बाध्य करना चाहते हैं; शायद, हम जानते हैं कि इसे किसी अन्य प्रक्रिया (उदाहरण के लिए पृष्ठभूमि नौकरी) द्वारा बदला जा रहा है। एक अन्य सामान्य स्थिति स्वचालित परीक्षणों में होती है जहां हम यह सत्यापित करने के लिए डेटाबेस में नवीनतम मान प्राप्त करना चाहते हैं कि कोड ने इसे सही तरीके से अपडेट किया है।
स्थिति के आधार पर ऐसा करने के दो सामान्य तरीके हैं। मुझे लगता है कि सबसे आम तरीका बस .reload
. पर कॉल करना है एसोसिएशन पर, जो ActiveRecord को बताता है कि हम जो कुछ भी कैश किया है उसे अनदेखा करना चाहते हैं और डेटाबेस से नवीनतम संस्करण प्राप्त करना चाहते हैं:
@user = User.find(1)
@user.posts # DB Call
@user.posts # Cached, no DB call
@user.posts.reload # DB call
@user.posts # Cached new version, no DB call
एक अन्य विकल्प केवल ActiveRecord मॉडल का एक नया उदाहरण प्राप्त करना है (उदाहरण के लिए, find
पर कॉल करके फिर से):
@user = User.find(1)
@user.posts # DB Call
@user.posts # Cached, no DB call
@user = User.find(1) # @user is now a new instance of User
@user.posts # DB Call, no cache in this instance
कैशिंग संबंध अच्छा है, लेकिन हम अक्सर जटिल .where(...)
. के साथ समाप्त होते हैं साधारण संबंध लुकअप से परे प्रश्न। यहीं पर ActiveRecord का SQL कैश आता है।
ActiveRecord का SQL कैश
ActiveRecord प्रदर्शन को गति देने के लिए किए गए प्रश्नों का एक आंतरिक कैश रखता है। हालाँकि, ध्यान दें कि यह कैश विशेष क्रिया से जुड़ा हुआ है; यह क्रिया की शुरुआत में बनाया जाता है और कार्रवाई के अंत में नष्ट हो जाता है। इसका मतलब है कि आप इसे केवल तभी देखेंगे जब आप एक ही क्वेरी को एक नियंत्रक कार्रवाई के भीतर दो बार निष्पादित कर रहे हों। यह भी इसका मतलब है कि रेल कंसोल में कैश का उपयोग नहीं किया जाता है। कैश हिट को रेल लॉग में CACHE
. के साथ दिखाया जाता है . उदाहरण के लिए,
class PostsController < ApplicationController
def index
...
Post.all.to_a # to_a to force DB query
...
Post.all.to_a # to_a to force DB query
निम्न लॉग आउटपुट उत्पन्न करता है:
Post Load (2.1ms) SELECT "posts".* FROM "posts"
↳ app/controllers/posts_controller.rb:11:in `index'
CACHE Post Load (0.0ms) SELECT "posts".* FROM "posts"
↳ app/controllers/posts_controller.rb:13:in `index'
आप ActiveRecord::Base.connection.query_cache
को प्रिंट करके किसी क्रिया के लिए वास्तव में कैशे के अंदर क्या है, इस पर एक नज़र डाल सकते हैं (या ActiveRecord::Base.connection.query_cache.keys
केवल SQL क्वेरी के लिए)।
एस्केप हैच
SQL कैश को बायपास करने के लिए शायद आपको कई कारणों की आवश्यकता नहीं होगी, लेकिन फिर भी, आप ActiveRecord को इसके SQL कैश को बायपास करने के लिए uncached
का उपयोग करके बाध्य कर सकते हैं। ActiveRecord::Base
पर विधि :
class PostsController < ApplicationController
def index
...
Post.all.to_a # to_a to force DB query
...
ActiveRecord::Base.uncached do
Post.all.to_a # to_a to force DB query
end
चूंकि यह ActiveRecord::Base
. पर एक विधि है , यदि यह पठनीयता में सुधार करता है तो आप इसे अपने मॉडल वर्गों में से एक के माध्यम से भी कॉल कर सकते हैं; उदाहरण के लिए,
Post.uncached do
Post.all.to_a
end
काउंटर कैश
वेब अनुप्रयोगों में किसी रिश्ते में रिकॉर्ड्स की गणना करना बहुत आम है (उदाहरण के लिए, उपयोगकर्ता के पास एक्स पोस्ट हैं या टीम खाते में वाई उपयोगकर्ता हैं)। यह कितना सामान्य है, इसके कारण ActiveRecord में स्वचालित रूप से एक काउंटर अप-टू-डेट रखने का एक तरीका शामिल है ताकि आपके पास .count
का एक गुच्छा न हो। डेटाबेस संसाधनों का उपयोग कर कॉल करता है। इसे सक्षम करने के लिए केवल कुछ चरणों की आवश्यकता होती है। सबसे पहले, हम counter_cache
add जोड़ते हैं रिश्ते के लिए ताकि ActiveRecord हमारे लिए गिनती को कैश करना जानता है:
class Post < ApplicationRecord
belongs_to :user, counter_cache: true
end
हमें User
. में एक नया कॉलम भी जोड़ना होगा , जहां गिनती संग्रहीत की जाएगी। हमारे उदाहरण में, यह होगा User.posts_count
. आप counter_cache
. पर एक सिंबल पास कर सकते हैं यदि आवश्यक हो तो कॉलम नाम निर्दिष्ट करने के लिए।
rails generate migration AddPostsCountToUsers posts_count:integer
rails db:migrate
काउंटर अब 0 (डिफ़ॉल्ट) पर सेट हो जाएंगे। यदि आपके एप्लिकेशन में पहले से ही कुछ पोस्ट हैं, तो आपको उन्हें अपडेट करना होगा। ActiveRecord एक reset_counters
प्रदान करता है बारीक-बारीक विवरणों को संभालने के लिए विधि, इसलिए आपको बस इसे आईडी पास करने और यह बताने की जरूरत है कि किस काउंटर को अपडेट करना है:
User.all.each do |user|
User.reset_counters(user.id, :posts)
end
अंत में, हमें उन स्थानों की जाँच करनी होगी जहाँ इस गणना का उपयोग किया जा रहा है। ऐसा इसलिए है क्योंकि कॉल करना .count
. है काउंटर को बायपास करेगा और हमेशा एक COUNT()
चलाएगा एसक्यूएल क्वेरी। इसके बजाय, हम .size
. का उपयोग कर सकते हैं , जो मौजूद होने पर काउंटर कैश का उपयोग करना जानता है। एक तरफ, आप डिफ़ॉल्ट रूप से .size
. का उपयोग करना चाह सकते हैं हर जगह, क्योंकि यह संघों को फिर से लोड नहीं करता है यदि वे पहले से मौजूद हैं, संभावित रूप से डेटाबेस की यात्रा को सहेजते हैं।
निष्कर्ष
अधिकांश भाग के लिए, ActiveRecord का आंतरिक कैशिंग "बस काम करता है"। मैं यह नहीं कह सकता कि मैंने कई मामलों को देखा है जिन्हें इसे बाईपास करने की आवश्यकता है, लेकिन जैसा कि सभी चीजों के साथ होता है, "अंडर-द-हुड" पर क्या होता है, यह जानने से आपको कुछ समय और पीड़ा से बचाया जा सकता है जब आप ऐसी स्थिति में ठोकर खाते हैं जिसके लिए कुछ की आवश्यकता होती है सामान्य से बाहर।
बेशक, डेटाबेस एकमात्र ऐसा स्थान नहीं है जहां रेल हमारे लिए कुछ पर्दे के पीछे कैशिंग कर रही है। HTTP विनिर्देश में हेडर शामिल हैं जिन्हें क्लाइंट और सर्वर के बीच भेजा जा सकता है ताकि डेटा को फिर से भेजने से बचा जा सके जो नहीं बदला है। कैशिंग पर इस श्रृंखला के अगले लेख में, हम 304 (Not Modified)
पर एक नज़र डालेंगे। एचटीटीपी स्थिति कोड, रेल आपके लिए इसे कैसे संभालती है, और आप इस हैंडलिंग को कैसे बदल सकते हैं।