เมื่อโมเดลข้อมูลของคุณซับซ้อน และ API ของคุณมีเวลาตอบสนองที่น่าเศร้า 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 ของเราจำนวนมากใช้เพื่อส่งคืนการตอบกลับที่แคชอย่างรวดเร็วอย่างไม่น่าเชื่อ
รูปแบบมีประโยชน์มากจนฉันเขียนอัญมณีเพื่อห่อหุ้มที่เรียกว่า bulk_cache_fetcher ดังนั้น หากคุณเคยพบว่าตัวเองพยายามแคชโมเดลข้อมูลขนาดใหญ่และซับซ้อน ให้ลองดู!