Open-source project
flyerhzm/bullet avatar
flyerhzm/bullet

Bullet: catching N+1 queries and unused eager loading in Rails

help to kill N+1 queries and unused eager loading

7,347 stars460 forksRubyMIT

At a glance

What is it?
Bullet is a Ruby gem that watches the queries your Rails app makes during development and tells you when to add eager loading, when eager loading is wasted, and when a counter cache would be cheaper. It is a development-mode tool, not a production monitor.
Who is it for?
Adopt Bullet if you run Rails or Mongoid with ActiveRecord 4.0 or newer and you want query problems surfaced while you develop rather than in production. Do not adopt it as a production monitoring system: the README says the best practice is development mode or a custom mode such as staging or profile, and it warns that you do not want clients receiving alerts.
Can I use it commercially?
Yes. MIT is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
Is it still maintained?
Yes. The repository last received commits 19 days ago.
What is it written in?
Mainly Ruby, according to GitHub's language statistics.

Answers come from the project's GitHub data, last synced on November 14, 2026, and from our analysis. They are not legal advice.

From daily snapshots

Star history

  • flyerhzm/bullet7,347 ★ · +7
Star history
Repository2026-09-232026-11-14
flyerhzm/bullet7.3k7.3k

7 Stars in the last 52 days, measured from the daily GitHub snapshot. Compare with other projects

Editorial analysis

What Bullet actually catches in a Rails request

An N+1 query pattern is what happens when a controller loads a list of records and then touches an association on each one, producing one query for the list plus one per row. The page works, the tests pass, and the query count scales with the data. Bullet watches the queries your application issues and notifies you when you should add eager loading, when you are using eager loading that is not necessary, and when you should use a counter cache. Those are three distinct detectors, not one. The audience is a Rails or Mongoid developer working locally: the README states that the best practice is to use Bullet in development mode or a custom mode such as staging or profile, and that the last thing you want is your clients getting alerts. That sentence sets the boundary of the tool. Bullet is a development aid that reports on your code as you exercise it, not a runtime guard that protects users.

How the detectors hook into ActiveRecord and Mongoid

Bullet ships as a gem that plugs into the ORM. The README states that it supports activerecord >= 4.0 and mongoid >= 4.0, with older ActiveRecord lines requiring older Bullet releases: 2.x needs bullet <= 4.5.0 and 3.x needs bullet < 5.5.0. The gem's notifier layer is a wrapper around uniform_notifier. Detection is opt-in at two levels. First, Bullet does nothing at all unless you set Bullet.enable = true. Second, each detector can be turned off individually; the README notes that n_plus_one_query_enable and the others default to true. The notification channels are separate flags: Bullet.alert, Bullet.bullet_logger, Bullet.console, Bullet.rails_logger, Bullet.add_footer, and integrations for Sentry, Honeybadger, Bugsnag, AppSignal, Airbrake, Rollbar, Slack, XMPP and OpenTelemetry. Because these are independent switches, you can keep the detection logic identical between a developer laptop and a staging box while changing only where the warnings go.

Installing Bullet and getting a first warning

Bullet installs as a gem. The README gives both a direct gem install and the Bundler form, and the Bundler form is the one you want in a Rails app because the group restricts it to development. Note the ordering requirement the README calls out: make sure the bullet gem is added after activerecord (rails) and mongoid.

ruby
gem 'bullet', group: 'development'

After adding it to the Gemfile, the README points to a generator that writes the default configuration and may ask whether to include the test environment as well.

ruby
bundle exec rails g bullet:install

The generated configuration is not active on its own. Bullet will not enable any notification system unless you tell it to explicitly, so you append an after_initialize block to config/environments/development.rb. A minimal version that logs to the Rails log and to Bullet's own log file looks like this.

ruby
config.after_initialize do
  Bullet.enable = true
  Bullet.bullet_logger = true
  Bullet.rails_logger = true
end

With that in place, exercise a page that lists records and touches an association per row. The README states that Bullet.bullet_logger writes to Rails.root/log/bullet.log, and Bullet.rails_logger adds warnings directly to the Rails log, so that is where the warning appears. If you prefer the warning in the browser instead, add Bullet.alert = true or Bullet.console = true; the README notes that both of those depend on HTML injection, which Bullet.skip_html_injection can disable.

The configuration surface is wide, and that is the cost

The initializer example in the README enables every notification channel at once, including XMPP credentials. That is a demonstration, not a recommendation, and copying it wholesale means a development machine trying to reach external services on every request. Two settings deserve attention before you enable anything else. Bullet.raise turns a detection into a raised error, which the README describes as useful for making specs fail unless they have optimized queries; that is the setting that turns Bullet from advice into a gate, and it belongs in a test environment rather than an interactive console. Bullet.skip_html_injection prevents Bullet from injecting code into the returned HTML, and the README is explicit that it must be false for alerts, the footer or console logging to work. Those two constraints interact: skip_http_headers also removes the JavaScript that relies on the headers, which the README says prevents logging warnings to the browser console or updating the footer. If you enable the footer and it does not appear, footer_position is worth checking, since the default bottom_left can clash with browser UI such as Chrome's URL preview. The stacktrace filters are the other place where configuration earns its keep: stacktrace_includes and stacktrace_excludes accept strings, regexes, or an array pairing a path with a line number, a range, or a method name, which is what lets you silence a warning that originates in a gem you cannot change.

