APMツールを使ったRailsアプリケーションのパフォーマンス改善ポイントの見つけ方

こんにちは!ソフトウェアエンジニアの福間(fkm_y)です!

Railsアプリケーションのパフォーマンス・チューニング入門という記事を個人で公開していましたが、社内向けに書き直して読んでもらっていたところ好評だったのでテックブログ用に再編集して載せることにしました。

Railsを普段書くけどあまりパフォーマンスのことは考えてなかったな…これからやっていくぞ!だけどどこから手を付けていったら…という方向けの入門記事となっていますので参考になれば幸いです。

この記事で書いてること

  • 改善が必要なエンドポイントの当たりの付け方
  • 当たりをつけたエンドポイントのボトルネックの調査方法
  • ボトルネックに対する改善アプローチ

この記事で書いてないこと

  • 改善アプローチの具体的な修正方法やツールの細かい説明は書いてません。
  • フロントエンドやインフラに関するパフォーマンス・チューニングは今回の記事のスコープ外として書いてません。

本題

前提

  • RailsアプリケーションのDBにはAmazon Aurora(MySQL)を利用した環境です。
  • APMツールはDatadogを使用しています。

1. 改善対象の当たりを付ける

どのページを速くしたいか改善すべき対象が決まっていればそのエンドポイントから改善すれば良いのですが、対象が定まっていない場合のほうが多いと思います。その場合は下記のいずれかの方法で当たりを付けます。

APMを使って当たりを付ける

  • 特定期間でリクエストが遅いエンドポイントのランキング上位から当たりを付けます。
    • 注意点は特定条件のみで発生している場合は全体影響度が小さく、全体からみた成果が小さい可能性があります。
  • Datadogの「APM > Service」を利用して、特定期間のリクエストトータルタイムの上位のエンドポイントから当たりを付けます。 f:id:fkmy:20211201180546p:plain
    • 仕様上どうしても時間が掛かってしまうエンドポイントなのか、もっと速いはずが遅くなっているのかを判別します。
    • トップページのような主要導線の場合は利用回数が多いことによってトータルタイムを押し上げている可能性があるので異常に多すぎる場合以外は除外したほうが良いかもしれません。
  • Datadogの「APM > Traces」を利用して、特定期間において90パーセンタイルに該当する時間が掛かっているリクエストに絞り、その数が多いエンドポイントに当たりを付けます。 f:id:fkmy:20211201180615p:plain

スロークエリログから当たりを付ける

  • スロークエリ数が多いクエリに当たりを付け、クエリ発行しているロジック及びエンドポイントはどこか調査します。

2. 改善対象の詳細調査

当たりを付けたエンドポイント、クエリについて詳細調査を行います。パターンに応じてローカルで状況を再現させたりしながら改善を行います。

  • APMを使って当たりを付けたエンドポイントのトレースを見てボトルネックがどこにあるのか確認します。
    • DatadogのAPM Traces でエンドポイントを絞った上でDURATIONの降順にして上位の遅いリクエストを表示させます。 f:id:fkmy:20211201180635p:plain
    • RESOURCEをクリックするとサイドバーに詳細が表示されるので、SpanListタブを表示した後、%EXEC TIME の降順にして比率が大きくボトルネックとなっている処理を割り出します。 f:id:fkmy:20211201180649p:plain
    • ボトルネックがロジック起因かクエリ起因か、クエリの場合はN+1クエリか1本のクエリに時間がかかっているのかと問題を切り分け特定していきます。
  • ローカルでデータを準備して再現させます。この時にlogを見て問題のクエリの発行箇所や発行回数を確認します。以下のGemを使うと調査が楽になります。

便利Gem

  • rack-mini-profiler
    • logを確認するよりも発行クエリの回数などの視認性が良く調査が捗ります。
  • bullet
    • N+1クエリとなっている箇所を検知してくれるので便利です。
    • ただし検知してくれないパターンもあるので過信は禁物です。

3. 改善パターン

