ก่อนหน้านี้เราได้ดูการแคชส่วนย่อยใน Rails บน AppSignal Academy วิธีนี้ช่วยปรับปรุงประสิทธิภาพของการดูได้อย่างมากด้วยการแคชส่วนที่มีขนาดเล็กลง เมื่อแคชบางส่วน เรามีประโยชน์เพิ่มเติมในการนำกลับมาใช้ใหม่ในส่วนอื่นในมุมมองของเราโดยเสียค่าใช้จ่ายเพียงเล็กน้อย
วิธีนี้ใช้ได้ผลดีกับคอลเล็กชันขนาดเล็ก แต่ปัญหาเกิดขึ้นอย่างรวดเร็วในคอลเล็กชันขนาดใหญ่ ในบทความนี้ เราจะมาดูกันว่าการแคชคอลเล็กชัน Rails ทำงานอย่างไร และเราจะใช้เพื่อเร่งการแสดงผลคอลเล็กชันขนาดใหญ่ได้อย่างไร
👋 และหากคุณชอบบทความนี้ ยังมีอีกมากที่เราเขียนเกี่ยวกับประสิทธิภาพของ Ruby (on Rails) ให้ดูที่รายการตรวจสอบการตรวจสอบประสิทธิภาพของ Ruby
การแสดงผลคอลเล็กชัน
เริ่มต้นด้วยตัวควบคุมขนาดเล็กที่โหลด 100 โพสต์ล่าสุดสำหรับหน้าดัชนีของบล็อกของเรา
class PostsController < ApplicationController
def index
@posts = Post.all.order(:created_at => :desc).limit(100)
end
end
หากต้องการแสดงโพสต์เหล่านี้ในมุมมอง เราจะวนซ้ำ @posts
ตัวแปรอินสแตนซ์
<!-- app/views/posts/index.html.erb -->
<h1>Posts</h1>
<div class="posts">
<% @posts.each do |post| %>
<div class="post">
<h2><%= post.title %></h2>
<small><%= post.author %></small>
<div class="body">
<%= post.body %>
</div>
</div>
<% end %>
</div>
เมื่อขอหน้านี้ เราจะเห็นการดึงข้อมูลโพสต์จากฐานข้อมูลและมุมมองที่กำลังแสดงผล หน้านี้ใช้เวลาเพียง 32 มิลลิวินาทีในเลเยอร์การดู หน้านี้ค่อนข้างเร็ว
Started GET "/posts"
Processing by PostsController#index as HTML
Rendering posts/index.html.erb within layouts/application
Post Load (1.5ms) SELECT "posts".* FROM "posts" ORDER BY "posts"."created_at" DESC LIMIT ? [["LIMIT", 100]]
↳ app/views/posts/index.html.erb:4
Rendered posts/index.html.erb within layouts/application (19.4ms)
Completed 200 OK in 37ms (Views: 32.4ms | ActiveRecord: 2.7ms)
การแสดงผลคอลเลกชันที่มีบางส่วน
ต่อไปเราต้องการใช้ post
องค์ประกอบในอีกมุมมองหนึ่ง ดังนั้นเราจึงย้ายโพสต์ HTML เป็นบางส่วน
<!-- app/views/posts/index.html.erb -->
<h1>Posts</h1>
<div class="posts">
<% @posts.each do |post| %>
<%= render post %>
<% end %>
</div>
<!-- app/views/posts/_post.html.erb -->
<div class="post">
<h2><%= post.title %></h2>
<small><%= post.author %></small>
<div class="body">
<%= post.body %>
</div>
</div>
Started GET "/posts"
Processing by PostsController#index as HTML
Rendering posts/index.html.erb within layouts/application
Post Load (1.2ms) SELECT "posts".* FROM "posts" ORDER BY "posts"."created_at" DESC LIMIT ? [["LIMIT", 100]]
↳ app/views/posts/index.html.erb:4
...
Rendered posts/_post.html.erb (0.1ms)
Rendered posts/_post.html.erb (0.1ms)
Rendered posts/index.html.erb within layouts/application (205.4ms)
Completed 200 OK in 217ms (Views: 213.8ms | ActiveRecord: 1.7ms)
ด้วยเวลา 213 มิลลิวินาทีที่ใช้ไปกับเลเยอร์การดู คุณจะเห็นว่าเวลาการเรนเดอร์เพิ่มขึ้นอย่างมาก เนื่องจากจำเป็นต้องโหลด รวบรวม และแสดงผลไฟล์ใหม่ (บางส่วน) สำหรับทุกโพสต์ มาดูกันสั้น ๆ ว่าเราจะสามารถปรับปรุงเวลาในการแสดงผลด้วยการแคชส่วนย่อยได้อย่างไร
การแคชแฟรกเมนต์
ตามที่อธิบายไว้ในบทความการแคชแฟรกเมนต์ เราจะใช้ cache
ผู้ช่วยในมุมมองรอบ ๆ render
เรียก. ด้วยวิธีนี้ เราจะแคชการแสดงผลบางส่วนสำหรับทุกโพสต์
<!-- app/views/posts/index.html.erb -->
<h1>Posts</h1>
<div class="posts">
<% @posts.each do |post| %>
<%= cache post do %>
<%= render post %>
<% end %>
<% end %>
</div>
Started GET "/posts"
Processing by PostsController#index as HTML
Rendering posts/index_with_partial_caching.html.erb within layouts/application
Post Load (1.4ms) SELECT "posts".* FROM "posts" ORDER BY "posts"."created_at" DESC LIMIT ? [["LIMIT", 100]]
↳ app/views/posts/index.html.erb:4
...
Read fragment views/posts/index.1ms)
Rendered posts/_post.html.erb (0.1ms)
Write fragment views/posts/index.1ms)
Read fragment views/posts/index.5ms)
Rendered posts/_post.html.erb (0.1ms)
Write fragment views/posts/index.1ms)
Rendered posts/index.html.erb within layouts/application (274.5ms)
Completed 200 OK in 286ms (Views: 281.4ms | ActiveRecord: 2.4ms)
คำขอแรกจะไม่เร็วขึ้นมากนัก เนื่องจากยังคงต้องแสดงผลทุกส่วนในครั้งแรกและเก็บไว้ในที่เก็บแคช
Started GET "/posts"
Processing by PostsController#index as HTML
Rendering posts/index.html.erb within layouts/application
Post Load (2.2ms) SELECT "posts".* FROM "posts" ORDER BY "posts"."created_at" DESC LIMIT ? [["LIMIT", 100]]
↳ app/views/posts/index.html.erb:4
...
Read fragment views/posts/index.1ms)
Read fragment views/posts/index.1ms)
Rendered posts/index.html.erb within layouts/application (63.8ms)
Completed 200 OK in 78ms (Views: 75.5ms | ActiveRecord: 2.2ms)
ในคำขอที่ตามมา เราเห็นว่าเวลาที่ใช้ในการดูลดลงมาก - จาก 286 มิลลิวินาทีเหลือ 78 มิลลิวินาที ถึงกระนั้นก็ยังช้ากว่าโค้ดเดิมที่เราได้รับมาก ซึ่งช้ากว่าเกือบสองเท่า
หมายเหตุ:หากคุณไม่เห็นบรรทัด "อ่าน/เขียนแฟรกเมนต์" ในบันทึกของคุณ อย่าลืมเปิดใช้งานการบันทึกแคชของแฟรกเมนต์ในสภาพแวดล้อมการพัฒนาของคุณ ซึ่งตั้งค่าเป็น false
โดยค่าเริ่มต้นใน Rails 5.1 ขึ้นไป:
# config/environments/development.rb
config.action_controller.enable_fragment_cache_logging = true
แคชคอลเลคชัน
ใน Rails 5 มีการทำงานมากมายเพื่อทำให้การแคชคอลเลกชันเร็วขึ้น เพื่อใช้ประโยชน์จากการปรับปรุงเหล่านี้ เราจะต้องเปลี่ยนโค้ดการดูของเรา แทนที่จะเรียก cache
ตัวช่วยเอง เราสามารถขอให้ Rails แสดงคอลเลกชันทั้งหมดและแคชพร้อมกันได้
<!-- app/views/posts/index.html.erb -->
<h1>Posts</h1>
<div class="posts">
<%= render partial: :post, collection: @posts, cached: true %>
</div>
หมายเหตุ render @collection, cached: true
ชวเลขจะไม่ทำงานสำหรับการปรับปรุงความเร็วแคชนี้
Started GET "/posts"
Processing by PostsController#index as HTML
Rendering posts/index.html.erb within layouts/application
Post Load (1.4ms) SELECT "posts".* FROM "posts" ORDER BY "posts"."created_at" DESC LIMIT ? [["LIMIT", 100]]
↳ app/views/posts/index.html.erb:4
Rendered collection of posts/_post.html.erb [0 / 100 cache hits] (28.2ms)
Rendered posts/index.html.erb within layouts/application (46.6ms)
Completed 200 OK in 64ms (Views: 59.9ms | ActiveRecord: 2.0ms)
ในคำขอแรก เราสามารถเห็นการปรับปรุงครั้งใหญ่ของเวลาที่ใช้ไปกับเลเยอร์การดู เนื่องจากตอนนี้ Rails ได้เตรียมการไว้ล่วงหน้า บางส่วนถูกใช้สำหรับคอลเลกชันทั้งหมด แทนที่จะใช้สำหรับแต่ละโพสต์แยกกัน
Started GET "/posts"
Processing by PostsController#index as HTML
Rendering posts/index.html.erb within layouts/application
Post Load (1.3ms) SELECT "posts".* FROM "posts" ORDER BY "posts"."created_at" DESC LIMIT ? [["LIMIT", 100]]
↳ app/views/posts/index.html.erb:4
Rendered collection of posts/_post.html.erb [100 / 100 cache hits] (19.2ms)
Rendered posts/index.html.erb within layouts/application (26.5ms)
Completed 200 OK in 37ms (Views: 35.7ms | ActiveRecord: 1.3ms)
ในคำขอที่ตามมา เราเห็นการปรับปรุงมากขึ้น - จาก 64 มิลลิวินาทีลงไปประมาณ 35 มิลลิวินาที การปรับปรุงความเร็วครั้งใหญ่สำหรับคอลเล็กชันทั้งหมดทำได้ที่นี่โดยการเพิ่มประสิทธิภาพ Rails สำหรับคอลเล็กชัน แทนที่จะตรวจสอบความพร้อมใช้งานของแคชสำหรับทุกส่วน Rails จะตรวจสอบคีย์แคชทั้งหมดของคอลเล็กชันพร้อมกัน ซึ่งช่วยประหยัดเวลาในการสืบค้นที่จัดเก็บแคช
ประโยชน์เพิ่มเติมของตัวช่วยแคชนี้คือการบันทึกสรุปของคอลเล็กชัน ในคำขอแรก ไม่พบคีย์แคช [0 / 100 cache hits]
แต่ในคำขอที่ 2 พบทั้งหมด [100 / 100 cache hits]
.
หลังจากอัปเดตออบเจ็กต์บางรายการในฐานข้อมูลแล้ว เราจะเห็นได้ว่าคีย์ที่ค้างอยู่กี่คีย์
Rendered collection of posts/_post.html.erb [88 / 100 cache hits] (13.4ms)
มีการปรับปรุงความเร็วมากมายที่จะได้รับด้วยการแสดงผลและการแคชคอลเลกชันที่ปรับให้เหมาะสมนี้ ความแตกต่างที่ยิ่งใหญ่กว่าจะเกิดขึ้นเมื่อแสดงคอลเล็กชันที่ใหญ่ขึ้น เว้นแต่ว่าคุณต้องการมุมมองที่ปรับแต่งเองสำหรับคอลเล็กชันของคุณ กลยุทธ์ที่ปรับให้เหมาะสมนี้เป็นวิธีที่จะไปสำหรับแอป Rails ของคุณ ที่ AppSignal เราจัดการเพิ่มความเร็วในการดูหนึ่งในผู้ดูแลระบบของเราได้อย่างมาก ซึ่งแสดงบันทึกเป็นพันๆ รายการด้วยวิธีนี้
มีคำถามเกี่ยวกับการแคชคอลเลกชันใน Rails หรือไม่? โปรดอย่าลังเลที่จะแจ้งให้เราทราบที่ @AppSignal! หากคุณมีความคิดเห็นเกี่ยวกับบทความหรือมีหัวข้อที่ต้องการให้เราพูดถึง โปรดติดต่อเรา