あすたぴのブログ

astap(あすたぴ)のブログ

Activerecordのパフォーマンスチューニング

背景

仕事でactiverecordを使用していて、

複数のテーブルのデータを返す必要がある場合に、

レスポンスが一気に遅くなったために調べていた。

N+1問題を解消

まず、最初にあまり気にせずにN+1を行っていたので解消した。

解消方法としては、includes, prelaod、eager_loadがある。

今回はjoinして絞り込む必要がなく、

複数のクエリで単純にセレクトをしたかったのでpreloadをしていた。

これを行うことによって、クエリの発行数が抑えられた(もともと、大した量ではなかったけど)

ボトルネックを明確にする。

しかし、preloadを行っても性能はあまり改善が見られなかった。

ボトルネックを明確にする為に、

rack-profilerを使用し、

細かく各処理の時間を見ていたが、

どうもロジックと全体の時間が合っていない。

activerecordでセレクトし、生成されたオブジェクトのループ全体の時間と

ループ内の処理の時間を足した時間が一致しない。

ループ時のループ変数(って言葉ある?)を生成するタイミングで時間がかかっているようだった。

Object生成のコスト

N+1問題の解消の為に、予めクエリを発行し、キャッシュしていくことが解決方法であるが、

そこで予め生成するオブジェクトのコストはN+1でクエリを発行するよりも重い場合がある。

多くをpreloadしたオブジェクトを each メソッド等でループする際に、

紐づくテーブルの情報もオブジェクト生成が行われる。

結果として、1ループあたりの、ループ変数の生成に時間がかかるようになる。

つまり、N+1のクエリ発行とpreloadのオブジェクト生成の時間はトレードオフになる。

結論

レコード数や関連するテーブル、オブジェクト生成数によって

何が一番良いかが変わるため、正解がない。

今回の場合は、preloadはしておきたいものの、

preloadしたいレコード数が決まっていた。(全てを取る必要がない)

そのため、 preloadで全レコードのオブジェクト生成のコストよりN+1のコストのほうが低くなった。

tips

オブジェクトの生成が必要なく、

セレクトしてきたデータのみが必要な場合は、pluckメソッドを使用することで

オブジェクト生成せずに、データが取得できる。

User.all.pluck(:id, :name)
[
  [1, 'hoge'],
  [2, 'moge']
]

みたいなデータが取れる。