Giảm số lượng các câu lệnh queries bằng gem Bullet
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 ...
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ệt
- Bullet.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 log
- Bullet.honybadger: thêm notifications vào Honeybatger
- Bullet.bugsnag: thêm notifications vào bugsnag
- Bullet.airbrake: thêm notifications vào airbrake
- Bullet.rollbar: thêm notifications vào rollbar
- Bullet.console: log warnings vào console của trình duyệt
- Bullet.growl: popup Growl warnings nếu hệ thống có cài đặt Growl
- Bullet.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 cho show_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ữa
- Bulelt.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ề specs
- Bullet.add_footer: thêm thông tim chi tiết vào phía trái bên dưới góc của page
- Bullet.stacktrace_includes: thêm đường dẫn với substrings vào stack trace, thậm chí nếu chúng không ở main app
- Bullet.stacktrace_excludes: bỏ qua đường dẫn với substrings vào stack trace, thậm chí nếu chúng không ở main app
- Bullet.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