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

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

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 the method with a list of items and for each item, it makes 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

Here we have separate queries 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

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

The last three instant methods call the Company object which 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 😀 💻

Did you find this article valuable?

Support Ahmed Nadar by becoming a sponsor. Any amount is appreciated!