はじめに

こんにちは!同志社大学大学院 修士 1年の近藤 大輔です。

2026年6月3日 〜 6月30日の1ヶ月間、CyberAgent が主催するインターンシップ「 CA Tech JOB 」に参加しました。

 

私が配属されたのは、株式会社AJAの DSP チームで、バックエンドエンジニアとして勤務させていただきました。

本記事では、広告配信基盤で発生していた Pub/Sub メッセージ滞留の原因を調査し、KEDA を利用した事前スケールアウトによって改善した取り組みについて紹介します。

参加背景

私がこのインターンに参加した理由は以下の2つです。

  1. 高トラフィック環境でのパフォーマンス改善について調査から設計・実装までの一連のプロセスを体験してみたい
    個人開発ではなかなか経験できない高トラフィック環境での開発を経験したいと考えていました。そこで発生するパフォーマンス課題や、その改善プロセスを実践的に学びたいという思いがありました。
  2. サイバーエージェントの社員の方と話し、将来のキャリアを見つけたい
    サイバーエージェントには、多様な部署や職種の方が在籍しています。そのため、社員の方々と交流を重ねることで、今後の自分自身のキャリア形成におけるヒントを得たいと考えていました。

AJA DSPとは

私が勤務した AJA DSP は、Demand Side Platform(DSP) の略で、広告主や広告代理店が広告を配信するためのシステムです。メディアからの広告リクエストが SSP を経由して送られてくると、配信条件やターゲティング、入札価格などをもとに最適な広告を選択し、入札結果を SSP に返します。SSP は複数の DSP から返された入札結果を比較し、最も条件に合った広告を選択してメディアへ配信します。

課題の詳細

DSP の広告配信における予算管理

DSP では、広告が実際に表示されると、その配信記録であるインプレッションログ(以降 IMP とします)が DSP サーバーに送られます。この IMP ログを DB(Bigtable)へ記録することで、広告の表示回数や、残りの広告予算を管理しています。

 

広告には月ごとの予算が設定されています。しかし、1ヶ月分の予算を1日で使い切ってしまうと、残り約30日間はその広告を配信できなくなります。これでは広告配信に偏りが生じてしまいます。
そこで、月予算をその月の日数で割り、1日あたりの日予算を設定します。そして、1日の配信費用が設定された日予算に達した時点で、該当する広告の配信を停止する制御が行われます。
このような仕組みを用いて、均等配信を行う場合には、広告費用が特定の日に偏らないように制御しています。

日付が変わるタイミングでのIMPログ増加

DSP の広告配信における予算管理では、日付が変わるタイミングで IMP ログが増加します。

なぜなら、日付が変わると1日の配信予算がリセットされ、日予算に達して停止していた広告が再び配信されるためです。

以下の図は、実際に日付が変わるタイミングにおける IMP ログのリクエスト数を示したメトリクスです。このメトリクスからも、日付が変わるタイミングで IMP ログが増加していることが分かります。

 

IMPログ増加で発生する課題

今回の課題は、IMP ログが増加すること自体ではありません。問題となるのは、IMP ログの増加によって Bigtable へのログ反映が遅延することです。

DSP では、 Bigtable に記録された IMP ログをもとに、時間ごとの目標配信量に沿って広告配信を制御しています。しかし、日付が変わるタイミングでは IMP ログが急増するため、 Bigtable への書き込みに遅延が生じます。

 

その結果、実際には時間ごとの目標配信量に達しているにもかかわらず、 Bigtable 上ではまだ目標に達していないと判断される可能性があります。これにより、広告配信の抑制が遅れ、一時的に目標のペースを上回って配信される可能性がありました。

原因の調査

IMP ログが Bigtable へ反映されるまでの処理は、以下の図のようになっています(実際の処理はより複雑ですが、一部を抜粋しています)。
まず、 IMP ログが Pub/Sub に送信されます。その後、一定数のメッセージが蓄積されると、イベント管理システムがそれらをまとめて Bigtable へ書き込みます。
イベント管理システムはストリーム処理エンジンです。しかし、セルフホスティング型の社内 SaaS のため、内部でどのような処理が行われているかが分からず、ブラックボックスとなっています。

まずは、Pub/Sub に蓄積されているメッセージ数について調査しました。以下の図は、 ack されずに Pub/Sub に残っているメッセージ数を示したメトリクスです。
このメトリクスから、日付が変わった直後の 0:03 に unacked のメッセージが急増していることが分かります。

次に、同じ時間帯における Pub/Sub の unacked メッセージの最長滞留時間を調査しました。以下の図は、unacked メッセージのうち、最も古いメッセージが Pub/Sub に送信されてからの経過時間を示すメトリクスです。

 

