結論
- ある特定ユーザだけ段違いにレコードを持っていて、全部のレコードを表示する画面を実装したら無事死亡
- 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