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

แคชตุ๊กตารัสเซียใน Rails

วันนี้เราจะเจาะลึกไปที่ Russian Doll Caching ซึ่งเป็นกลวิธีในการปรับปรุงการแคชของคุณให้มากกว่าการแคชแฟรกเมนต์ในตัวของ Rails

การแคชแฟรกเมนต์

เมื่อใช้การแคชแฟรกเมนต์ในตัวของ Rails ส่วนของมุมมองที่แสดงจะถูกจัดเก็บเป็นแฟรกเมนต์ของมุมมองและนำกลับมาใช้ใหม่หากมีการร้องขออีกครั้ง ชิ้นส่วนที่แคชเหล่านี้ถูกนำมาใช้ซ้ำจนกว่าจะเปลี่ยนเป็น เก่า หมายความว่าล้าสมัยเนื่องจากข้อมูลที่แสดงมีการเปลี่ยนแปลงตั้งแต่สร้างส่วน หากคุณต้องการอ่านเพิ่มเติม ในโพสต์ก่อนหน้านี้ เราจะอธิบายการแคชส่วนย่อยในเชิงลึกอีกเล็กน้อย

👋 และหากคุณต้องการอ่านเกี่ยวกับประสิทธิภาพที่กว้างกว่าแค่การแคช โปรดดูรายการตรวจสอบการตรวจสอบประสิทธิภาพ Ruby ของเรา

แม้ว่าสิ่งนี้จะช่วยเพิ่มความเร็วได้ดี โดยเฉพาะอย่างยิ่งสำหรับมุมมองที่ซับซ้อนหรือมุมมองที่มีการเรนเดอร์บางส่วน เราสามารถปรับปรุงสิ่งนี้ได้มากขึ้นโดยเพิ่มเป็นสองเท่าด้วยวิธีการที่ชื่อ Russian doll caching .

เมื่อใช้วิธีการแคชนี้ ดูแฟรกเมนต์จะวางอยู่ภายในกันและกัน เช่นเดียวกับตุ๊กตา "Matryoshka" ที่กลยุทธ์นี้ตั้งชื่อตาม การแบ่งส่วนย่อยที่แคชเป็นชิ้นเล็ก ๆ จะทำให้แคชภายนอกสามารถแสดงผลได้เร็วขึ้นเมื่อมีการเปลี่ยนแปลงส่วนย่อยที่ซ้อนกันเพียงส่วนเดียว

ตัวอย่างการแคชตุ๊กตารัสเซีย

ตัวอย่างเช่น ลองใช้ร้านค้าที่ขายสินค้า แต่ละผลิตภัณฑ์สามารถมีรายละเอียดปลีกย่อยได้หลายแบบ ซึ่งช่วยให้ขายสินค้าได้หลายสี ตัวอย่างเช่น ในดัชนี เราจะแสดงผลิตภัณฑ์แต่ละรายการที่มีขาย รวมถึงรายละเอียดปลีกย่อยทั้งหมด

ในดัชนีผลิตภัณฑ์ เราได้รวมผลิตภัณฑ์แต่ละส่วนไว้ใน cache บล็อก. เรากำลังใช้ product ออบเจ็กต์เพื่อสร้างคีย์แคช ซึ่งใช้เพื่อทำให้ส่วนย่อยที่แคชใช้ไม่ได้ ประกอบด้วย id ของอ็อบเจ็กต์ เป็น updated_at date และไดเจสต์ของแผนผังเทมเพลต ดังนั้นระบบจะถือว่าเก่าโดยอัตโนมัติหากออบเจ็กต์เปลี่ยนแปลง หรือหากเนื้อหาของเทมเพลตเปลี่ยนไป

# app/views/products/index.html.erb
<h1>Products</h1>
 
<% @products.each do |product| %>
  <% cache product do %>
    <%= render product %>
  <% end %>
<% end %>

เคล็ดลับ :เรากำลังเขียนบล็อกทั้งหมดเพื่อความชัดเจน แต่คุณสามารถแสดงแต่ละผลิตภัณฑ์ในบล็อกแคชได้โดยใช้ <%= render partial: 'products/product', collection: @products, cached: true %> แทน

ในผลิตภัณฑ์บางส่วน เราแสดงแถวสำหรับตัวเลือกสินค้าแต่ละรายการ

# app/views/products/_product.html.erb
<article>
  <h1><%= product.title %></h1>
 
  <ul>
    <% product.variants.each do |variant| %>
      <%= render variant %>
    <% end %>
  </ul>
