DEV Community

Ahmed Nadar
Ahmed Nadar

Posted on

Eliminate N+1 queries and improve Rails app performance with this effective Eager Loading tip

Originally posted on ahmednadar.com

In Rails applications, N+1 queries can cause significant performance issues. They occur when you have a list of items and perform a separate query to retrieve associated data for each item. This can result in poor performance, and even more so if the list contains a large number of items.

A problem

I worked on an application, and initially, I wrote the following index method with a list of items and for each item, it make a separate query to retrieve associated data, as you can see this method represents an example of vulnerable code to the N+1 query problem:

def index
    @companies = Company.most_recent

    @open_companies = Company.with_active_status.includes(:_members, :users).most_recent

    @previous_companies = Company.without_active_status.includes(:_members, :users).most_recent.limit(50)

    @archived_companies = Company.with_archived_status.includes(:_members, :users).most_recent
  end
Enter fullscreen mode Exit fullscreen mode

Here we have separate queries are run for each company to retrieve the associated _members, users, and user needed data. This can result in poor performance, not to mention if a large number of companies are displayed.

I need to avoid the N+1 query issue. How? Eager loading can be used to include the associated data in the initial query. In Active Record, this is accomplished by using the includes method.

A Solution

Here is a modified version of the index method that avoids the N+1 query problem by using the includes method:

def index
  # Eager load _members, users, and user associations for @companies
  @companies = Company.includes(:_members, :users, _members: [:user]).most_recent

  # Eager load _members and users associations for @open_companies
  @open_companies = Company.with_active_status.includes(:_members, :users).most_recent

  # Eager load _members and users associations for @previous_companies
  @previous_companies = Company.without_active_status.includes(:_members, :users).most_recent.limit(50)

  # Eager load _members and users associations for @archived_companies
  @archived_companies = Company.with_archived_status.includes(:_members, :users).most_recent
end
Enter fullscreen mode Exit fullscreen mode

That’s much better as you can tell. The new index method employs the includes method to eagerly load the associations for each of the variables @companies, @open_companies, @previous_companies, and @archived_companies. This reduces the number of queries made and improves code performance, assisting in avoiding the N+1 query issue.

Using eager loading can improve the performance of your application and avoid the N+1 query issue. You can improve the performance of your application and avoid the N+1 query issue by using eager loading. Use it as much as possible to keep your code running smoothly and efficiently.

Refactor

While we solved the N+1 issue, the code smells because it breaks the DRY principle of software development. And we can avoid that by refactoring the ‘index’ method.

The refactored method below, combines the queries for the variables @open_companies, @previous_companies, and @archived_companies into a single query, reducing the number of queries and improving overall code performance as we continue to use the includes method, isn’t that beautiful:

def index
  # Eager load _members, users, and user associations for @companies
  @companies = Company.includes(:_members, :users, _members: [:user]).most_recent

  # Eager load _members and users associations for @open_companies, @previous_companies, and @archived_companies
  companies = Company.includes(:_members, :users)

  # Filter companies by active, inactive, and archived status
  @open_companies = companies.with_active_status.most_recent
  @previous_companies = companies.without_active_status.most_recent.limit(50)
  @archived_companies = companies.with_archived_status.most_recent
end
Enter fullscreen mode Exit fullscreen mode

The last three instant methods call the Company object that includes _members attribute three times. And this could be extracted into a variable to call different scopes.

As you have seen, following the DRY principle and using Eager Load can improve the performance of your application and avoid the N+1 query issues. Be sure to use it whenever possible to keep your code running smoothly and efficiently.

And as usual, Happy Coding 😀 💻

Top comments (0)