Giảm số lượng các câu lệnh queries bằng gem Bullet
Bài đăng này đã không được cập nhật trong 3 năm
I. Bullet
1. Giới thiệu
- Gem Bullet được thiết kể để giúp các application tăng hiệu năng bằng cách giảm các câu lệnh queries của app đó. Bullet sẽ xem xét các câu lệnh queries từ lúc bạn develop sản phẩm và thông báo cho bạn khi bạn sử đã bị N+1 queries, khi bạn sử dụng bộ nhớ cache.
- Bạn nên sử dụng Bullet trong môi trường development hoặc các custom mode khác như staging, profile, etc.
- Bullet gem hiện tại đang support cho activerecord >= 3.0 và mongoid >= 2.4.1.
- Nếu bạn sử dụng activerecord 2.x thì hãy sử dụng bullet <= 4.5.0.
2. Install
- Thêm vào
Gemfile
:gem 'bullet', group: :development
- Hoặc install bằng terminal:
gem install bullet
3. Configuration
- Bullet sẽ không làm gì cả nếu bạn ko khai báo cho nó 1 cách rõ ràng trong
config/environments/development.rb
config.after_initialize do
Bullet.enable = true
Bullet.alert = true
Bullet.bullet_logger = true
Bullet.console = true
Bullet.growl = true
Bullet.xmpp = { :account => 'bullets_account@jabber.org',
:password => 'bullets_password_for_jabber',
:receiver => 'your_account@jabber.org',
:show_online_status => true }
Bullet.rails_logger = true
Bullet.honeybadger = true
Bullet.bugsnag = true
Bullet.airbrake = true
Bullet.rollbar = true
Bullet.add_footer = true
Bullet.stacktrace_includes = [ 'your_gem', 'your_middleware' ]
Bullet.stacktrace_excludes = [ 'their_gem', 'their_middleware' ]
Bullet.slack = { webhook_url: 'http://some.slack.url', foo: 'bar' }
end
- Thông báo của Bullet được support bởi uniform_notifier
Những dòng code dưới đây sẽ enable tất cả 7 các thông báo của Bullet
Bullet.enable
: enable Bullet gem, nếu không thì không làm gì cảBullet.alert
: popup ra JavaScript alert trên trình duyệtBullet.bullet_logger
: log Bullet's log ra file (Rails.root/log/bullet.log
)Bullet.rails_logger
: thêm các warnings trực tiếp vào rails console logBullet.honybadger
: thêm notifications vào HoneybatgerBullet.bugsnag
: thêm notifications vào bugsnagBullet.airbrake
: thêm notifications vào airbrakeBullet.rollbar
: thêm notifications vào rollbarBullet.console
: log warnings vào console của trình duyệtBullet.growl
: popup Growl warnings nếu hệ thống có cài đặt GrowlBullet.xmpp
: gửi XMPP/Jabber notifications tới người nhận. Chú ý rằng code mặc định sẽ không handle các contacts đang được add, bởi vậy sẽ cần phải làm cả 2 accounts được xác nhận trước khi bạn nhận bất kì thông báo nào. Nếu bạn set choshow_online_status: false
thì bạn vẫn nhận được thông báo nhưng Bullet account sẽ không hiện online status nữaBulelt.raise
raise error, nếu bạn không tối ưu hóa thì sẽ dễ làm cho hệ thống sai về specsBullet.add_footer
: thêm thông tim chi tiết vào phía trái bên dưới góc của pageBullet.stacktrace_includes
: thêm đường dẫn với substrings vào stack trace, thậm chí nếu chúng không ở main appBullet.stacktrace_excludes
: bỏ qua đường dẫn với substrings vào stack trace, thậm chí nếu chúng không ở main appBullet.slack
: thêm notifications vào slack
Nhưng Bullet cũng cho phép bạn disable các chức năng detectors
# Each of these settings defaults to true
# Detect N+1 queries
Bullet.n_plus_one_query_enable = false
# Detect eager-loaded associations which are not used
Bullet.unused_eager_loading_enable = false
# Detect unnecessary COUNT queries which could be avoided
# with a counter_cache
Bullet.counter_cache_enable = false
4. Whitelist
- Nhưng thỉnh thoảng Bullet có thể thông báo cho bạn về vấn đề queries cái mà bạn không quan tâm fix, hoặc những cái đến từ ngoài code của bạn. Bạn có thể thêm vào whitelist để bỏ qua nó:
- Ví dụ:
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
- Nếu bạn muốn bỏ qua bullet trong các controllers thì bạn có thể làm như thế này:
class ApplicationController < ActionController::Base
around_action :skip_bullet
def skip_bullet
Bullet.enable = false
yield
ensure
Bullet.enable = true
end
end
5. Log
Bullet's log sẽ trông như thế này
- N+1 Query
# log/bullet.log
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'
Hai dòng đầu tiên là những thông báo về N+1 queries. Những dòng tiếp theo là nói tới chỗ nào trong code của bạn của lỗi đó, từ đó bạn có thể tìm nó và sửa
- Unused eager loading:
2009-08-25 20:53:56[INFO] Unused eager loadings: PATH_INFO: /posts; model: Post => associations: [comments]·
Remove from your finder: :include => [:comments]
Important
Nếu bạn không thấy Bullet hoạt động thì hãy tắt cache trình duyệt mà các bạn đang sử dụng.
II. Demo
1. Tạo 1 application test
$ rails new test_bullet
$ cd test_bullet
$ rails g scaffold post name:string
$ rails g scaffold comment name:string post_id:integer
$ bundle exec rake db:migrate
2. Change app/model/post.rb
and app/model/comment.rb
class Post < ActiveRecord::Base
has_many :comments
end
class Comment < ActiveRecord::Base
belongs_to :post
end
3. Bật rails c
để thêm dữ liệu
post1 = Post.create(:name => 'first')
post2 = Post.create(:name => 'second')
post1.comments.create(:name => 'first')
post1.comments.create(:name => 'second')
post2.comments.create(:name => 'third')
post2.comments.create(:name => 'fourth')
4. Thay đổi app/views/posts/index.html.erb
để làm xuất hiện N+1 query
<% @posts.each do |post| %>
<tr>
<td><%= post.name %></td>
<td><%= post.comments.map(&:name) %></td> # Ta thêm dòng này
<td><%= link_to 'Show', post %></td>
<td><%= link_to 'Edit', edit_post_path(post) %></td>
<td><%= link_to 'Destroy', post, :confirm => 'Are you sure?', :method => :delete %></td>
</tr>
<% end %>
5. Ta add gem bullet
vào Gemfile
gem 'bullet'
sau đó chạy bundle install
6. Ta config Bullet trong config/environments/development.rb
config.after_initialize do
Bullet.enable = true
Bullet.alert = true
Bullet.bullet_logger = true
Bullet.console = true
# Bullet.growl = true
Bullet.rails_logger = true
Bullet.add_footer = true
end
7. Chạy server
rails s
8. Vào trình duyệt rồi đi tới http://localhost:3000/posts
, ta sẽ thấy được 1 popup
- JavaScript alert
- Log server:
9. Sửa N+1 query
Ta sửa file app/controllers/posts_controller.rb
:
def index
@posts = Post.includes(:comments)
respond_to do |format|
format.html # index.html.erb
format.xml { render :xml => @posts }
end
end
10. Tải lại trang http://localhost:3000/posts
. Ta không thấy bất kỳ 1 popup nào được hiện lên
- Log server:
=> Vậy là N+1 query đã được sửa.
11. Mô phỏng unused eager loading
- Thay đổi các file
# app/controllers/posts_controller.rb
def index
@posts = Post.includes(:comments)
respond_to do |format|
format.html # index.html.erb
format.xml { render :xml => @posts }
end
end
# app/views/posts/index.html.erb
<% @posts.each do |post| %>
<tr>
<td><%= post.name %></td>
<td><%= link_to 'Show', post %></td>
<td><%= link_to 'Edit', edit_post_path(post) %></td>
<td><%= link_to 'Destroy', post, :confirm => 'Are you sure?', :method => :delete %></td>
</tr>
<% end %>
12. Tải lại trang http://localhost:3000/posts
- Ta thấy xuất hiện 1 popup
- Sửa:
# app/controllers/posts_controller.rb
def index
@posts = Post.all
respond_to do |format|
format.html # index.html.erb
format.xml { render :xml => @posts }
end
end
III. Tài liệu tham khảo
All rights reserved