4
2

誰も使わなかった奇妙な設定

Posted at

こちらは「Ruby on Rails パフォーマンスアポクリファ」のサンプル章です。日本語での完全版が現在Gumroadで入手可能です

Rails のバックグラウンドジョブの多くには、一般にパフォーマンスの問 題があります。
バックグラウンドジョブでは、しばしば大きなデータセットを処理しま す。そして、その際に User.all.each(&:send_daily_newsletter) のよ うな愚かなことをしてしまいます。

開発環境やテスト環境では、User.all が返すのは、おそらく数行、多くて も十数行でしょう。開発者の多くは、自分のローカルマシン上の seed デー タを極端に限定しています。

しかし、プロダクション環境では、User.all はおそらくかなりの数の行 を返します。開発するアプリケーションによっては、数十万行になるかもし れません。

10 万行を返す結果セットには、ほんのわずかばかり問題があります。それ は SQL クエリーが返すのに時間がかかるというだけではありません。Ruby アプリケーションにも取り返しのつかない影響を及ぼしてしまいます。

Active Record の大規模な結果セットは、メモリ使用量を増大させる可能 性があります。Ruby のメモリアロケーターは入り組んでいて、Ruby のプ ロセスが大量のメモリを使うと、ガベージコレクションされても OS に返さ ない傾向があります。これは Ruby のせいではなく、アロケーターのせいで す。今後数年のうちに Ruby 側で修正されるかもしれませんが、今のところ は対処せざるを得ません。

このような動作は、しばしば見落とされ、実運用で大失敗する可能性があ ります。さらに悪いことに、ほとんどの人はバックグラウンドジョブで何が 起こっているか、それが動作する限りはあまり気にしません。しかしなが ら、キューが一杯になったり、メモリが足りなくなったりすると文句を言う のです。

大量の結果セットを取得する傾向は、バックグラウンドジョブが遅くなっ たりメモリを大量に消費したりする理由の 1 つに過ぎないものの、よくある ことです。しかし Rails 5.0 以降では、この現象が発生したときに識別しや すくする設定項目が用意されています。その名も warn_on_records_fetc hed_greater_than です。

この設定について最初に書いたとき、GitHub を調べてみたら、この設定 を使っていたアプリケーションはたった 1 つだけでした。

これに整数に設定すると、ActiveRecord::Relation オブジェクトがそ の整数より大きな結果セットを返した場合に、ログに警告を出力します。

この設定は、大きな結果セットを取得する場合は ActiveRecord::Batch es の find_each などのメソッドを使用したほうがよいという注意喚起のた めのものです。

次のコードは、

 User.all.each(&:send_daily_newsletter)

代わりに次のように書けます。

User.find_in_batches do |group| group.each(&:send_daily_newsletter)
end

今すぐ config/environments/development.rb にアクセスしてこの警 告を入れるのが合理的だと思います。1500 より大きい結果セットは少な くとも 2 つの完全なバッチで取得されるので、1500 あたりが妥当な値で しょう。

config.active_record.warn_on_records_fetched_greater_than = 1500

すると、その上限を超えると、ログにこのような警告が表示されます。

Query fetched 1501 User records:
SELECT "users". FROM "users" 
ORDER BY "users"."id" ASC LIMIT $1

さて、これを application.rb あるいは環境別ファイル production.rb で有効にするかは迷うところです。この設定を行うと ActiveSupport::No tifications.subscribe("sql.active_record") がされます。つまり、 SQL クエリーごとの notification で購読されることになります。ローカル ベンチマークでは、クエリーあたりのオーバーヘッドは非常に小さくなって いますが、「Active Record のリクエストごとにオーバーヘッドを追加する」 というのは、必ずしもやりたいことではありません。

development モードでは常にオンにしつつ、バックグラウンドジョブを実 行するときだけ production モードでもオンにするのが便利でしょう。たと えば、config/initializers に以下のように記述します。

if Sidekiq.server? ActiveRecord::Base.
  warn_on_records_fetched_greater_than = 1500
end

一度に読み込むレコード数には注意が必要です。

4
2
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
4
2