วันนี้เราจะเจาะลึกไปที่ 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 ที่เรากล่าวถึงก่อนหน้านี้ในโพสต์