N+1查詢:Rails開發者的隱藏陷阱 (3/3)
使用工具找出隱藏的 N+1
Rails 檢測 N+1 最常見的有幾個套件 Bullet 、n_plus_one_control 、prosopite 等等的 gem。
Bullet
Bullet 在 rspec 常常沒辦法攔截到 N+1,即便頁面上攔截成功,這邊我們直接舉一個例子
# config/environments/test.rb
config.after_initialize do
Bullet.enable = true
Bullet.bullet_logger = true
Bullet.raise = true # raise an error if n+1 query occurs
end
# spec/rails_helper.rb
RSpec.configure do |config|
config.include_context "bullet", bullet: true
config.alias_example_to :bulletify, bullet: true
end
class Post < ApplicationRecord
belongs_to :user
end
class PostsController < ApplicationController
def index
@posts = Post.all
end
end
# view index 頁面
<% @posts.each do |post| %>
<%= post.user.eamil %>
<% end %>
測試中的 Bullet 並沒有抓到這個 N+1 !
# spec/controllers/post_spec.rb
require 'rails_helper'
describe PostsController, type: :controller do
describe "GET #index" do
render_views
let!(:user) { create(:user) }
let!(:posts) { create_list(:post, 5, user: user) }
bulletify { get :index }
end
end
n_plus_one_control 協助找出 N+1
# spec/controllers/post_spec.rb
require 'rails_helper'
describe PostsController, type: :controller do
describe "GET #index" do
render_views
context "N+1", :n_plus_one do
# 使用 populate 方法來產生資料
populate { |n|
user = create(:user)
posts = create_list(:post, n, user: user)
}
it 'performs constant number of queries' do
expect { get :index }.to perform_constant_number_of_queries
end
end
end
end
這時候他就告訴我們當你有3篇文章時,你的頁面執行了4次查詢;而當你有3篇文章時,它執行了2次查詢
Prosopite
初始化設置
# config/environments/development.rb
config.after_initialize do
Prosopite.rails_logger = true
end
# controllers/application_controller.rb
unless Rails.env.production?
before_action do
Prosopite.scan
end
after_action do
Prosopite.finish
end
end
設定非常簡便使用起來我比較喜歡這個,在 log 中就能明顯的看到紅色的 error log
在測試中也可以使用
# config/environments/test.rb
config.after_initialize do
Prosopite.rails_logger = true
Prosopite.raise = true
end
require 'rails_helper'
describe PostsController, type: :controller do
describe "GET #index" do
render_views
context "N+1" do
let!(:user) { create(:user) }
let!(:posts) { create_list(:post, 5, user: user) }
it 'trigger N+1 queries on the index page' do
get :index
end
end
end
end
總結
不管是不是用了套件輔助,在設計階段也可能寫出效能較差的 queries ,只要記得有看 log 的習慣,就能即使修正發生的效能問題。
以上 N+1 文章的參考資料
Rails 實戰聖經 Avoid N+1 queries on rails 這本書裡面介紹了非常詳細,必看!!! 5 ways to fix the latest-comment n+1 problem squash-n-plus-one-queries-early-with-n-plus-one-control-test-matchers-for-ruby-and-rails