Prosopite is able to auto-detect Rails N+1 queries with zero false positives / false negatives.
N+1 queries detected:
SELECT `users`.* FROM `users` WHERE `users`.`id` = 20 LIMIT 1
SELECT `users`.* FROM `users` WHERE `users`.`id` = 21 LIMIT 1
SELECT `users`.* FROM `users` WHERE `users`.`id` = 22 LIMIT 1
SELECT `users`.* FROM `users` WHERE `users`.`id` = 23 LIMIT 1
SELECT `users`.* FROM `users` WHERE `users`.`id` = 24 LIMIT 1
Call stack:
app/controllers/thank_you_controller.rb:4:in `block in index'
app/controllers/thank_you_controller.rb:3:in `each'
app/controllers/thank_you_controller.rb:3:in `index':
app/controllers/application_controller.rb:8:in `block in <class:ApplicationController>'
The need for prosopite emerged after dealing with various false positives / negatives using the bullet gem.
Prosopite can auto-detect the following extra cases of N+1 queries:
FactoryBot.create_list(:leg, 10)
Leg.last(10).each do |l|
l.chair
end
Leg.last(4).each do |l|
Chair.find(l.chair_id)
end
Chair.last(20).each do |c|
c.legs.first
c.legs.last
c.legs.pluck(:id)
end
Chair.last(20).map{ |c| c.becomes(ArmChair) }.each do |ac|
ac.legs.map(&:id)
end
class Leg::Design
include Mongoid::Document
...
field :cid, as: :chair_id, type: Integer
...
def chair
@chair ||= Chair.where(id: chair_id).first!
end
end
Leg::Design.last(20) do |l|
l.chair
end
Prosopite monitors all SQL queries using the Active Support instrumentation and looks for the following pattern which is present in all N+1 query cases:
More than one queries have the same call stack and the same query fingerprint.
Add this line to your application's Gemfile:
gem 'prosopite'
And then execute:
$ bundle install
Or install it yourself as:
$ gem install prosopite
The preferred type of notifications can be configured with:
Prosopite.rails_logger = true
: Send warnings to the Rails logProsopite.prosopite_logger = true
: Send warnings tolog/prosopite.log
Prosopite.stderr_logger = true
: Send warnings to STDERRProsopite.raise = true
: Raise warnings as exceptions
Prosopite auto-detection can be enabled on all controllers:
class ApplicationController < ActionController::Base
before_action do
Prosopite.scan
end
after_action do
Prosopite.finish
end
end
And the preferred notification channel should be configured:
# config/environments/development.rb
config.after_initialize do
Prosopite.rails_logger = true
end
Tests with N+1 queries can be configured to fail with:
# config/environments/test.rb
config.after_initialize do
Prosopite.rails_logger = true
Prosopite.raise = true
end
And each test can be scanned with:
# spec/spec_helper.rb
config.before do
Prosopite.scan
end
config.after do
Prosopite.finish
end
Ignore notifications for call stacks containing one or more substrings:
Prosopite.whitelist = ['substring_in_call_stack']
All you have to do is to wrap the code with:
Prosopite.scan
<code to scan>
Prosopite.finish
Bug reports and pull requests are welcome on GitHub at https://github.com/charkost/prosopite.
Prosopite is licensed under the Apache License, Version 2.0. See LICENSE.txt for the full license text.