モンモンブログ

技術的な話など

rake task で bullet を使って N+1 問題を検出

N+1 問題を検出する bullet は、rake タスクではそのままでは動作しないです。

各 rake タスクの最初と最後に Bullet を開始・終了する記述を追加する必要があります。こんな風に。

$ vi lib/tasks/hoge.rake
namespace :hoge do
  task :hoge => :environment do |_task|
    # Bullet開始
    Bullet.start_request

    # rakeタスク本体 〜N+1問題を添えて〜
    Shop.limit(3).each do |shop|
      puts shop.items.map(&:name).to_s
    end

    # Bullet終了
    Bullet.perform_out_of_channel_notifications if Bullet.notification?
    Bullet.end_request
  end
end

こんなのいちいち全タスクに書きたくないですよね。

なので全タスクの開始時・終了時に呼ばれる hook を定義して、その中で Bullet を開始・終了させようと思います。

lib/tasks/hooks.rake(ファイル名はなんでもいいです)に、以下のような2つのタスクを定義します。それぞれ、rake タスク開始時と終了時に呼ばれる hook です。

$ vi lib/tasks/hooks.rake
desc 'rakeタスク開始時のhook'
task before_hook: :environment do
  # Bullet開始
  Bullet.start_request
end

desc 'rakeタスク終了時のhook'
task after_hook: :environment do
  at_exit do
    # Bullet終了
    Bullet.perform_out_of_channel_notifications if Bullet.notification?
    Bullet.end_request
  end
end

これらの hook を他の全タスクの開始時・終了時に呼ぶように Rakefile に追記します。

$ vi Rakefile
require_relative "config/application"

Rails.application.load_tasks

# ↓ここから追記

# タスク実行前後の共通処理を追加
Rake.application.tasks.each do |task|
  next if %w[before_hook after_hook environment].include?(task.name)
  task.enhance([:before_hook, :after_hook])
end

最初に載せた N+1 な rake タスクからは Bullet 開始・終了の記述を削除できます。

$ vi lib/tasks/hoge.rake
namespace :hoge do
  task :hoge => :environment do |_task|
    # rakeタスク本体 〜N+1問題を添えて〜
    Shop.limit(3).each do |shop|
      puts shop.items.map(&:name).to_s
    end
  end
end

この状態で実行してみましょう。

$ bundle exec rake hoge:hoge

↓のようにログが吐かれて、ちゃんと N+1 問題が検出できるようになりました。

$ tail -0f log/bullet.log
2023-03-30 07:18:41[WARN] user: root

USE eager loading detected
  Shop => [:items]
  Add to your query: .includes([:items])
Call stack
  /app/src/lib/tasks/hoge.rake:5:in `map'
  /app/src/lib/tasks/hoge.rake:5:in `block (3 levels) in <main>'
  /app/src/lib/tasks/hoge.rake:5:in `block (2 levels) in <main>'

やったね。