โš ๏ธ

N+1 Query Problem

Performance enemy โ€” solving with includes

The N+1 problem is the most common performance issue in Rails.

The problem:

# Controller
@posts = Post.all  # Query 1: SELECT * FROM posts

# View
<% @posts.each do |post| %>
  <%= post.user.name %>  # Query N: SELECT * FROM users WHERE id = ? (every time!)
<% end %>

100 posts = 101 queries!

Solution: includes

@posts = Post.includes(:user)  # Solved with 2 queries
# SELECT * FROM posts
# SELECT * FROM users WHERE id IN (1, 2, 3, ...)

3 approaches:

  • includes โ€” Rails auto-selects optimal strategy

  • preload โ€” loads via separate queries (default)

  • eager_load โ€” uses LEFT OUTER JOIN

Detection tool: The bullet gem auto-detects N+1 and shows warnings.

Architecture Diagram

N+1 Problem (101 queries!)
1. SELECT * FROM posts
2. SELECT * FROM users WHERE id = 1
3. SELECT * FROM users WHERE id = 2
4. SELECT * FROM users WHERE id = 3
... (repeated N times!)
101. SELECT * FROM users WHERE id = 100
Post.all โ†’ post.user (query each time)
With includes (2 queries!)
1. SELECT * FROM posts
2. SELECT * FROM users WHERE id IN (1, 2, 3, ..., 100)
Done! Completed with just 2 queries
Post.includes(:user) โ†’ preloaded
Code comparison:
Before
@posts = Post.all
After
@posts = Post.includes(:user)
Key point: <strong>One line of includes</strong> reduces 101 queries to 2 โ€” auto-detectable with bullet gem

Key Points

1

Recognize: accessing associations in loops causes N+1

2

Check rails log for repeated SELECT queries

3

Add includes(:association) to controller queries

4

Nested: includes(posts: :comments) or includes(:posts, :profile)

5

Auto-detect N+1 with bullet gem

6

Set strict_loading mode to raise exceptions on N+1 (Rails 6.1+)

Pros

  • One line of includes improves performance by orders of magnitude
  • Auto-detectable with bullet gem
  • Preventable at runtime with strict_loading
  • Easily verifiable via SQL logs

Cons

  • Adding includes everywhere loads unnecessary data
  • eager_load may duplicate data due to JOINs
  • Complex relationships need optimal strategy decisions
  • May increase memory usage

Use Cases

Post list + author display Order list + product info Categories + sub-items Dashboard statistics page