Web Development

Counting data is something we normally do. We count cart items in e-commerce websites, answers to questions in question and answer websites. Bullet is a gem that helps you increase your application’s performance by reducing the number of queries it makes.

Here is an example of a request that has a N+1 issue

Question Load (123.0ms)  SELECT "questions".* FROM "questions"
[active_model_serializers]    (0.9ms)  SELECT COUNT(*) FROM "answers" WHERE "answers"."question_id" = $1  [["question_id", 9]]
[active_model_serializers]       app/serializers/question_serializer.rb:7
[active_model_serializers]    (3.6ms)  SELECT COUNT(*) FROM "answers" WHERE "answers"."question_id" = $1  [["question_id", 2]]
[active_model_serializers]       app/serializers/question_serializer.rb:7

Bullet will show this in the log as:

[active_model_serializers]       app/serializers/question_serializer.rb:7
user: theuser
GET /api/v1/questions
Need Counter Cache
  Question => [:answers]

Need Counter Cache

Here is how to do caching counters with ActiveRecord’s counter caches.

To create a counter cache, we should add a field to the parent table. In this case, it is the question table.

class AddAnswersCountToQuestions < ActiveRecord::Migration[5.2]
  def change
    add_column :questions, :answers_count, :integer
  end
end

When we request from our application, we can see that only one query is applied

Started GET "/api/v1/questions" for ::1 at 2019-08-31 09:13:55 +0800
Processing by Api::V1::QuestionsController#index as JSON
  Question Load (8.5ms)  SELECT "questions".* FROM "questions"
    app/controllers/api/v1/questions_controller.rb:8
[active_model_serializers] Rendered ActiveModel::Serializer::CollectionSerializer with ActiveModelSerializers::Adapter::Attributes (6.34ms)
Completed 200 OK in 84ms (Views: 61.6ms | ActiveRecord: 18.8ms)