Where Bullet is the wrong tool

Bullet is not a profiler and it is not a production monitor. It reports patterns it recognises in the queries issued during a request, and the README frames it as a development-time aid. If your problem is a single expensive query with a bad plan, or a query that is slow because of missing indexes, Bullet has nothing to say about it. If your problem is load-related, the README's own warning applies: clients should not be receiving these alerts. There is also a coverage limit imposed by the supported versions. The README states that activerecord 2.x requires bullet <= 4.5.0 and activerecord 3.x requires bullet < 5.5.0, so an application on an old Rails line is pinned to an old Bullet. The repository carries separate Gemfile variants for rails-7.0 through rails-8.2 and for mongoid 8.0 and 9.0, which tells you the compatibility matrix is maintained deliberately rather than incidentally. Finally, a detector can only fire on code paths you actually execute. A rarely visited admin page will not report its N+1 until someone opens it.

Bullet against a query-count assertion in tests

The closest alternative is not another gem but a hand-written assertion: count the queries a block makes and fail the test if the number changes. That approach is deterministic and runs in CI without any gem in the development group. Its weakness is that it tells you a number moved, not why, and it only covers the blocks you remembered to wrap. Bullet works the other way round. It inspects queries as they happen and names the association involved, so it explains the cause, but it only sees the requests you make by hand. The README's Bullet.raise setting is the bridge between the two: it converts a detection into a raised error so a spec fails on an unoptimized query. That gives you the explanation of Bullet with the enforcement of a test assertion, at the cost of Bullet being present in the test environment as well as development, which the install generator explicitly asks about.

Maintenance, licence and upgrade cost

The repository is not archived, and the last push was on 2026-10-27, so the project is receiving changes. The repository has no published GitHub releases, so there is no release cadence to describe. The licence is MIT, recorded in the MIT-LICENSE file at the repository root; that permits commercial use and modification, but this is a description of the licence text and not legal advice, and you should read the file yourself if the terms matter to your organisation. Upgrade cost is mostly version arithmetic. Because the README pins older ActiveRecord lines to older Bullet releases, upgrading Rails and upgrading Bullet are coupled decisions, and the Gemfile variants in the repository are the fastest way to see which combinations the maintainers build against. The other recurring cost is configuration drift: because every notification channel is a separate boolean, a stale initializer can keep sending warnings to a Slack channel or an error tracker long after someone stopped reading them.

Editorial conclusion

Adopt Bullet if you run Rails or Mongoid with ActiveRecord 4.0 or newer and you want query problems surfaced while you develop rather than in production. Do not adopt it as a production monitoring system: the README says the best practice is development mode or a custom mode such as staging or profile, and it warns that you do not want clients receiving alerts. Before wiring it in, verify two things: that the gem sits after activerecord or mongoid in your Gemfile, and that your ActiveRecord version matches the compatibility table, since 2.x needs bullet <= 4.5.0 and 3.x needs bullet < 5.5.0.

Frequently asked questions

Does the Bullet gem run in production?

The README states that the best practice is to use Bullet in development mode or a custom mode such as staging or profile, and that the last thing you want is your clients getting alerts. Nothing in the documentation presents it as a production monitoring tool.

Which versions of ActiveRecord and Mongoid does Bullet support?

The README states that Bullet supports activerecord >= 4.0 and mongoid >= 4.0. Applications on activerecord 2.x must use bullet <= 4.5.0, and applications on activerecord 3.x must use bullet < 5.5.0.

Where does the Bullet gem write its warnings?

Bullet.bullet_logger logs to the Bullet log file at Rails.root/log/bullet.log, and Bullet.rails_logger adds warnings directly to the Rails log. The README also documents browser alert, browser console, an on-page footer, and integrations with several error-tracking services.

Can Bullet make my test suite fail on N+1 queries?

Yes. The README describes Bullet.raise as raising errors, useful for making your specs fail unless they have optimized queries. The install generator may also ask whether to include Bullet in the test environment.

Official sources

  1. flyerhzm/bullet on GitHub
  2. Issues
  3. License: MIT
  4. README
Add this badge to your README

If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.

Add this badge to your README

markdown
[![Hysen Labs](https://hysenlabs.com/badge/flyerhzm-bullet.svg)](https://hysenlabs.com/projects/flyerhzm-bullet)

Star history chart for your README

markdown
[![Star history](https://hysenlabs.com/chart/flyerhzm-bullet.svg)](https://hysenlabs.com/projects/flyerhzm-bullet)

Was this useful?