このメトリクスを見ると、 unacked メッセージ数が増加したタイミングで、 unacked メッセージの最長滞留時間が約1分まで増加していました。

 

つまり、 一部の IMP ログは最大で約1分間 Pub/Sub に滞留し、その間は Bigtable へ反映されません。その結果、配信状況の反映が遅れ、広告が目標を上回るペースで配信される可能性があることが分かりました。

 

また、直近30日間を調査したところ、Pub/Sub で1分以上の滞留が発生した日は複数回ありました。

仮説1:イベント管理システムのスケールアウトが間に合っていない

イベント管理システムは GKE(Google Kubernetes Engine) 上で動作しており、HPA(Horizontal Pod Autoscaler)によって Pod の CPU 負荷に応じて自動でスケールします。

しかし、IMP ログが増加して Pod の CPU 負荷が高まった後にスケールアウトが始まるため、スケールアウトが完了するまでの間は増加した IMP ログに対応しきれません。その結果、処理しきれなかった IMP ログが Pub/Sub に滞留している可能性があると考えました。

仮説1の調査

以下の図は、Pub/Sub で2分の滞留が発生した日における unacked メッセージの最長滞留時間(左)とイベント管理システムの Pod 数の推移(右)を示しています。

 

この図を見ると、 Pub/Sub にメッセージが滞留しているにもかかわらず、 イベント管理システムの Pod 数は増加していませんでした。このことから、スケールアウトの遅れではなく、そもそもスケールアウト自体が発生していないと判断しました。

仮説2:CPUリソースが不足している可能性

仮説1の調査では、イベント管理システムはスケールアウトしておらず、スケールアウトの遅れが原因ではないことが分かりました。

 

そこで、イベント管理システム自体の処理能力に着目しました。具体的には、 CPU リソースが不足することで、IMP ログの処理速度が低下し、処理しきれなかったメッセージが Pub/Sub に滞留している可能性があることを考えました。

仮説2の調査

以下の図は、 Pub/Sub で2分の滞留が発生した日におけるイベント管理システムの各 Pod の CPU 使用率を示しています。

 

この図を見ると、 Pub/Sub にメッセージが滞留した時間帯(緑線付近)でも、 CPU 使用率は約20%で推移していました。CPU 使用率には十分な余裕があったことから、 CPU リソース不足がメッセージ滞留の原因である可能性は低いと考えました。

仮説3:Bigtableの書き込みレイテンシーが高い

仮説2の調査では、 イベント管理システムの CPU 使用率には十分な余裕があり、 CPU リソース不足がメッセージ滞留の原因ではないことが分かりました。

 

そこで次に着目したのが、 Bigtable への書き込み遅延です。

イベント管理システムは Pub/Sub から受け取った IMP ログを Bigtable に書き込んでいるため、 Bigtable 側のレイテンシーが高ければ、処理全体が詰まり、 Pub/Sub にメッセージが滞留する可能性があることを考えました。

仮説3の調査

以下の図は、 Pub/Sub でメッセージが滞留した日における、イベント管理システムから Bigtable への書き込みレイテンシーを示しています。

 

この図を見ると、 Pub/Sub にメッセージが滞留した時間帯(緑線付近)においても、 Bigtable への書き込みレイテンシーは他の時間帯と比べて大きな変化は見られませんでした。このことから、 Bigtable の書き込みレイテンシーがメッセージ滞留の原因である可能性は低いと判断しました。

仮説4:イベント管理システムとBigtable間のコネクション数が不足

仮説2・3の調査から、 CPU リソース不足や Bigtable の書き込みレイテンシーが原因である可能性は低いと判断しました。

 

イベント管理システムの HPA の設定では CPU 使用率が高くなると Pod がスケールアウトします。しかし、今回のケースでは CPU 使用率が上昇していないため、Pod のスケールアウトも発生していませんでした。

そのため、CPU ではなく I/O 待ちが発生している可能性に着目しました。具体的には、イベント管理システムから Bigtable へのコネクション数が上限に達し、新たな書き込み処理がコネクション待ちとなっている可能性があると考えました。

仮説4の調査

イベント管理システムはブラックボックスとなっているため、内部で利用しているコネクション数を直接確認することはできません。

そのため、イベント管理システム内部を直接調査するのではなく、Pod 数を変化させた場合に Pub/Sub の滞留が改善するかを確認することで、間接的に仮説を検証することにしました。