</article>

แคชใช้ไม่ได้

แม้ว่าคีย์แคชในการแคชแฟรกเมนต์ของ Rails จะทำให้แคชใช้ไม่ได้ผล คุณก็ไม่ต้องกังวลเรื่องการตรวจสอบแคชอย่างสมบูรณ์ (หนึ่งในสองสิ่งที่ยากที่มีชื่อเสียงในด้านวิทยาการคอมพิวเตอร์)

ในตัวอย่างนี้ เราแคชผลิตภัณฑ์บางส่วน ซึ่งมีรายการตัวเลือกสินค้า เนื่องจากคีย์แคชไม่มีข้อมูลใด ๆ เกี่ยวกับตัวเลือกสินค้า ตัวเลือกที่เพิ่มใหม่จะไม่แสดงขึ้นเว้นแต่ตัวผลิตภัณฑ์จะเปลี่ยนแปลงด้วย

วิธีแก้ไขคือต้องแน่ใจว่าผลิตภัณฑ์ ไม่ เปลี่ยนเมื่อมีสิ่งใดเปลี่ยนแปลงในตัวแปรใดรูปแบบหนึ่ง ในการทำเช่นนั้น เราจะอัปเดต updated_at . ของผลิตภัณฑ์ คุณลักษณะเมื่อใดก็ตามที่ที่เกิดขึ้น เนื่องจากเป็นเรื่องปกติ จึงมีอาร์กิวเมนต์สำหรับ belongs_to (และวิธีการสัมพันธ์อื่นๆ ของ ActiveModel) เรียกว่า :touch ที่จะอัปเดตอ็อบเจ็กต์หลัก updated_at ให้เราโดยอัตโนมัติ

class Variant < ApplicationRecord
  belongs_to :product, touch: true
end

เศษที่ซ้อนกัน

ตอนนี้เราได้อัปเดตส่วนย่อยของผลิตภัณฑ์แล้วเมื่อตัวเลือกสินค้าเปลี่ยนแปลง ก็ถึงเวลาแคชตัวเลือกสินค้าด้วยเช่นกัน เหมือนเมื่อก่อน เราจะเพิ่ม cache ปิดกั้นแต่ละอัน

<article>
  <h1><%= product.title %></h1>
 
  <ul>
    <% product.variants.each do |variant| %>
      <% cache(variant) do %>
        <%= render variant %>
      <% end %>
    <% end %>
  </ul>
</article>

เคล็ดลับ :เรากำลังเขียนบล็อกทั้งหมดเพื่อความชัดเจน แต่คุณสามารถแสดงตัวแปรแต่ละตัวในบล็อกแคชได้โดยใช้ <%= render partial: 'variants/variant', collection: product.variants, cached: true %> แทน

บนแคชเย็น (คุณสามารถล้างแคชได้โดยเรียกใช้ rake tmp:cache:clear ) คำขอแรกจะทำให้แต่ละผลิตภัณฑ์มีบางส่วน

เมื่อขอหน้าตอนนี้ (อย่าลืมเปิดแคชในการพัฒนาโดยการเรียกใช้ rails dev:cache ) บางส่วนของผลิตภัณฑ์แต่ละรายการจะถูกแคชเป็นบางส่วน และคำขอที่สองจะส่งคืนส่วนย่อยที่แคชไว้

Started GET "/products" for 127.0.0.1 at 2018-03-30 14:51:38 +0200
Processing by ProductsController#index as HTML
  Rendering products/index.html.erb within layouts/application
  Product Load (0.2ms)  SELECT "products".* FROM "products"
  Variant Load (0.9ms)  SELECT "variants".* FROM "variants" WHERE "variants"."product_id" IN (27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51)
  Rendered variants/_variant.html.erb (0.5ms)
  Rendered variants/_variant.html.erb (0.1ms)
  Rendered variants/_variant.html.erb (0.1ms)
  Rendered variants/_variant.html.erb (0.0ms)
  Rendered variants/_variant.html.erb (0.1ms)
  Rendered variants/_variant.html.erb (0.1ms)
  Rendered variants/_variant.html.erb (0.1ms)
  Rendered variants/_variant.html.erb (0.1ms)
  Rendered variants/_variant.html.erb (0.1ms)
  Rendered variants/_variant.html.erb (0.1ms)
  Rendered products/_product.html.erb (44.8ms) [cache miss]
  ...
  Rendered variants/_variant.html.erb (0.1ms)
  Rendered variants/_variant.html.erb (0.1ms)
  Rendered variants/_variant.html.erb (0.1ms)
  Rendered variants/_variant.html.erb (0.1ms)
  Rendered variants/_variant.html.erb (0.1ms)
  Rendered variants/_variant.html.erb (0.1ms)
  Rendered variants/_variant.html.erb (0.1ms)
  Rendered variants/_variant.html.erb (0.1ms)
  Rendered variants/_variant.html.erb (0.1ms)
  Rendered variants/_variant.html.erb (0.1ms)
  Rendered products/_product.html.erb (46.2ms) [cache miss]
  Rendered products/index.html.erb within layouts/application (1378.6ms)
