[ORM] Eager Loading and N+1 Query Problem

What is N+1 query problem?

When your code loads the children in a parent-child relationship via the parent, most ORM have lazy-loading enabled by default, so queries are issued for the parent record, and then one query for EACH child record. As you can expect, doing N + 1 queries instead of a single query will floor your database with queries; something to avoid. 

Consider this code:

#Articles model
class Article < ActiveRecord::Base
    belongs_to :author
end 


#Authors model
class Author < ActiveRecord::Base
    has_many :posts
end


If you then ran:

#In our controller
@recent_articles = Article.order(published_at: :desc).limit(5)

#in our view file
@recent_articles.each do |article|
    Title: <%= article.title %>
    Author:<%= article.author.name %> # <-- this will trigger N+1 query
end


You
would send 6 (5+1) queries to the database. 1 to fetch 5 recent articles, and then 5 for their corresponding authors.

Remember: it is faster to send 1 query which returns 100 results, than to issue 100 queries which returns 1 result. 

Solution - Eager Loading

In Rails, ActiveRecord has a method called includes, which ensures that all the associated datas specified are loaded with the minimum number of queries.

Eager loading means to fetch all the data ahead of time. Instead of accessing EACH child id and calling a single query, it will fetch the ids all the child and then use a single query to get data of all the child of interest. 

Before Eager Loading:

Article Load (0.9ms) SELECT 'articles'.* FROM 'articles'
Author Load (0.4ms) SELECT 'authors'.* FROM 'authors' WHERE 'authors'.'id' = ? ORDER BY 'authors'.'id' ASC LIMIT 1 [["id", 1]]
Author Load (0.3ms) SELECT 'authors'.* FROM 'authors' WHERE 'authors'.'id' = ? ORDER BY 'authors'.'id' ASC LIMIT 1 [["id", 2]]
Author Load (0.4ms) SELECT 'authors'.* FROM 'authors' WHERE 'authors'.'id' = ? ORDER BY 'authors'.'id' ASC LIMIT 1 [["id", 3]]
Author Load (0.3ms) SELECT 'authors'.* FROM 'authors' WHERE 'authors'.'id' = ? ORDER BY 'authors'.'id' ASC LIMIT 1 [["id", 4]]    
Author Load (0.4ms) SELECT 'authors'.* FROM 'authors' WHERE 'authors'.'id' = ? ORDER BY 'authors'.'id' ASC LIMIT 1 [["id", 5]]

After Eager Loading:

Article Load (0.4ms) SELECT 'articles'.* FROM 'articles'
Author Load (0.4ms) SELECT 'authors'.* FROM 'authors' WHERE 'authors'.'id' IN (1,2,3,4,5)

Remember: less query = better.



Comments

Popular posts from this blog

[Redis] Redis Cluster vs Redis Sentinel

[Unit Testing] Test Doubles (Stubs, Mocks....etc)

[Node.js] Pending HTTP requests lead to unresponsive nodeJS