जब आपका डेटा मॉडल जटिल हो जाता है, और आपके APIs उस दुखद 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 APIs इसका उपयोग कैश्ड रिस्पॉन्स को अविश्वसनीय रूप से तेजी से वापस करने के लिए करते हैं।
यह पैटर्न इतना उपयोगी रहा है कि मैंने इसे bulk_cache_fetcher नामक एक जेम में समाहित करने के लिए लिखा। तो यदि आप कभी भी खुद को बड़े, जटिल डेटा मॉडल्स को कैश करने की कोशिश करते हुए पाते हैं, तो इसे आज़माएं!