Rails Automatic eager loading với Goldiloader
This post hasn't been updated for 6 years
Introduction
Trong khi phát triển dự án của mình, mọi người ai cũng gặp vấn đề N+1 query, và thường giải quyết nó bằng cách sử dụng eager_load/includes ... Như vậy, nó cũng không tiện cho lắm và có thể không eager_load hết các trường hợp.
Để giải quyết vấn đề này, mình đã tìm hiểu về gem Goldiloader có khả năng tự động eager load cho mình.
Installation
gem 'goldiloader'
$ bundle install
Usage
Các association sẽ mặc định tự đông eager load cho mình, không cần phải config gì thêm hết. Tuy nhiên bạn vẫn có thể dùng eager_load
, includes
hoặc preload
như bình thường.
Nếu bạn muốn disable việc eager load tự động này, bạn có thể dùng như sau:
- Với scope method
Blog.order(:name).auto_include(false)
- Với association
class Blog < ActiveRecord::Base
has_many :posts, -> { auto_include(false) }
end
fully_load
Trong một số trường hợp như sau:
blogs = Blogs.limit(5).to_a
# SELECT * FROM blogs LIMIT 5
blogs.each do |blog|
if blog.posts.exists?
puts blog.posts
else
puts 'No posts'
end
end
# SELECT 1 AS one FROM posts WHERE blog_id = 1 LIMIT 1
# SELECT * FROM posts WHERE blog_id IN (1,2,3,4,5)
Như ví dụ trên mỗi lần gọi blog.posts.exists?
nó sẽ thực hiện query SQL.
Các method như này bao gồm:
first
second
third
fourth
fifth
last
size
ids_reader
empty?
exists?
Để tránh những trường hợp trên, mình cần sử dụng thêm fully_load vào association:
class Blog < ActiveRecord::Base
has_many :posts, fully_load: true
end
Full Example
- mình có các model như sau:
class Blog < ActiveRecord::Base
has_many :posts
end
class Post < ActiveRecord::Base
belongs_to :blog
belongs_to :author
end
class Author < ActiveRecord::Base
has_many :posts
end
- PostControllers có index method như sau:
class PostsController < ApplicationController
def index
@blog = Blog.find(params[:blog_id])
@posts = @blog.posts.order(published_at: :desc).limit(5)
end
end
- View index
<h1>Recent <%= @blog.name %> Posts</h1>
<ul>
<% @posts.each do |post| %>
<li><%= post.title %> - <%= post.author.name %><li>
<% end %>
<ul>
- Chạy và xem log
SELECT * FROM posts WHERE blog_id = 1 ORDER BY published_at DESC LIMIT 5
SELECT * FROM authors WHERE id IN (1,2,3,4,5)
Các eager load đã tự động sẵn cho mình.
Lưu ý
Eager load trong rails có một số hạn chế mà không thể eager load được. như limit, offset, ...
Nhự vậy gem này cũng có limitation tương tự, tức là không thể tự động eager load với những cái hạn chế này.
Conclusion
Xác định và sử dụng eager load đúng cách sẽ làm cho performance tốt, nếu dùng không đúng có thể ảnh hưởng đến performance. Với gem goldiloader đã tạo cho bạn tự động việc eager load một cách nhanh chống. Bạn có thể tham khao thêm tài liệu sau đây.
References:
https://github.com/salsify/goldiloader
https://www.salsify.com/blog/engineering/automatic-eager-loading-rails
All Rights Reserved