Completed 200 OK in 1414ms (Views: 1410.5ms | ActiveRecord: 1.1ms)


Started GET "/products" for 127.0.0.1 at 2018-03-30 14:51:41 +0200
Processing by ProductsController#index as HTML
  Rendering products/index.html.erb within layouts/application
  Product Load (0.3ms)  SELECT "products".* FROM "products"
  Variant Load (12.7ms)  SELECT "variants".* FROM "variants" WHERE "variants"."product_id" IN (27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51)
  Rendered products/index.html.erb within layouts/application (48.1ms)
Completed 200 OK in 76ms (Views: 59.0ms | ActiveRecord: 13.0ms)

Voila:มายากลตุ๊กตารัสเซีย

ความมหัศจรรย์ของการแคชตุ๊กตารัสเซียสามารถเห็นได้เมื่อเปลี่ยนหนึ่งในตัวแปร เมื่อขอดัชนีอีกครั้งหลังจากตัวเลือกสินค้าตัวใดตัวหนึ่งเปลี่ยนแปลง ส่วนย่อยของผลิตภัณฑ์ที่แคชไว้จะแสดงผลใหม่เนื่องจาก updated_at เปลี่ยนแอตทริบิวต์แล้ว

บางส่วนของผลิตภัณฑ์รวมถึงตัวเลือกสินค้าแต่ละรายการ ส่วนย่อยที่แคชไว้สำหรับตัวแปรที่เราเพิ่งเปลี่ยนนั้นเก่าแล้ว ดังนั้นจึงจำเป็นต้องสร้างใหม่ แต่ตัวแปรอื่นๆ ไม่เปลี่ยนแปลง ดังนั้นชิ้นส่วนที่แคชไว้จึงถูกนำมาใช้ซ้ำ ในบันทึก เราจะเห็นว่าทั้งตัวเลือกสินค้าและส่วนย่อยของผลิตภัณฑ์แสดงผลเพียงครั้งเดียว

Started GET "/products" for 127.0.0.1 at 2018-03-30 14:52:04 +0200
Processing by ProductsController#index as HTML
  Rendering products/index.html.erb within layouts/application
  Product Load (0.3ms)  SELECT "products".* FROM "products"
  Variant Load (1.2ms)  SELECT "variants".* FROM "variants" WHERE "variants"."product_id" IN (27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51)
  Rendered variants/_variant.html.erb (0.5ms)
  Rendered products/_product.html.erb (13.3ms) [cache miss]
  Rendered products/index.html.erb within layouts/application (45.9ms)
Completed 200 OK in 78ms (Views: 73.5ms | ActiveRecord: 1.5ms)

ผลลัพธ์ที่ได้

ด้วยการซ้อนแฟรกเมนต์แคชในลักษณะนี้ มุมมองแทบจะไม่เคยแสดงผลอย่างครบถ้วน เว้นแต่แคชจะว่างเปล่าทั้งหมด แม้ว่าข้อมูลจะเปลี่ยนไป หน้าที่แสดงผลส่วนใหญ่ก็ยังให้บริการโดยตรงจากแคช

เราหวังว่าสิ่งนี้จะช่วยให้คุณได้รับข้อมูลเชิงลึกใหม่ๆ เกี่ยวกับประสิทธิภาพของแอปของคุณ นั่นเป็นเหตุผลที่เรามาที่นี่ ถ้าคุณชอบบทความนี้เกี่ยวกับการแคช และคุณกำลังหิวกระหายมากขึ้น ของว่างเพิ่มเติมคือโพสต์ของเราเกี่ยวกับ Counter Cache ของ ActiveRecord โพสต์เกี่ยวกับ Cache Stores ใน Rails โพสต์เกี่ยวกับการแคชคอลเลกชั่น และส่วนที่เกี่ยวกับแฟรกเมนต์ การแคชใน Rails ที่เรากล่าวถึงก่อนหน้านี้ในโพสต์