Back to guides

Ruby on Rails

Rails N+1 Queries: How to Detect Them and Fix Them for Good

DEVRUBY engineering team 6 min read

N+1 queries are the most common reason a Rails page that was fast in development becomes slow in production. They are easy to introduce and easy to miss, because each individual query is fast. This guide shows how to find them systematically and how to choose the right fix.

What an N+1 query is

An N+1 happens when code loads a list of records with one query and then runs one more query for each record to fetch an association. A page that lists 50 orders and shows each customer name runs 51 queries instead of 2. With 10 rows nobody notices; with 1,000 rows the page times out.

How to detect them

Combine several signals instead of relying on one.

  • Development logs: repeated identical SELECT statements with different ids are the classic pattern.
  • Bullet: a gem that warns in development when a page triggers N+1 queries or loads associations it never uses.
  • Prosopite: detects N+1 patterns from the queries actually executed, with fewer false positives in complex code.
  • strict_loading: mark a relation, a model or the whole app with strict loading so lazy-loading an association raises an error in tests.
  • Production monitoring: an APM that shows queries per request points to the endpoints worth fixing first.

Choosing the fix

Rails offers three ways to load associations in advance, and the right one depends on what you do with the data.

  • preload runs a separate query per association; it is the safest default when you only display associated data.
  • eager_load uses a single query with LEFT OUTER JOIN; use it when you filter or order by columns of the association.
  • includes lets Rails choose between the two, and switches to eager_load when you reference the association in conditions.

Keeping them from coming back

Fixing today's N+1s is half the job. Enable strict_loading in the test suite or on the models that matter, keep Bullet or Prosopite active in development and CI, and add a request spec that asserts a maximum number of queries for the heaviest pages. Counter caches help for counts, and serializers or view components should receive already-loaded data instead of querying on their own.

Key takeaways

  • Each query is fast; the cost is their number, so measure queries per request.
  • Use logs, Bullet or Prosopite, and strict_loading in tests to find them.
  • preload to display, eager_load to filter or sort, includes when unsure.
  • Guard the heaviest pages with tests that limit query counts.

Related

Want to review your case with context?

Share the codebase, workflow, or systems involved and the result you need. We will use the call to decide a practical next step.

More guides

Ruby on Rails

Rails 7 to 8 Upgrade Guide: What Breaks and How to Plan the Upgrade

How to upgrade a production app from Rails 7 to Rails 8: the version path, the Ruby requirement, the changes that actually break code, and a safe rollout plan.

AI workflow automation

AI Automation for Small Businesses: What to Automate First and What to Leave to People

How small businesses can choose their first AI automation: where language models remove real work, where simple rules work better, and how to stay in control.