Optimizing N+1 Queries in Rails

As your application grows, so does your data. This has a direct effect on every query that your app makes. It’s likely that your app’s models have more than a few has_many and belongs_to associations. One common mistake that engineers can make when writing queries is known as an N+1 query.

What is an N+1 Query

An N+1 query describes when an extra query is needed for an associated object. For example, let’s say we have the following models in our Rails app, a Author model and a Book model:

  • Authors can have many books and

  • Books belong to an author

Now let’s say in our view, we want to loop over every book and display the name of that books’s author. We’d do something like this:

If we take a look at the logs for this query, we’ll notice that our Rails application will make a query for:

  • all books and

  • the name of every author for each book

What’s happening here is we initially make a request for all the books (the ‘1’ in our N+1 query). As we loop through the list of books, we’re making a separate request to the get the author of that book (this happens N number of times depending on the number of authors or the ‘N’ in our N+1 query).

This is quite expensive as it taxes the server and and ultimately slows down the time it takes for our page to load — 2370ms in this case. You want to avoid N+1 queries in order to keep your application performant and scalable.

So… how do we fix this?

Solving N+1 queries in Rails

Luckily, Rails provides a way to optimize for N+1 queries.

Eager Loading

Eager loading is a way to load all associated objects within a single query, rather than needing to query for each associated object separately. It does this by proactively preloading associated data into memory before the view is rendered.

By simplying appending .includes(:author) to our query, Rails will now proactively preload the associated data in memory before the view if rendered. This reduces our query down to only two calls to our database :

This optimization greatly reduces the time it took to render this information from 2370ms down to 184ms in this case:

Next
Next

Understanding Elasticsearch