Project

n_one

0.01
No release in over a year
N+1 auto-detection for ActiveRecord and PostgreSQL
2005
2006
2007
2008
2009
2010
2011
2012
2013
2014
2015
2016
2017
2018
2019
2020
2021
2022
2023
2024
 Dependencies

Runtime

 Project Readme

NOne CI

NOne is able to auto-detect N+1 queries with confidence

Note! It is a rewrite of similar library https://github.com/charkost/prosopite. All credits go there.

How it works

NOne 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.

Installation

Add this line to your application's Gemfile:

gem 'pg_query'
gem 'n_one'

And then execute:

$ bundle install

Or install it yourself as:

$ gem install n_one

Development Environment Usage

NOne auto-detection can be enabled on all controllers:

class ApplicationController < ActionController::Base
  unless Rails.env.production?
    around_action do
      NOne.scan! do
        yield
      end
    end
  end
end

Test Environment Usage

And each test can be scanned with:

# spec/spec_helper.rb
config.around(:each) do |example|
  NOne.scan! do
    example.run
  end
end

or with custom code using scan report

# spec/your_spec.rb
it 'has no N+1 queries' do
  n_ones = NOne.scan do
    MyAction.perform(arguments)
  end
  
  expect(n_ones.size).to eq(0)
end

Whitelisting

Ignore notifications for call stacks containing one or more substrings:

NOne.scan!(whitelist: ['myapp/lib/known_n_plus_ones/']) do
  example.run
end

Ignore names

Ignore queries with names:

NOne.scan!(ignore_names: ['SCHEMA']) do
  example.run
end

It will skip schema queries(e.g. for column names of a given table)

Stack trace sanitizing

Sanitize the call stack trace that is used to calculate the query fingerprint:

sanitizer = lambda do |stacktrace|
  stacktrace.reject { |s| s.include?('/active_record/relation/delegation.rb') }
end

NOne.scan!(stacktrace_sanitizer: sanitizer) do
  example.run
end

Consider the following example:

class Foo < ActiveRecord::Base
  def self.bar
    first(5)
  end
end

2.times { Foo.all.bar }

The subsequent Foo.all.bar call here will not be recognized as an N+1 query since it will have a different call stack trace (see the reason here). This can be fixed with the stacktrace_sanitizer option as described above.

Contributing

Bug reports and pull requests are welcome on GitHub at https://github.com/prikha/n_one.

License

NOne is licensed under the Apache License, Version 2.0. See LICENSE.txt for the full license text.