जब आपका डेटा मॉडल जटिल हो जाता है, और आपके एपीआई उस दु:खद 1 सेकंड प्रतिक्रिया समय पर हिट करते हैं, तो आमतौर पर एक आसान समाधान होता है::includes
. जब आप अपने मॉडल की संबद्धता को प्रीलोड करते हैं, तो आप उतनी SQL कॉल नहीं करेंगे। और इससे आपका बहुत सारा समय बच सकता है।
लेकिन फिर आपकी साइट धीमी हो जाती है, और आप कैशिंग प्रतिक्रियाओं के बारे में सोचते हैं। और अब आपको समस्या है। क्योंकि अगर आप कैशे से प्रतिक्रिया प्राप्त करना चाहते हैं:
results = {lawyer_1: 1, lawyer_2: 2, lawyer_3: 3}
cached_objects = Rails.cache.fetch_multi(results.keys) do |key|
Lawyer.find(results[key]).as_json
end
अब आप अपना सारा :includes
. खो चुके हैं . क्या आपके पास दोनों हो सकते हैं? आप अपने कैश्ड ऑब्जेक्ट के लिए तेज़ प्रतिक्रिया कैसे प्राप्त करते हैं, और फिर भी उन ऑब्जेक्ट्स को जल्दी से लोड कैसे करते हैं जो कैश में नहीं हैं?
करने के लिए बहुत कुछ है, इसलिए इसके बारे में सोचना कठिन है। जब आप समस्या को छोटे-छोटे टुकड़ों में बांटते हैं, और एक आसान अगले चरण के साथ आते हैं तो यह आसान हो जाता है।
तो आप सबसे पहले क्या कर सकते हैं? कुछ भी करने के लिए, आपको यह जानना होगा कि आपके कैश में कौन सी वस्तुएं हैं, और आपको अभी भी किन वस्तुओं को खोजने की आवश्यकता है।
कैश्ड को कैश न किए गए से अलग करें
तो, मान लें कि आपके पास कैश कुंजियों का एक गुच्छा है:
cache_keys = [:key_1, :key_2, :key_3]
आप कैसे बता सकते हैं कि इनमें से कौन-सा कैश में है?
ActiveSupport::Cache
read_multi
. नामक एक आसान तरीका है :
# When only lawyer_1 is cached
cache_keys = [:lawyer_1, :lawyer_2, :lawyer_3]
Rails.cache.read_multi(cache_keys) # => {:lawyer_1 => {"id": 1, "name": "Bob the Lawyer"} }
read_multi
{key: value}
. का हैश लौटाता है कैश में मिली प्रत्येक कुंजी के लिए। लेकिन आप उन सभी कुंजियों को कैसे खोजेंगे जो नहीं हैं कैश में? आप इसे सीधे तरीके से कर सकते हैं:सभी कैश कुंजियों के माध्यम से लूप करें और पता करें कि कौन सी हैश में नहीं हैं read_multi
रिटर्न:
cache_keys = [:lawyer_1, :lawyer_2, :lawyer_3]
uncached_keys = []
cached_keys_with_values = Rails.cache.read_multi(cache_keys)
cache_keys.each do |key|
uncached_keys << key unless cached_keys_with_values.has_key?(key)
end
तो, अब आपके पास क्या है?
- सभी कैश कुंजियों की एक सरणी जिसके लिए आप ऑब्जेक्ट चाहते थे।
{key: value}
. का हैश कैश में मिली प्रत्येक वस्तु के लिए जोड़े।- उन कुंजियों की सूची जो कैश में नहीं थीं।
और आपको आगे क्या चाहिए?
- मान उन चाबियों के लिए जो कैश में नहीं थीं। अधिमानतः सभी को एक साथ लाया गया।
यह आपका अगला कदम है।
अनकैश्ड मान प्रीलोड करें
जल्द ही, आपको कैश कुंजी का उपयोग करके एक वस्तु ढूंढनी होगी। चीजों को आसान बनाने के लिए, आप कोड को कुछ इस तरह बदल सकते हैं:
cache_identifiers = {lawyer_1: 1, lawyer_2: 2, lawyer_3: 3}
cache_keys = cache_identifiers.keys
uncached_keys = []
cached_keys_with_values = Rails.cache.read_multi(cache_keys)
cache_keys.each do |key|
uncached_keys << key unless cached_keys_with_values.has_key?(key)
end
तो cache_identifiers
अब कैश कुंजी का ट्रैक रखता है और लाने के लिए ऑब्जेक्ट आईडी।
अब, आपकी कैश न की गई कुंजियों के साथ:
uncached_keys # => [:lawyer_2, :lawyer_3]
और आपके cache_identifiers
हैश:
cache_identifiers # => {lawyer_1: 1, lawyer_2: 2, lawyer_3: 3}
आप उन सभी वस्तुओं को एक साथ ला सकते हैं, प्रीलोड कर सकते हैं और क्रमबद्ध कर सकते हैं:
uncached_ids = uncached_keys.map { |key| cache_identifiers[key] }
uncached_lawyers = Lawyer.where(id: uncached_ids)
.includes([:address, :practice_areas, :awards, ...])
.map(&:as_json))
तो अब आपके पास क्या है?
- उन सभी कैश कुंजियों की एक सरणी जिनके साथ आप ऑब्जेक्ट शुरू करना चाहते थे।
{key: value}
. का हैश कैश में मिली प्रत्येक वस्तु के लिए जोड़े।- उन कुंजियों की सूची जो कैश में नहीं थीं।
- वे सभी मान जो संचय में नहीं पाए गए।
और आपको आगे क्या चाहिए?
- आपके द्वारा अभी प्राप्त किए गए सभी मानों को कैश करने के लिए, ताकि आपको अगली बार इस पूरी प्रक्रिया से न गुजरना पड़े।
- आपके सभी ऑब्जेक्ट की अंतिम सूची, चाहे वे कैश से आए हों या नहीं।
कैश न किए गए मानों को संचित करें
आपके पास दो सूचियाँ हैं:एक अनकैश्ड कुंजियों की सूची और दूसरी अनकैश्ड मानों की। लेकिन उन्हें कैश करना आसान होगा यदि आपके पास एक . होता [key, value]
. की सूची जोड़े, ताकि आपका value
इसकी key
. के ठीक बगल में है . यह मेरी पसंदीदा विधियों में से एक का उपयोग करने का एक बहाना है, zip
:
[1, 2, 3].zip(["a", "b", "c"]) # => [[1, "a"], [2, "b"], [3, "c"]]
zip
के साथ , आप अपने प्राप्त मूल्यों को आसानी से कैश कर सकते हैं:
uncached_keys.zip(uncached_lawyers).each do |key, value|
Rails.cache.write(key, value)
end
अब आपके पास क्या है?
- उन सभी कैश कुंजियों की एक सरणी जिनके साथ आप ऑब्जेक्ट शुरू करना चाहते थे।
{key: value}
. का हैश कैश में मिली प्रत्येक वस्तु के लिए जोड़े।- पूर्व में कैश नहीं किए गए मानों की सूची जिन्हें आपने अभी-अभी कैश किया है।
और आपको अब भी क्या चाहिए?
- आपके सभी ऑब्जेक्ट की एक बड़ी सूची, चाहे वे कैश से आए हों या नहीं।
सभी को एक साथ लाएं
अब, आपके पास कैश कुंजियों की एक आदेशित सूची है:
cache_keys = cache_identifiers.keys
आपके द्वारा कैश से प्राप्त की गई वस्तुओं की सूची:
cached_keys_with_values = Rails.cache.read_multi(cache_keys)
और उन वस्तुओं की सूची जिन्हें आपने अभी डेटाबेस से पकड़ा है:
uncached_ids = uncached_keys.map { |key| cache_identifiers[key] }
uncached_lawyers = Lawyer.where(id: uncached_ids)
.includes([:address, :practice_areas, :awards, ...])
.map(&:as_json))
अब आपको सब कुछ एक साथ रखने के लिए बस एक आखिरी लूप चाहिए:
results = []
cache_keys.each do |key|
results << cache_keys_with_values[key] || uncached_lawyers.shift
end
यही है, प्रत्येक कैश कुंजी के लिए, आप उस कुंजी के लिए कैश में मिली वस्तु को पकड़ लेते हैं। यदि वह कुंजी मूल रूप से कैश में नहीं थी, तो आप डेटाबेस से खींची गई अगली वस्तु को पकड़ लेते हैं।
उसके बाद, आपका काम हो गया!
यहाँ पूरी चीज़ कैसी दिखती है:
cache_identifiers = {lawyer_1: 1, lawyer_2: 2, lawyer_3: 3}
cache_keys = cache_identifiers.keys
uncached_keys = []
# Fetch the cached values from the cache
cached_keys_with_values = Rails.cache.read_multi(cache_keys)
# Create the list of keys that weren't in the cache
cache_keys.each do |key|
uncached_keys << key unless cached_keys_with_values.has_key?(key)
end
# Fetch all the uncached values, in bulk
uncached_ids = uncached_keys.map { |key| cache_identifiers[key] }
uncached_lawyers = Lawyer.where(id: uncached_ids)
.includes([:address, :practice_areas, :awards, ...])
.map(&:as_json))
# Write the uncached values back to the cache
uncached_keys.zip(uncached_lawyers).each do |key, value|
Rails.cache.write(key, value)
end
# Create our final result set from the cached and uncached values
results = []
cache_keys.each do |key|
results << cache_keys_with_values[key] || uncached_lawyers.shift
end
results
क्या यह लायक था? शायद। यह बहुत सारे कोड है। लेकिन यदि आप बहुत सारे संघों के साथ ऑब्जेक्ट कैशिंग कर रहे हैं, तो यह आपको दर्जनों या सैकड़ों SQL कॉल सहेज सकता है। और यह आपके API प्रतिसादों में से एक टन का समय निकाल सकता है।
Avvo में, यह पैटर्न अविश्वसनीय रूप से उपयोगी रहा है:हमारे बहुत से JSON API कैश्ड प्रतिक्रियाओं को अविश्वसनीय रूप से तेज़ी से वापस करने के लिए इसका उपयोग करते हैं।
पैटर्न इतना उपयोगी रहा है कि मैंने इसे जोड़ने के लिए एक रत्न लिखा, जिसे बल्क_कैश_फेचर कहा जाता है। इसलिए यदि आप कभी खुद को बड़े, जटिल डेटा मॉडल को कैश करने का प्रयास करते हुए पाते हैं, तो इसे आज़माएं!