Fix N+1 Queries In Rails: A Developer's Journey To Speed
Learn to detect and fix Rails N+1 queries with real examples. Transform slow apps into fast ones with includes and eager loading.
• 4 min read
• 4 min read
Learn to detect and fix Rails N+1 queries with real examples. Transform slow apps into fast ones with includes and eager loading.
• 4 min read
• 4 min read
I remember staring at my screen, confused.
The page loaded. No errors flashed. Nothing broke. But something felt wrong.
Then I opened the Rails logs.
A waterfall of SQL queries cascaded down my terminal. One query triggered another. Then another. Then ten more.
My app was drowning in database calls, and I had no idea why.
That was the day I discovered the N+1 query problem. And if you're reading this, you're probably experiencing that same sinking feeling right now.
Don't worry. You're about to learn exactly how to fix it.
Let me show you the code that caused my headache:
@articles = Article.all
Simple, right? Then, in my view:
<% @articles.each do |article| %>
<%= article.author.name %>
<% end %>
Here's what Rails actually did behind the scenes:
1 query to fetch all articles
1 query per article to fetch each author
With 10 articles, that's 11 database queries. With 100 articles? 101 queries.
That's the N+1 problem in action. It works perfectly fine in development. But it absolutely destroys performance at scale.
Here's the beautiful truth: Rails never hides this from you.
When I finally learned to read my development logs properly, I saw patterns like this:
Admin Load SELECT "admins".* WHERE "admins"."id" = 1
Admin Load SELECT "admins".* WHERE "admins"."id" = 1
Admin Load SELECT "admins".* WHERE "admins"."id" = 1
The same query. Repeated over and over.
That repetition? That's Rails screaming at you.
Once you recognize this pattern, you'll spot N+1 problems instantly. It's like suddenly being able to see the Matrix.
Every SQL line in your logs is a clue.
When you see:
Category Load SELECT "categories".*
Rails is telling you: "Somewhere in your view, you called article.categories, but you didn't preload it."
The fix becomes obvious:
includes(:categories)
No guessing. No trial and error. Just reading what Rails is telling you.
If you're still getting comfortable with how Rails connects models, views, and controllers, understanding Rails MVC architecture will make these patterns click even faster.
ActionText Trap Everyone Falls IntoWhen you add rich text to your model:
has_rich_text :content
Rails creates a hidden association called rich_text_content.
So when your view renders:
<%= article.content %>
Rails fires off an N+1 query for ActionText::RichText.
I spent hours debugging this before I understood the pattern. The fix:
includes(:rich_text_content)
This catches almost every Rails developer at least once. Now you know the secret.
Here's another gotcha I learned the hard way.
When you attach an image:
has_one_attached :featured_image
Rails uses two separate tables internally:
active_storage_attachmentsactive_storage_blobsIf you only preload the attachment, Rails still queries for the blob later.
The correct solution:
includes(featured_image_attachment: :blob)
This applies to profile pictures, avatars, and any file attachment in your app.
What if your article displays the author's profile picture?
<%= image_tag article.author.profile_picture %>
Rails needs to preload:
Which translates to:
includes(
author: { profile_picture_attachment: :blob }
)
It looks complex at first glance. But you're just following the chain of associations. Article to author. Author to picture. Picture to blob.
Simple logic, nested syntax.
After months of refining, here's what my controller query evolved into:
Article.includes(
:categories,
:rich_text_content,
:author,
author: { profile_picture_attachment: :blob },
featured_image_attachment: :blob
)
Every single line corresponds to something my view actually uses. Nothing extra. Nothing missing.
This query went from 50+ database calls down to 6.
The page load time dropped from 2 seconds to 200 milliseconds.
Rails is built on convention over configuration. Clarity over cleverness.
The same mindset that helps you choose between build and new applies here, too.
Rails wants you to be explicit about your intentions. When you use includes, you're telling Rails: "I know I'll need this data. Please fetch it upfront."
That's not a performance hack. It's just good design.
Stop asking: "What should I put inside includes?"
Start asking: "What methods am I calling in my view?"
If your view calls it, Rails must preload it.
That single mental shift will guide you through every N+1 problem you'll ever face.
Every Rails developer reaches a turning point.
You stop blindly copying code from Stack Overflow or AI assistants.
You start reading your logs.
You start understanding what Rails is actually doing beneath the surface.
That's when Rails stops feeling like magic and starts feeling like a tool you truly control.
I remember the first time I fixed an N+1 problem without googling anything. I just read the logs, spotted the pattern, and knew exactly what to add.
That moment felt incredible.
Because suddenly, performance wasn't scary anymore. It was predictable. It made sense.
And that's when I knew I was becoming a real Rails developer.
My advice for you: Keep building. Keep learning. Keep pushing forward.