Gem bullet và config
Bài đăng này đã không được cập nhật trong 7 năm
Giới thiệu
- N+1 query là gì? Giả sử chúng ta có 2 model với quan hệ parent-child, chúng ta cần truy vấn database để load dữ liệu của model "con" thông qua model "cha". Hầu hết việc truy vấn mặc định sử dụng lazy-loading, đồng nghĩa với việc các truy vấn sẽ tìm tới bản ghi "cha" rồi thực hiện từng truy vấn đối với các bản ghi "con". Điều này dẫn đến sẽ thực hiện N+1 truy vấn vào database làm giảm tốc độ load dữ liệu, và có thể làm tràn bộ nhớ. Để kiểm soát vấn đề này, chúng ta sử dụng gem bullet để đưa ra các cảnh báo đối với query N+1 từ đó dễ dàng fix chúng về việc chỉ truy vấn 1 lần để tăng hiệu suất load dữ liệu.
- Bullet gem Bullet gem là một công cụ mạnh trong việc refactor code, hiệu quả với việc tăng tốc độ query vào database trong quá trình development. Bởi vì tính năng thông báo đối với N+1 query, một trong những lỗi phổ biến khi lập trình liên quan đến database nếu người lập trình không kiểm soát tốt nó.
Cài đặt
Chúng ta có thể cài đặt bằng terminal
gem install bullet
hoặc add trong Gemfile
gem 'bullet', group: 'development'
Configuration
Bullet sẽ không làm gì cả trừ khi bạn config cho nó. Trong môi trường config/environments/development.rb chúng ta làm theo đoạn code sau:
config.after_initialize do
Bullet.enable = true
Bullet.bullet_logger = true
Bullet.raise = true # raise an error if n+1 query occurs
Bullet.unused_eager_loading_enable = false
end
đơn giản, config trên làm chức năng, bật bullet, tạo log thông báo khi có n+1, check lỗi n+1 query, đối với config Bullet.unused_eager_loading_enable cảnh báo khi chúng ta sử dụng việc fix query N+1 đối với việc load dữ liệu của model con không qua model cha. Nhưng thực tế thì lại không có case nào đòi hỏi việc load dữ liệu model con cả. Điều đó dẫn đến việc fix là dư thừa. Và config này là để check điều đó.
Whitelist
Đôi khi có một số đoạn code bạn không cần quan tâm đến N+1 query, và không muốn bullet thông báo về nó, thì bạn có thể đưa nó vào whitelist. Ví dụ như
Bullet.add_whitelist :type => :n_plus_one_query, :class_name => "Post", :association => :comments
Bullet.add_whitelist :type => :unused_eager_loading, :class_name => "Post", :association => :comments
Bullet.add_whitelist :type => :counter_cache, :class_name => "Country", :association => :cities
Hoặc bạn cũng có thể sử dụng:
class ApplicationController < ActionController::Base
around_action :skip_bullet
def skip_bullet
Bullet.enable = false
yield
ensure
Bullet.enable = true
end
end
Log
Khi có lỗi liên quan đến bullet ta có thể xem log của nó để biết thêm chi tiết, lỗi đó là lỗi gì log/bullet.log
Đối với lỗi N+1 query
Giả sử bạn có đoạn code
def index
@comments = Post.first.comments
end
ở đây, post và comment có mối quan hệ 1 nhiều. Nếu viết như trên chắc chắn bị báo lỗi N+1 Query
2009-08-25 20:40:17[INFO] N+1 Query: PATH_INFO: /posts; model: Post => associations: [comments]·
Add to your finder: :include => [:comments]
2009-08-25 20:40:17[INFO] N+1 Query: method call stack:·
/Users/richard/Downloads/test/app/views/posts/index.html.erb:11:in `_run_erb_app47views47posts47index46html46erb'
/Users/richard/Downloads/test/app/views/posts/index.html.erb:8:in `each'
/Users/richard/Downloads/test/app/views/posts/index.html.erb:8:in `_run_erb_app47views47posts47index46html46erb'
/Users/richard/Downloads/test/app/controllers/posts_controller.rb:7:in `index'
Cách sửa là includes comment trước khi thực hiện truy vấn
def index
@comments = Post.first.includes(:comments).comments
end
Đối với Unused eager loading:
Giả sử bạn có đoạn code
def index
@post_first = Post.first.includes(:comments)
end
Nhận thấy 1 điều là việc sử dụng includes ở đây là dư thừa vì chúng ta không cần lấy comments để làm gì cả. Và lỗi sẽ là:
2009-08-25 20:53:56[INFO] Unused eager loadings: PATH_INFO: /posts; model: Post => associations: [comments]·
Remove from your finder: :include => [:comments]
Fix đơn giản là remove includes đi.
Bullet trong môi trường test
Phần lớn chúng ta sử dụng môi trường test để test lại tính năng vừa code, và để check những lỗi của bullet khi chạy test thì chúng ta phải config nó:
# spec/spec_helper.rb
if Bullet.enable?
config.before(:each) do
Bullet.start_request
end
config.after(:each) do
Bullet.perform_out_of_channel_notifications if Bullet.notification?
Bullet.end_request
end
end
Ok. chỉ đơn giản vậy, bạ cứ thử có N+1 Query trong code xem, chạy test nó sẽ cảnh báo cho bạn liền.
Kết Luận
Để tăng tốc độ truy vấn, chúng ta cần quan tâm tới vấn đề N+1 query trong quá trình lập trình, và bullet là một người bạn hữu ích giúp bạn kiểm soát điều đó.
Tài liệu tham khảo
All rights reserved