パフォーマンス改善の始め方と、APIレスポンスタイムを67%短縮した話

こんにちは、YOUTRUST Webエンジニアの寺井(YOUTRUST/X)です。

私はYOUTRUSTに入社してからこれまでプロダクト開発部に所属しており、主に機能開発を担当していました。

2024年8月からは技術開発室に異動し、この1ヶ月はパフォーマンス改善に取り組んできました。

そこで、今回はこの1ヶ月間パフォーマンス改善に取り組んだ過程とその結果を記事にしたいと思います。

1. することの方針の決定

技術開発室に異動と言っても、既存のチームに加入する形ではなく、私の異動とともに新たに品質チームというチームができた形でした。

そのため、着手可能な状態の具体的なタスクがあるわけではなく、何をするかどんな優先順位で進めていくかから決めていく必要がありました。

チームができた背景としては、品質面の問題は開発組織として把握しつつも、これまでどうしても対応が後回しになってしまっており、特にYOUTRUSTを普段からよく使ってくれているようなユーザーにおけるパフォーマンスが大きな問題となっていたので、その問題を解決するために品質チームが立ち上げられました。

このことから、「全員を少しずつ速くすること」よりも「一部のヘビーユーザーを大きく速くすること」を優先することにしました。

また、最初はレスポンスタイムがX秒以上のすべてのAPIを洗い出して、利用頻度が高いAPIからパフォーマンスを改善していこうと考えていましたが、「YOUTRUSTのAPIがレスポンスタイムX秒を切っていること」よりも「ユーザーがYOUTRUSTを使ってストレスを感じないこと」の方が大事であると判断して、「数値として遅いところ」よりも「遅くてストレスを感じているところ」を優先する方針に決定しました。

今回のパフォーマンス改善で優先する方針

2. パフォーマンス改善の対象とする機能の決定

次に、「YOUTRUSTをよく使っているユーザーが遅さによってストレスを感じているところ」を把握するために、YOUTRUST内のつながり数TOP10の社員に対してヒアリングを行いました。

※つながり数を指標にしたのは、つながり数に比例してレスポンスタイムが大きくなっているAPIが多いという仮説を持っていたため

つながり数TOP10の社員に対するヒアリングの結果

その結果、ヒアリングした10人がストレスを感じていた箇所はかなり共通していて、主に以下の3つに分類することができました。

  • チャットルーム(メッセージ)機能
  • ホーム
  • 検索機能

そこで、まずは実装観点で最もパフォーマンス改善が容易に実現できそうなチャットルーム機能から取り組むことに決定しました。

3. 現状把握のための準備

YOUTRUSTでは2024年9月現在はDatadogNew RelicといったAPMを導入しておらず、Sentryを活用してパフォーマンスの測定を行っています。

余談ですが、SentryではAPIのトレースやプロファイルだけでなくWeb Vitalsなどの値も計測することが可能で、パフォーマンス改善に大いに役立ちます。

パフォーマンス改善時に役立つSentryの機能の例

YOUTRUSTでは、データ取得による費用とパフォーマンス低下を考慮して全リクエストのデータを取得せずに、一定のレートでデータをサンプリングするようにしていました。

今回パフォーマンス改善のターゲットにしたチャットルーム機能の現状把握と各改善の効果を測定するために、一時的にチャットルーム関連APIだけサンプリングレートを上げて、より詳細に現在のレスポンスタイムや遅延に繋がっている問題点を把握しました。

Sentry上で検出された課題

4. 具体的に行った改善

今回は、チャットルーム機能のコメント一覧を取得するためにアクセスしている以下の2つのAPIを対象にしました。

  • チャットルーム詳細API
  • チャットルームコメント一覧API

チャットルーム詳細APIとチャットルームコメント一覧APIで取得している箇所

