Bullet Gem and Eager Loading Associations

Bullet Gem and Eager Loading Associations

Bullet Gem is designed to help you increase your application’s performance by reducing the number of queries it makes. It will watch your queries while you develop your application and notify you when you should add eager loading (N+1 queries)..  before we understand more deeply about Bullet, we will elucidate a bit Eager Loading associations..

 

What is Eager Loading Associations ?

 

If you so lazy to read RailsGuides, here we explain Eager loading to you..

Eager Loading  is the mechanism for loading the associated records of the objects returned by Model.find using as few queries as possible. when you want to elaborate relation of object and the object was looping, so that will made N+1 query problem. So the Eager Loading is the solution for not making query again when you loop the object and elaborate the relation (or collect some nested data) from it.

The Example:

[code language="ruby"]
	posts = Post.limit (7)
	posts.each do|post| puts post.author.name end
[/code]
	

 

This is okay.. but not to be okay because the problem lies within the total number of queries executed.  That code executes 1 (to find 7 posts) + 7 (one per each post to load the author) = 8 queries in total.  so let’s go to see the log:

Post Load (0.9ms) SELECT “posts”.* FROM “posts”

Author Load (0.4ms) SELECT "authors".* FROM "authors" WHERE "authors"."id" = ?
	ORDER BY "authors"."id" ASC LIMIT 1 [["id", 2]]

Author Load (0.3ms) SELECT "authors".* FROM "authors" WHERE "authors"."id" = ? ORDER BY "authors"."id" ASC LIMIT 1 [["id", 1]]

CACHE (0.1ms) SELECT "authors".* FROM "authors" WHERE "authors"."id" = ? 
	ORDER BY "authors"."id" ASC LIMIT 1 [["id", 1]]

CACHE (0.1ms) SELECT "authors".* FROM "authors" WHERE "authors"."id" = ?
	ORDER BY "authors"."id" ASC LIMIT 1 [["id", 2]]

CACHE (0.1ms) SELECT "authors".* FROM "authors" WHERE "authors"."id" = ? 
	ORDER BY "authors"."id" ASC LIMIT 1 [["id", 1]]

CACHE (0.1ms) SELECT "authors".* FROM "authors" WHERE "authors"."id" = ? 
	ORDER BY "authors"."id" ASC LIMIT 1 [["id", 1]]

CACHE (0.1ms) SELECT "authors".* FROM "authors" WHERE "authors"."id" = ? 
	ORDER BY "authors"."id" ASC LIMIT 1 [["id", 2]]
	

 

That’s not a big deal because handled by cache, but still not good.

Let’s see if we use Eager Loading :

[code language="ruby"]
	posts = Post.includes(:author).limit (7)
	posts.each do |post| puts post.author.name end 
[/code]
	

 

And see the log:

Post Load (0.4ms) SELECT “posts”.* FROM “posts”

 Author Load (0.4ms) SELECT "authors".* FROM "authors" WHERE "authors"."id" IN (2, 1)

 

Just It.. no cache, no double queries..

So what is Bullet jobs? we know about eager loading so can we just implement it?

As in explanation above, Bullet is for  increase your application’s performance by reducing the number of queries it makes. In other words so Bullet help us remaining to use eager loading… so let’s begin.!!!

The first and of course, you should install the gem :

 

Gem Install Bullet

 

Or add it to a Gemfile (Bundler):

 
[code language="ruby"]
 gem "bullet", :group => "development"
[/code]
	

 

And then of course you should run bundle install or just bundle After that, set the configuration: Append to  config/environments/development.rb [code language=”ruby”] config.after_initialize do Bullet.enable = true Bullet.alert = true Bullet.bullet_logger = true Bullet.console = true Bullet.rails_logger = true end [/code] Explanation :

  • Bullet.enable = enable Bullet gem, otherwise if you don’t set it, it will do nothing.

  • Bullet.alert = will appear an popup alert javascript in browser (if you caught to use n+1 queries)

  • Bullet.bullet_logger = will made a bullet log file in  Rails.root/log/bullet.log 

  • Bullet.console = will made warnings in your browser’s console.log (like firebug in safari/chrome/firefox, etc)

  • Bullet.rails_logger = will add warnings directly on your Rails log. 

For more information about Bullet configuration, you can see it in Github.

 

Now, keep your N+1 queries code and run rails s

Go to posts index page in your browser and you will got warnings about your N+1 queries…

 

N+1 Query detected

 Post => [:author]

Add to your finder: :include => [:author]

 

N+1 Query method call stack

/home/combo/kerjaan/test_bullet/app/views/posts/index.html.erb:17:in `block in _app_views_posts_index_html_erb__710560823_83886120'

/home/combo/kerjaan/test_bullet/app/views/posts/index.html.erb:14:in `_app_views_posts_index_html_erb__710560823_83886120'
	

 

So you should change your bad N+1 queries in app/controllers/posts_controller.rb file:

[code language="ruby"]
 	def index @posts = Post.includes (:author)
  end 
[/code]
	

 

Refresh your browser page.. and you will get nothing.. (no alert, warning on firebug and your rails log)..

You will get your log just like this :

Post Load (0.4ms) SELECT "posts".* FROM "posts"
	

Author Load (0.4ms) SELECT "authors".* FROM "authors" WHERE "authors"."id" IN (2, 1)
	

 

N+1 fixed… Yay…!!!

Now we will try to unuse eager loading in view.. so let’s change the code in app/views/posts/index.html.erb:

[code language="ruby"]
 <% @posts.each do |post| %> <tr> <td><%= post.name %></td> #change post.author.name to be post.name <td><%= link_to 'Show', post %></td> <td><%= link_to 'Edit', edit_post_path(post) %></td> <td><%= link_to 'Destroy', post, :confirm => 'Are you sure?', :method => : delete %></td> </tr> <% end %>
[/code]
	

 

Refresh your browser and you will get this :

Unused Eager Loading detected

Post => [:author]

Remove from your finder: :include => [:author]

 

So in this case, if you wont use elaborate relation or collect nested data from an object, don’t use eager loading.