The general recommendation is to avoid “n+1” database queries. What does that look like?
Typically, you make one database request, and then an additional request for each result. For example, fetching Post
records and their Comments
:
class Post < ApplicationRecord
has_many :comments
end
class Comment < ApplicationRecord
belongs_to :post
end
<% Post.all.each do |post| %>
<div>
<%= post.body %>
<%= render post.comments %>
</div>
<% end %>
This approach can cause serious performance issues as the number of posts grows or as each post becomes more complex.
The simplest fix is to eager load the associations using includes
:
Post.includes(:comments).each
This reduces the queries to just two:
However, while this improves database performance, it can significantly increase memory usage.
Instead of just avoiding “n+1” queries, you can improve both database and render performance by caching individual components. Here’s how:
Break Components into Partials:
<%# posts/index.html.erb %>
<%= render partial: "posts/post", collection: Post.all, cached: true %>
<%# posts/_post.html.erb %>
<div>
<%= post.body %>
<%= render partial: "comments/comment", collection: post.comments, cached: true %>
</div>
With Russian Doll Caching, Rails:
id
and updated_at
as the cache key.id
and updated_at
as the cache key.If a post is updated, Rails generates a new cache because the updated_at
timestamp changes.
To handle changes to comments, ensure the post’s updated_at
updates when a comment changes:
class Comment < ApplicationRecord
belongs_to :post, touch: true
end
This ensures that adding or updating a comment invalidates the post’s cache, keeping everything fresh.
By using Russian Doll Caching, you can avoid most “n+1” queries, reduce memory usage, and keep your app performant as it scales.