Railsアプリケーションで遅い画面を産まないためにチェックすること

Webアプリケーションを運用する中で「この画面が遅いね、もっと動作を速くしたいね」というシーンが度々ありました。 その都度、その時思いつくさまざまな対応をしてきたので、それを思い出しながら雑にメモしておきます。

自分は主にRailsでアプリケーション開発をしているので、その前提で「遅い画面を産まないためにチェックすること」という視点で書き残します。 (思いついたら追記します)

Railsのキャッシュ機構を活用できそうか

Rails のキャッシュ機構 - Railsガイド を頭に入れておきましょう。 どれも便利です。

あと、オプションのところに記載されている race_condition_ttl も便利です。使いましょう。

マルチプロセスで同じエントリが同時に再生成されてキャッシュが無効になる(dog pile効果とも呼ばれます)ときに発生する競合状態を防止するのに使います。このオプションは、新しい値の再生成が完了していない状態で、無効になったエントリを再利用してよい時間を秒で指定します。

N+1問題が発生してないか

初歩的な話ですが preloadeager_load を適切に使っているかを確認します。

この辺りの挙動については2014年の記事ですが ActiveRecordのjoinsとpreloadとincludesとeager_loadの違い - Qiita が分かりやすいです。

個人的には includes はコードリーディングしたときに挙動を想像しにくいと感じるのであまり積極的には使いません。*1

目的を意識しながら preloadeager_load を使い分けて実装するようにしています。*2

N+1問題の検知については、 bullet を導入して、CIの実行時に bullet がテストコードでN+1問題の発生を検知したらCIをfailさせるように設定しておくと便利です。

適切にindexが張られているか

クエリの実行計画をみます。EXPLAIN しましょう。

巨大なテーブル同士のJOINや不要なJOINがないか

巨大なJOINがいくつもある場合は、テーブル構成を見直すきっかけになります。

また、古い機能の名残りでいまは必要のないJOINが含まれていた、といった場合もありそうなので合わせてチェックしましょう。

Materialized View が活用できそうか

例えばランキング表示などために既存のテーブルをいくつか組み合わせて集計する必要がある時など、画面へのアクセスのたびに集計用の重いクエリが発行されてしまう場合があります。そんな時は Materialized View を作って集計結果を記録しておくと便利です。ただし、即時性が必要とされない画面だけで使える手段ですね(1時間毎に最新の集計結果になっていればOK、などであれば適していそう)

Railsアプリケーションの場合は scenic がとても便利です。

*1: includes した association に対する joins, references などによって発行されるクエリの挙動が変わるため

*2:既存コードで includes が使われている場合はそのまま使うことはあります