具体的には、HPA の minReplicas を maxReplicas と同じ値に設定し、常に最大数の Pod を起動した状態で動作を確認しました。

 

まず、Pub/Sub への Publish 数を比較しました。

以下の図より、事前に Pod を最大数まで起動した日の方が Pub/Sub への Publish 数は多く、イベント管理システムの処理対象となるメッセージ数も多いことが分かります。そのため、この後の比較結果は、「負荷が小さかったため改善した」のではないことを示しています。

次に Pub/Sub の unacked メッセージ数を比較しました。

以下の図より、事前に Pod を最大数まで起動した日は、メッセージ滞留が発生した日と比較して、 unacked メッセージ数の増加が約20分の1程度に抑えられていたことが確認できます。

そのため、より多くのメッセージが Pub/Sub へ送信された状況でも、Pub/Sub に滞留するメッセージ数が大幅に減少しました。

最後に、Pub/Sub における最長 ack 待ち時間を比較しました。

以下の図よりメッセージ滞留が発生した日は最長 ack 待ち時間が約2分まで増加していましたが、事前に Pod を最大数まで起動した日は約4秒まで短縮されていました。

これは、メッセージ数だけでなく、メッセージが処理されるまでの時間も大幅に改善されたことを示しています。

以上の結果から、より多くのメッセージが Pub/Sub へ送信された状況でも、Pod 数を事前に確保することで Pub/Sub のメッセージ滞留を大幅に改善できました。

Pod 数の増加に伴い、Bigtable への書き込みに利用できるコネクション数も増えるため、イベント管理システム内部では、このコネクション数がボトルネックとなっていた可能性が高いと考えました。

解決方法

仮説4の調査から、イベント管理システム内部では Bigtable への書き込み時に利用できるコネクション数がボトルネックとなっていた可能性が高いことが分かりました。

そこで、まず考えた解決策は、イベント管理システムから Bigtable へのコネクション数を増やすことです。しかし、イベント管理システムはブラックボックスとなっており、内部実装を変更することはできませんでした。そのため、この方法を採用することはできませんでした。

 

次に、仮説4の調査で実施したように、日付が変わる前から十分な数の Pod を起動しておく方法を検討しました。これにより、日付が変わるタイミングで Pod の起動を待つことなく、十分な数のコネクションを利用できる状態を維持できます。

この仕組みを自動化するために、KEDA の Cron Scaler を導入しました。Cron Scaler を利用することで、指定した時刻に合わせて Pod を自動的にスケールアウトできるため、日付が変わる前に Pod 数を増やし、アクセス増加に備えることができます。

