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)