同じクエリが大量に発行されている

  • N+1クエリが原因の場合はループ処理の前にpreloadeager_loadを書き加えるようにしましょう。
  • preloadeager_loadを書き加えてもクエリ発行回数が抑えられず問題が改善されない場合もあります。例えばcountメソッドをループ内で使っているために都度クエリが発行されるパターンです(この場合はsizeメソッドを使えば解決されます)。ループ処理に入れず1回で目的のデータを取得する生クエリを書くなども試してみましょう(普段は生クエリは多用しないほうが良いと思う派ですが、パフォーマンス・チューニング時には選択肢として考えます。数年前のa_matsudaさんのパフォーマンス・チューニングのスライドでも言及されていました)。

1クエリに時間がかかっている

  • EXPLAINで実行計画を確認しながらクエリを最適化しましょう。
  • 最近対応することが多かったパターンの一部を紹介します。
    • INDEXが活用されていない場合は、INDEXを追加したりINDEXが活用されるようにクエリを組み替えましょう。
    • DISTINCTが全カラムに指定されていることによって遅くなっているケースもありました。DISTINCTで指定するカラムを絞ったり、全カラムをDISTINCTさせる必要がないクエリに組み替えましょう。
    • 不必要なテーブルの結合をしていたら止めましょう。

計算効率の悪い処理が実行されている

データ構造やアルゴリズムの見直しを行いましょう。

  • 要件や可読性を意識しつつ計算量が少なくなるデータ構造を選択しましょう。
    • include? を例に各データ構造の計算量を以下に示します。
      • Array は計算量がO(n)
      • Hash は計算量がO(log n)
      • Set は計算量がO(log n)
    • キーから値を探す場合はHashを使いましょう。集合に含まれることを確認したい、差分を取りたい場合はSetを使いましょう。
  • ループが回る回数を減らせないか、ループ外で十分な処理をループ内で行っていないかなど計算が少なく済むようになっていないか確認しましょう。

不要な処理が実行されている

不要な処理が実行されていたり、不要なクエリを発行しているケース、callbackが実行されているが不要なケースも稀にあります。仕様をよく確認して不要なら消すなりスキップするなり実行されないようにしましょう。計算処理がなくなれば当たり前ですが最も効果が大きいです。 過去にANDPADで対応した例では、調査の結果特定のエンドポイントでは不要なcallback処理が見つかりスキップさせた結果、100倍速くなったこともありました。

4. 改善後の結果確認

推測通りの改善成果を得られているか本番の計測結果を確認しましょう! 良い結果だった場合はチームで祝いモチベーションを高めましょう!どこが効いたのか他ロジックにスケール出来そうかなども検討すると次に活かすことが出来るでしょう。また悪い結果だった場合もチームで共有しどこか良くなかったのか考察することによって共有知を増やしていくことが大事だと思います。

さいごに

今回はAPMツールを使ったRailsアプリケーションのパフォーマンス改善ポイントの見つけ方やいくつかの改善パターンを紹介させていただきました。 Railsアプリケーションのパフォーマンス改善ポイントはDBがボトルネックになっていることが多いです。そのためDBの発行回数やクエリのチューニングをすると成果を得やすいです。ですが、そもそも不要な処理や効率の悪い処理を行っていることも稀によくあるので要件を確認しつつ元の実装を疑ってみることも大切だと思います。
プロダクトが運営される限りロジックの改修、蓄積データの増減によってボトルネックは移り変わります。拡大しているプロダクトであればその移り変わりも激しく「計測→改善→計測→改善→計測」のプロセス継続が重要になってきます。重要なんですが、解くべき課題に対して対応できる人が圧倒的に不足しているアンドパッドでは一緒に働く仲間を募集しています! 事業拡大中のプロダクトのパフォーマンス改善に興味がある人は一緒に働きましょう!

engineer.andpad.co.jp

この記事を読んでパフォーマンスについて興味を持たれた方はぜひ過去の勉強会の記事も合わせてご覧ください。 記事内のスライドのマスキングが多いですが入社するとなんとマスキングされていないスライドを見ることができます! tech.andpad.co.jp