はじめに
Railsアプリケーションのパフォーマンス改善を始めるとどこからどんな手順で進めたら良いのか、進め方が誤っているんじゃないかと不安になることが多いですよね。 私も試行錯誤しながらパフォーマンス改善しているのですが、いくつかパターンがわかってきたので手順にまとめてみました。
この記事で書いてること
この記事で書いてないこと
- 改善アプローチの具体的な修正方法やツールの細かい説明は書いてません
- フロントエンドやインフラに関するパフォーマンス・チューニングは今回の記事のスコープ外として書いてません
やること
前提
- 環境はDBにAmaozn Aurora(MySQL)を使用したRailsを想定しています。
- 直近でAPMツールにDatadogを使っているのでその前提になります。
- New Relic APMやScout APMなどの他のAPMツールを使っても同様なことはできると思います。
改善対象の当たりを付ける
どのページを速くしたいか改善すべき対象が決まっていればそのエンドポイントから改善すれば良いのですが、対象が定まっていない場合のほうが多いと思います。その場合は下記のいずれかの方法で当たりを付けます。
APMを使って当たりを付ける
- 特定期間でリクエストが遅いエンドポイントのランキング上位から当たりを付ける。
- 注意点は特定条件のみで発生している場合は全体影響度が小さく、全体からみた成果が小さい可能性があります。
- 特定期間のリクエストトータルタイムの上位のエンドポイントから当たりを付ける。
- Datadogの場合は
APM > Service
を利用します。 - 仕様上どうしても時間が掛かってしまうエンドポイントなのか、もっと速いはずが遅くなっているのかを判別します。
- トップページのような主要導線の場合は利用回数が多いことによってトータルタイムを押し上げている可能性があるので異常に多すぎる場合以外は除外したほうが良いかもしれません。
- Datadogの場合は
- 特定期間で90パーセンタイルに該当する時間掛かっているリクエストに絞りその数が多いエンドポイントに当たりを付ける。
- Datadogの場合は
APM > Traces
を利用します。
- Datadogの場合は
スロークエリログから当たりを付ける
- スロークエリ数が多いクエリに当たりを付け、クエリ発行しているロジック及びエンドポイントはどこか調査する。
改善対象の詳細調査
当たりを付けたエンドポイント、クエリについて詳細調査を行います。パターンに応じてローカルで状況を再現させたりしながら改善を行います。
- APMを使って当たりを付けたエンドポイントのトレースを見てボトルネックがどこにあるのか確認します。
- ローカルでデータを準備して再現させます。この時にlogを見て問題のクエリの発行箇所や発行回数を確認します。以下のGemを使うと調査が楽になります。
便利Gem
- rack-mini-profiler
- logを確認するよりも視認性が良く調査が捗ります。
- bullet
- N+1クエリとなっている箇所を検知してくれるので便利です。
- ただし検知してくれないパターンもあるので過信は禁物です。
改善パターン
同じクエリが大量に発行されている
- N+1クエリが原因の場合はループ処理の前に
preload
かeager_load
を書き加えるようにしましょう。 preload
やeager_load
を書き加えてもクエリ発行回数が抑えられず問題が改善されない場合もあります。例えばcount
メソッドをループ内で使っているために都度クエリが発行されるパターンです(この場合はsize
メソッドを使えば解決されます)。ループ処理に入れず1回で目的のデータを取得する生クエリを書くなども試してみましょう(普段は生クエリは多用しないほうが良いと思う派ですが、パフォーマンス・チューニング時には選択肢として考えます。数年前のa_matsudaさんのパフォーマンス・チューニングのスライドでも言及されていました)。
1クエリに時間がかかっている
- EXPLAINで実行計画を確認しながらクエリを最適化しましょう。
- EXPLAINの見方については (オトコ)のコンピュータ道: MySQLのEXPLAINを徹底解説!!を読みましょう。
- 最近対応することが多かったパターンの一部を紹介します。
- INDEXが活用されていない場合は、INDEXを追加したりINDEXが活用されるようにクエリを組み替えましょう。
- DISTINCTが全カラムに指定されていることによって遅くなっているケースもありました。DISTINCTで指定するカラムを絞ったり、全カラムをDISTINCTさせる必要がないクエリに組み替えましょう。
- 不必要なテーブルの結合をしていたら止めましょう。
計算効率の悪い処理が実行されている
データ構造やアルゴリズムの見直しを行いましょう。
不要な処理が実行されている
不要な処理が実行されていたり、不要なクエリを発行しているケース、callbackが実行されているが不要なケースも稀にあります。仕様をよく確認して不要なら消すなりスキップするなり実行されないようにしましょう。計算処理がなくなれば当たり前ですが最も効果が大きいです。
改善後の結果確認
推測通りの改善成果を得られているか本番の計測結果を確認しましょう! 良い結果だった場合はチームで祝いモチベーションを高めましょう!どこが効いたのか他ロジックにスケール出来そうかなども検討すると次に活かすことが出来るでしょう。また悪い結果だった場合もチームで共有しどこか良くなかったのか考察することによって共有知を増やしていくことが大事だと思います。
まとめ
サーバサイドのパフォーマンス・チューニングはDBがボトルネックであることが多いです。そのためDBの発行回数やクエリのチューニングをすると成果を得やすいです。ですが、そもそも不要な処理や効率の悪い処理を行っていることも稀によくあるので要件を確認しつつ元の実装を疑ってみることも大事です。
今回は触れませんでしたが、テーブル分割や不要となったレコードの削除、キャッシュの利用などのできる対応は他にもあるので対応コストやプロダクトの状況を考慮した上で検討していきましょう。
さいごに
プロダクトが運営される限りロジックの改修、蓄積データの増減は発生し続けるため、パフォーマンス改善は1回やったら終わりではなく「計測→改善→計測→改善→計測」のプロセスを継続することが重要です。 パフォーマンス改善をすることによって、ユーザーの待ち時間を減らしユーザー体験の向上やスパイク発生時のサーバー負荷が減少させていきましょう。
追記
記事を再編集して社のテックブログでも公開した。 tech.andpad.co.jp