Rails: ActiveRecordのスコープで`present?`を使うとパフォーマンスが落ちることがある(翻訳)

概要

原著者の許諾を得て翻訳・公開いたします。

日本語タイトルは内容に即したものにしました。

Rails: ActiveRecordのスコープでpresent?を使うとパフォーマンスが落ちることがある(翻訳)

RailsではあらゆるRubyオブジェクトで#present?メソッドが利用できるので、このメソッドは多用される傾向があります。たとえば、ビューで配列をループで回す前に配列に要素があるかどうかをチェックしてからデータを表示するなどです。

しかし、#present?をスコープで使うと、思わぬパフォーマンス低下が生じることがあります。

次のように書くのではなく

Active Relation(Active Recordクラスのスコープ)で#present?を使う。

books = Book.recently_released
if books.present?
  books.paperbacks.each { |book| puts book.title }
end

次のように書く

#any?#exists?を使う。

books = Book.recently_released
if books.any?
  books.paperbacks.each { |book| puts book.title }
end

そうする理由

Active Relationで#present?を呼び出すと、そのスコープのActive Recordオブジェクトがすべてメモリ上の配列に読み込まれ、それから(おそらく非常に巨大な)配列が空かどうかがチェックされます。

多くの場合これで問題はありませんが、極端なケース(モデルが複雑な場合やデータセットが巨大な場合)ではメモリ使用量が大きく跳ね上がってパフォーマンスが低下するかもしれません。この現象は、それらのオブジェクトが使われるかどうかにかかわらず発生します。

#any?を使えば、配列を一切ビルドしない別のもっとシンプルなSQLコマンドが実行されます。一般に、SQL呼び出しを追加するときのパフォーマンス低下は、全オブジェクトをメモリに読み込む場合と比べればたかが知れています。

そのリレーションが既に利用されている(つまりオブジェクトがメモリに読み込まれている)のであれば、#present?を実行してもペナルティは受けません。この場合#present?は新しい配列を読み込むのではなく、既存の配列を使います。

アプリでのデータベースの使い方が変わるとなれば、きっと皆さんも自分たちのアプリで現実のパフォーマンスにどう影響するかを監視したいと思うでしょう。その場合、#any?で発行される余分なSQLリクエストによるパフォーマンス低下が、データに#present?を使ったときのパフォーマンス低下を下回らないことを確認する必要があります。

Railsでattendance gemを用いれば、Active Relationsの#present?で常に#any?を使うようデフォルトの振る舞いを変更するモンキーパッチを当てることも可能です。attendanceは少々そっけないツールですが、このgemでまとめて解決した場合の自分たちのアプリのパフォーマンスをじっくり監視したくなることでしょう。

そうしない理由があるとすれば

#any?を用いる場合、SQLクエリを余分に発行することになるので、パフォーマンスが必ずしも改善するとは限りません。レコードを事前に読み込む方が効率よくできるのであれば、そうしましょう。

Railsで、attendance gemと同様の修正(Active Relationの#present?の動作を変更する)である#10539がいったんマージされましたが、その後2b76313で取り消され、既存の振る舞いが採用されました。このため、この振る舞いについては各自が注意して#any?を明示的に使わなければなりません。

ここから、このパフォーマンスチューニングは非常に微妙であることがわかります。皆さんのアプリで必ずしも効果を上げるとは限らないでしょう。

関連記事

Rails: `present?`より便利なActiveSupportの`presence`(翻訳)

Rails: pluckでメモリを大幅に節約する(翻訳)

デザインも頼めるシステム開発会社をお探しならBPS株式会社までどうぞ 開発エンジニア積極採用中です! Ruby on Rails の開発なら実績豊富なBPS

この記事の著者

hachi8833

Twitter: @hachi8833、GitHub: @hachi8833 コボラー、ITコンサル、ローカライズ業界、Rails開発を経てTechRachoの編集・記事作成を担当。 これまでにRuby on Rails チュートリアル第2版の半分ほど、Railsガイドの初期翻訳ではほぼすべてを翻訳。その後も折に触れてそれぞれ一部を翻訳。 かと思うと、正規表現の粋を尽くした日本語エラーチェックサービス enno.jpを運営。 実は最近Go言語が好き。 仕事に関係ないすっとこブログ「あけてくれ」は2000年頃から多少の中断をはさんで継続、現在はnote.muに移転。

hachi8833の書いた記事

BPSアドベントカレンダー

週刊Railsウォッチ

インフラ

ActiveSupport探訪シリーズ