Computer >> คอมพิวเตอร์ >  >> การเขียนโปรแกรม >> Ruby

วิธีที่เร็วกว่าในการแคชโมเดลข้อมูลที่ซับซ้อน

เมื่อโมเดลข้อมูลของคุณซับซ้อน และ 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 ดังนั้น หากคุณเคยพบว่าตัวเองพยายามแคชโมเดลข้อมูลขนาดใหญ่และซับซ้อน ให้ลองดู!