YOUTRUSTのチャットルームにはいくつかの種類があり、スカウトルームの場合はスカウトが送信可能かどうかを計算していたりと、チャットルーム詳細APIはYOUTRUSTの中でも少し肥大化した複雑なAPIとなっていました。

チャットルーム詳細APIで対応したことは、主に以下の通りです。

  • 各種リソースで発生していたN+1問題の解消
  • 二次のつながり計算時のスライスサイズを増加
  • 二次のつながり計算を呼び出さなくても良いところは別経路で判定するように変更

二次のつながりとは「自分の友達の友達」のことで、これを計算するときはRubyのeach_sliceメソッドを使って分割しながら計算を行っていました。

user.friend_ids.each_slice(SLICE_SIZE) do |sliced_friend_ids|
  # DBへクエリを発行するような処理
end

スライスサイズが小さいと、1つのクエリでDBにかかる負荷が小さくなる一方で、発行されるクエリ数が増加します。

逆にスライスサイズを大きくすると、1つのクエリでDBにかかる負荷が大きくなる一方で、発行されるクエリ数は少なくなります。

クエリは直列で発行されているため、発行されるクエリ数を減らせるとAPIのレスポンスタイムも短縮できます。

一次のつながりが極端に多い人の場合のクエリ発行の様子

直近YOUTRUSTではDBのスペックを増強していたのでスライスサイズを上げることが可能であると判断して、負荷を見ながら段階的にスライスサイズを大きくしていきました

また、チャットルームに参加している全ユーザーに対して二次のつながりを計算していたのですが、他箇所ですでに計算済みの「自分と対象者との共通のつながり数」を使うことで

二次のつながりである = 自分自身でない && 一次のつながりでない && 共通のつながり数>0

と言い換えて、ボトルネックとなっていた二次のつながり計算を呼び出す回数自体を減らしました。

チャットルームコメント一覧APIでは、チャットルーム詳細APIと同様にN+1問題の解消を行いました。

5. パフォーマンス改善の結果の検証

チャットルーム詳細APIのパフォーマンス改善の結果は以下のようになりました。

※横の目盛り幅が違うことに注意

パフォーマンス改善前(山の頂上は0.25秒)

パフォーマンス改善後(山の頂上は0.14秒)

2つのヒストグラムを比較すると、モード(山の頂上)が0.25秒から0.14秒へと44%減少していることが分かります。

また、各パーセンタイルごとに比較した結果は以下のようになりました。

チャットルーム詳細APIのレスポンスタイムの比較

改善前 改善後 比較
P50 0.35秒 0.20秒 -0.15秒(-43%)
P75 0.67秒 0.30秒 -0.37秒(-55%)
P90 1.45秒 0.52秒 -0.93秒(-64%)
P95(目標の指標) 2.09秒 0.70秒 -1.39秒(-67%)
P99 6.47秒 1.70秒 -4.77秒(-74%)

一部のヘビーユーザーを大きく速くすること」という最初に決めた方針に従って、P95(遅い順上位5%に当たる人)を指標として使うと、最終的にはレスポンスタイムを67%短縮という結果になりました!🎉

まだまだ改善の余地はありますが、社内でも「以前と比べてかなり速くなった」という声を聞くことができました。

6. 感想

これまでのキャリアでは、パフォーマンス改善はどうしても機能開発の片手間でしかできないことが多かったのですが、今回初めてこれだけ集中してパフォーマンス改善に取り組むことができ、とても学びが多かったし楽しかったです!

チャットルームの次はホーム画面のパフォーマンス改善を目標として、2024年9月現在試行錯誤しながら取り組んでいるので、サクサクになることを期待してお待ちください👍

今回の記事のように、バックエンドエンジニアとしての機能開発の経験を活かしてパフォーマンス改善や技術的負債の解消などに取り組むポジションをYOUTRUSTでは募集しています!

立ち上がったばかりでこれから力を入れていきたい領域なので、ぜひご興味がある方はカジュアル面談をお待ちしております!

herp.careers