Money Forward Developers Blog

株式会社マネーフォワード公式開発者向けブログです。技術や開発手法、イベント登壇などを発信します。サービスに関するご質問は、各サービス窓口までご連絡ください。

20230215130734

アプリケーションロジックを見直してマネーフォワードMEのとあるAPIを約50%高速化しました

マネーフォワード ME のサーバサイドエンジニアの taka.naoga です。 ME が提供するとある機能のアプリケーションロジックを見直すことで、エンドポイント単位でパフォーマンスを改善した話を紹介しようと思います。

改善した機能紹介

はじめに、改善の対象となった機能について概要をご紹介します。

画像赤枠で囲われているアプリホーム画面の「お知らせ」機能です。

ここに表示されているお知らせは、ユーザの属性ごとに任意のセグメントを設定することで、リクエストしたユーザに合わせたキャンペーンや関連サービスの紹介などをすることができます。 社内からアクセスできる管理用のwebアプリケーションを使ってマーケティングチームなどが設定、運営をしています。

設定できるユーザ属性には、年齢や性別といったプロフィール情報から、「証券口座を連携しているユーザ」のようにユーザの連携口座に基づいたものなど多岐にわたります。

改善したアプリケーションロジックは、リクエストしたユーザがどのセグメントに属するかを判定し、表示するお知らせを抽出する部分です。

単語の定義

ここで改めてこの記事内の単語を整理しておきます。

ユーザ属性 ... リクエストユーザの情報に応じた判定を行う個別の抽出条件。開発者が管理用webアプリケーションへ実装しマーケティングチームに機能として提供する。 例: 性別・年齢・プレミアムユーザかの判定・連携口座情報 など

セグメント ... キャンペーンなどをパーソナライズするため、マーケティングチーム等が管理用webアプリケーションから設定するもの。複数のユーザ属性を組み合わせることによって1つのセグメントを設定する。 "セグメント has_many ユーザ属性" の関係。 例: 「関東在住 20代男性」「プレミアムユーザで資産系口座を連携しているユーザ」など

お知らせ ... アプリホーム画面で表示されているキャンペーンなどの実際の情報。上記のセグメントと紐つけパーソナライズを実現する。 "お知らせ has_one セグメント" の関係。

進め方

改善のきっかけ

たびたびマーケティングチームから、こんな「セグメント」が設定できるように、とある「ユーザ属性」を追加して欲しい といった依頼が来ます。

判定するユーザ属性の追加は、内部のコード的にはパターン化されており、比較的工数をかけずに対応できていましたが、ひとつ大きな問題を抱えていました。

それは判定するユーザ属性の追加ごとに、セグメント判定のためのクエリ長が伸びパフォーマンスがどんどん悪化すること でした。

前述したように追加コスト自体はそれほど大きくなかったため、この問題は認識されながらも年単位で放置されていました。 ですが、あるときマーケティングチームからユーザ属性の追加依頼が数件来たタイミングで、このまま放っておけないと改善へのメス入れに立ち上がったという流れです。

問題の認識

まず、ユーザ属性の判定を追加するごとにクエリ長が伸びる原因をコードレベルで理解しました。 サーバサイドの処理はざっくり以下のようでした。

  1. すべてのお知らせに対して設定されている全てのセグメントを走査し、リクエストユーザにマッチするセグメントを抽出する
  2. 1で抽出したセグメントに対応するお知らせを取得する
  3. お知らせには「公開期間」が指定できるので、公開期間中のもののみ選択し、レスポンスする

1の処理では、設定可能なユーザ属性ひとつひとつに対して条件判定を行っています。肝は「セグメントに対して設定されたユーザ属性」ではなく「設定可能なユーザ属性」に対して処理が走っていることです。判定するユーザ属性の追加ごとにクエリ長が伸びるのはこのためです。

導入しているDatadogのAPMから、ワーストケースだとこのセグメント抽出周りで1リクエストでデータベースへ40クエリほど投げていることも確認しました。

改善案

上記のフローを以下の様に組み替えます。

  1. 公開期間中のお知らせから、それらが持つセグメントのリストを取得
  2. そのセグメントがそれぞれ持っている複数のユーザ属性を個別に判定する
  3. 2の結果、セグメント対象と判定されたお知らせをレスポンスする

組み替えたメリットとして2つを想定していました。

  • 公開期間中のお知らせを対象に判定を行うので、そもそも公開期間のお知らせが存在しない場合はセグメント抽出関連の処理が走らないようになる
  • 公開期間中のお知らせを対象に、かつそのセグメント判定に必要な条件判定のみ行うようにすることで、クエリ長が伸びる根本原因が解消される

ここまで整理したところで、サーバサイドチーム内で共有と方針のレビューを行いました。 プロダクトマネージャにも現状の課題と工数感を共有し、作業を進めるための準備を行います。

どちらからもGOサインをもらえたので、あとは方針に従って再実装するだけです。

リリース後...

ひと通り実装が完了し、いざリリースです。Datadog の APM ではエンドポイント単位でのレスポンスタイムを見ることができるのでそちらを確認すると...

以下画像のように 大きな改善を得ることができました! リリース前後1週間でp99の平均値で比較すると 1.49s -> 0.80s と、46% の改善です。

また、アプリのホーム画面で必ず叩かれるAPIのレスポンスタイムを改善したこともあり、全エンドポイント共通でのレスポンスタイムにもトレンドの変化が見て取れます。

そのほか、発行されるクエリを改善したこともあり対象DBのCPU使用率もわずかながら改善していました。

(リリース後予想以上の改善に調子に乗っている様子)

まとめ

いかがでしたでしょうか。一口にパフォーマンスチューニングと言っても、意外とアプリケーションレイヤでロジックを見直すだけで今回のように成果が得られるケースもあるという紹介でした。 実際に取り組んでみて、数値に基づいた定量で成果を理解することができるのがパフォーマンスチューニングの良いところだなと思いました。

またチーム内でも「パフォーマンス改善に対してより前向きに取り組んでいきたい」という声が上がっていてポジティブな変化が生まれています。

ここまで読んで頂いたみなさんが、業務・個人問わず開発しているアプリケーションのロジックを見直すきっかけになったら幸いです。


マネーフォワードでは、エンジニアを募集しています。 ご応募お待ちしています。

【会社情報】 ■Wantedly株式会社マネーフォワード福岡開発拠点関西開発拠点(大阪/京都)

【SNS】 ■マネーフォワード公式noteTwitter - 【公式】マネーフォワードTwitter - Money Forward Developersconnpass - マネーフォワードYouTube - Money Forward Developers