LoginSignup
2
0

More than 3 years have passed since last update.

RailsのViewでActiveRecordな大量データを出したら…めっちゃ遅!高速化した話

Posted at

結論

  • ある特定ユーザだけ段違いにレコードを持っていて、全部のレコードを表示する画面を実装したら無事死亡
  • ActiveRecordで取得したオブジェクトがすごく巨大だった
  • ActiveRecord::QueryMethods.selectで劇的に早くなった!
  • あくまで特殊の場合だと思うから、銀の弾丸ではないよ

めっちゃ遅くて使いものにならない

特定ユーザ様だけ大量のレコードを持っていました
問題はそこではなく、あるオペレーションの確認画面を作ったときにその影響ある全てのレコードを表示させたかったので、全レコードを表示しようとしたらすごく遅くなってしまいました

ページネーションを実装する程でもないと考え、ローカル環境なのでデータも少ないため「よさそう」「ヨシッ」という気分で使ってもらったら…ゴメンナサイゴメンナサイ

どうしようどうしよう

実際のページを見てみたらページを表示しきるのに何十秒とかかっていました
なんで遅いんでしょうか…

  • 大量のDOMを表示しきるのに時間がかかっている?
  • 発行しているSQLクエリが遅い?

そういえば、メモリ使用量も段違いに上昇していることに気づきました。やっぱりSQLクエリが悪いんでしょうか…
しかしながら、発行されているクエリはN+1問題は発生しておらず、2テーブルくらいしかJOINしていませんでした

ひとまず本番で起きていた状況を再現するため、ローカルでデータを大量に登録し実行してみました。同じように遅くメモリが大量に消費されていました
確認にはrack-mini-profiler Gemを使いました

問題はメモリ消費だ。だがどうやって? → selectを使う

records = Model.where(condition: true)

このようなクエリを書く時、モデルの全てのカラムを変数に格納するので、カラム数の多いテーブルは特に一つずつのレコードのメモリも当然大きくなりますよね
今回の場合、確認画面でしたのでid, nameぐらいで良かったのでそれ以外のデータを取得しないようにしました

records = Model.select(:id, :name)
               .where(condition: true)

それだけの対策で実行時間を25分の1まで縮めることが出来ました。やったぜ

終わりに

落ちがつまらなく申し訳ないですが、高速化の手法だと

  • N+1を避ける
  • ページネーションをする
  • 適切なDBインデックスを貼る

などかな思っていたので、個人的に新鮮な体験だったのでドキュメント化しました

selectで使うカラムを制限して恩恵が受けられるのは

  • 集計的な、特定の情報だけを大量に用意する場合
  • カラム数が多いテーブルに対して

などだと思います。みなさんもお気をつけて

以上

References

2
0
1

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
2
0