(参考: https://keda.sh/docs/2.20/scalers/cron/

具体的なスケール条件は、23:45 〜 1:20は Pod を maxReplicas に固定し、それ以外の時間帯は、従来の HPA の設定と同じように CPU でスケールするように設定しました。

スケール条件の根拠

23:45 からスケールアウトするように設定したのは、日付が変わるタイミングまでに Pod の起動と JVM の Warm up を完了させるためです。

 

事前調査の結果、Pod の起動自体は最大で約1分かかることが分かりました。しかし、Node に十分な空きリソースがない場合は、新たに Node が作成されてから Pod が起動されます。そのため、最悪のケースでは、Node の作成から Pod の起動完了までに最大で約5分かかることが分かりました。

 

また、イベント管理システムは Java で実装されているため、JVM の Warm up にも最悪の場合で約5分かかることが分かりました。したがって、Node 作成・Pod 起動と JVM の Warm up を合わせると、最悪のケースでは約10分程度かかります。この時間に5分のマージンを加え、日付が変わる15分前の23:45 からスケールアウトする設定としました。

 

1:20 まで Pod 数を維持するように設定したのは、過去の傾向として、Pub/Sub のメッセージ滞留が 1:00 頃まで発生していたためです。そのため、20分のマージンを設け、1:20 からスケールダウンするように設定しました。

HPAからKEDAへの移行で注意したこと

今回の対応では、従来利用していた HPA から、KEDA の ScaledObject へスケール制御を移行しました。このとき、既存の HPA を残したまま ScaledObject をデプロイすると、1つの Deployment に対して2つの HPA がスケール制御を行う状態となり、コンフリクトが発生します。
そのため、HPA で設定していた CPU によるスケール条件も ScaledObject 側へ移行し、 Deployment を監視する HPA が1つだけになるように構成しました。

また、移行手順にも注意が必要でした。既存の HPA を削除してから ScaledObject をデプロイするまでの間は、自動スケールが機能しない時間帯が発生します。このタイミングでアクセスが急増すると、Pod 数が不足し、今回と同様のメッセージ滞留が発生する可能性があります。
そのため、移行時は事前に HPA の minReplicas maxReplicas まで引き上げ、十分な数の Pod を確保した状態で HPA を削除しました。その後、ScaledObject をデプロイし、スケール制御を KEDA へ移行しました。

この手順を採用することで、移行中も十分な処理能力を維持したまま、安全に HPA から KEDA へ移行することができました。

今後の展望

Pub/Subのメトリクスを用いたスケール制御

今回の対応では、日付が変わるタイミングに合わせて Pod を事前にスケールアウトすることで、Pub/Sub のメッセージ滞留を大幅に改善できました。

一方で、この対策は日付が変わるタイミングに発生するアクセス増加を見越して、あらかじめ Pod 数を増やすという戦略です。今回の課題は、Publish 数が増加しても Pod の CPU 使用率が十分に上昇せず、 HPA が負荷の増加を検知できない点にあります。その結果、CPU 使用率を指標とする従来の設定では、HPA によるスケールアウトの条件を満たしませんでした。

 

そこで今後は、既存の Cron Scaler による事前スケールアウトや CPU 使用率による制御に加えて、Pub/Sub のメトリクスを用いたスケール制御を検討しています。KEDA の ScaledObject では、Pub/Sub の Publish 数や unacked メッセージ数などのメトリクスをスケール指標として利用できます。

 

これらのメトリクスを利用することで、CPU 使用率だけでは検知できないメッセージの急増や滞留状況をもとに、Pod 数を制御できるようになります。その結果、日付が変わるタイミングのように発生時刻を予測できるスパイクには Cron Scaler であらかじめ備えつつ、Pub/Sub の実際の滞留状況に応じた対応が可能になります。これにより、メッセージが滞留しているにもかかわらず CPU 使用率が低いためにスケールインしてしまう状況を防ぎ、実際の処理状況に応じたスケール制御を実現できると考えています。

 

Pod起動時のWarm Upを考慮したトラフィック制御

今回の対応により、日付が変わるタイミングにおける Pub/Sub のメッセージ滞留は大幅に改善することができました。

しかし、Pod 数を maxReplicas までスケールさせて運用した場合でも、 Pub/Sub でメッセージ滞留が発生しました。

この時間帯を調査したところ、同時刻に Node の縮退が発生し、一部の Pod が別 Node へ再配置されていたことが分かりました。

このことから、再配置された Pod が起動直後の Warm Up 中であったため、十分な処理能力を発揮できない状態でリクエストを受け付け、Pub/Sub にメッセージが滞留した可能性があると考えています。

今後は、Pod 起動直後に Pub/Sub からのメッセージ取得をすぐに開始するのではなく、Warm Up が完了してから処理を開始できるような仕組みを検討したいと考えています。

JOBインターンを通して学んだこと

今回のインターンを通して、パフォーマンス課題の調査では、仮説を立ててから検証することが重要だと学びました。

最初は、どのメトリクスを確認すべきか分からず、的外れな仮説を立てることもありました。しかし、仮説を立てて検証することを繰り返す中で、少しずつ原因に近づくことができました。この経験から、システム構成や処理の流れをもとに、原因として考えられる箇所を整理し、調査を進める力を身につけることができました。これは、今回の環境に限らず、どの現場でも応用できる力だと感じています。

また、調査や検証の過程では AI を活用することができましたが、仮説を立てずに「この課題について調査してほしい」と聞くだけでは、実際に確認すべき観点から外れてしまうことがありました。そのため、AI を有効に活用するためにも、人間が課題を整理し、調査の方向性を決める力が重要だと感じました。

 

加えて、 AI 時代においても、エンジニアリングの知識は重要だと感じました。

AI を活用することで、設計案の作成や実装を効率化することはできます。しかし、その設計や実装が本当に適切かを評価するのは人間です。特に、パフォーマンスや運用面まで考える必要がある場面では、複数の選択肢の中から、システムの制約や要件に合った方法を選ぶ必要があります。

そのため、AI に任せる部分が増えたとしても、人間側には、AI の出力を評価し、最適な判断を行うためのエンジニアリング知識が必要だと学びました。

 

おわりに

この1ヶ月間、実際の高トラフィックな配信基盤に触れ、パフォーマンス改善の調査から解決策の導入までを一貫して経験できたことは、私にとって非常に貴重な経験となりました。

 

最後に、手厚くサポートしてくださったトレーナーさん、メンターさんをはじめ、1ヶ月間温かく迎えてくださった AJA DSP チームの皆さんに心から感謝します。本当にありがとうございました!