こんにちは、 m-hamashita です。
最近、広告のスコアリングサーバをフルリプレイスしました。 今回はフルリプレイスした背景と設計、Istio を用いたサーバ移行について紹介します。
背景
広告のスコアリングサーバとは
広告のスコアリングサーバは広告の CTR や CVR を推定し、ランキング付けするためのスコアや入札単価を算出するサーバです。 このサーバによって、ユーザごとに適した広告を配信することができます。
なぜスコアリングサーバをフルリプレイスしたのか
今回スコアリングサーバをフルリプレイスした主な理由は以下のようになります。
- ディープラーニングモデルで広告のスコアリングをおこないたい*1
- 既存のスコアリングサーバ設計では新しい機能を追加することが難しく、アドホックな実装が多かったため設計の見直しが必要
もちろん A/B テスト時は同等の性能となることを確認したいため、既存のスコアリングサーバと同じビジネスロジックで動作するようにしました。
スコアリングサーバのアーキテクチャ
今回のスコアリングサーバのアーキテクチャを簡単に表すと以下のようになります。
上記のアーキテクチャにおける各コンポーネントの役割は次のとおりです。
- 広告配信サーバ
- 実際に広告を配信するサーバ
- スコアリングサーバと gRPC でやり取りし、スコアリングサーバから返ってきた結果を用いて広告候補から配信する広告を選択
- Go で実装
- 実際に広告を配信するサーバ
- スコアリングサーバ
- スコアリング
- モデルによって CTR や CVR を推定しスコアや入札単価を算出し、広告配信サーバに返すコンポーネント
- ディープラーニングモデルの推論に ONNX Runtime を採用
- Go の場合 C の API を呼び出すのにかかるコストが大きいなどの理由から Rust で実装*2
- モデル管理
- モデルと特徴量の変更を検知、ロードしたものを共有ディレクトリに保存し、スコアリングコンポーネントに通知
- 設計時点で Rust の AWS SDK*3 が developer preview だったなどの理由から Python で実装
- スコアリング
- 各モデルの学習
- CTR/CVR を推定するモデルを学習し、モデルと特徴量を S3 と Aurora に保存
- digdag で定期的に学習を実行
ビルドツールに Bazel を使用しています。導入したときの背景については以下の記事を参考にしてください*4。
Istio を用いたサーバ移行
サーバ移行は弊社の A/B テスト基盤に加えて、Istio を用いることにしました。 そうすることで、一気に移行するのではなく、段階的に移行することができ、移行中に発生した問題を検知し、対応することができます。
ここでは、A/B テストの設定と Istio の設定について説明します。 全体的な流れとしては A/B テストのパラメータにどちらのサーバにリクエストを送るかというパラメータを設定することで、どちらのサーバにリクエストを送るかを決定できるようにするといったものです。
A/B テスト基盤の設定
弊社では独自の A/B テスト基盤を使用しています。こちらの基盤では yaml ファイルに記述することで、A/B テストの設定を行うことができます。 また、CI で設定の validation をおこなっており、(トラフィックの総和が 100% になっていない等)誤りがある場合は検知することができます。
A/B テスト基盤についてはこちらの記事を参考にしてください。
A/B テスト基盤を使用したサーバ移行の設定は以下のように記述します。
apiVersion: v1 kind: ReplaceServer spec: groups: - name: old_server/new_server controls: - old_server_A treatments: - new_server_A hashSalt: type: const value: hogefugapiyo traffics: - name: old_server_A ratio: 20 version: 1 parameters: route: old_server - name: new_server_A ratio: 20 version: 1 parameters: route: new_server - name: old_server_B ratio: 60 version: 1 parameters: route: old_server
簡単にこの yaml ファイルの説明をします。まず、 traffics
では A/B テストのトラフィックの設定をおこないます。
old_server_A
は 20% のトラフィックを old_server
に送る、new_server_A
は 20% のトラフィックを new_server
に送るというような設定になります。
groups
では A/B テストのグループの設定をおこないます。ここの controls
と treatments
には traffics
で設定した name
を設定します。この設定をおこなうことで、A/B テスト毎に各種 KPI を自動で集計しレポートを作成することができます。
A/B テストのレポート自動作成についてはこちらの記事を参考にしてください。 tech.gunosy.io
A/B テストの設定は広告配信サーバで受け取るようにしています。 広告配信サーバからスコアリングサーバに gRPC リクエストの metadata にルーティング情報を追加することで、A/B テストの設定に従ってリクエストを送ることができます。
広告配信サーバは Go で実装されています。gRPC の metadata にルーティング情報を追加するためには以下のようにします。
ctx = metadata.AppendToOutgoingContext(ctx, "route", route)
Istio によるルーティング
Istio では VirtualService を用いて、リクエストを送るサーバを変更することができます。 具体的には以下のように設定し、ヘッダー内の route に基づいてリクエストを送るサーバが変更できるようにします。 また、route が設定されていないときは、基本的に安定に動作する old-server にリクエストを送るようにしています。
apiVersion: networking.istio.io/v1beta1 kind: VirtualService metadata: name: new-server spec: hosts: - new-server http: - match: - port: 50051 headers: route: exact: new_server route: - destination: host: new-server port: number: 50051 - match: - port: 50051 headers: route: exact: old_server route: - destination: host: old-server port: number: 50051 - match: - port: 50051 route: - destination: host: old-server port: number: 50051
実際、トラフィックを大きくした時に負荷が高まり、レイテンシが悪化するといったことがありました。 そういった時にトラフィックを減らし、どの部分がボトルネックになっているかトレーシングによって特定し、改善する(必要であれば負荷試験を実施)といったサイクルを回すことができました。
具体的にレイテンシを改善した方法に関してはこの記事では割愛し、別の記事で紹介したいと思います*5。 最終的にはレイテンシを改善することができ、A/B テストの結果も悪くなく、完全移行することができました。
さいごに
広告のスコアリングサーバをリプレイスした話について紹介しました。 インフラからサーバサイドまで、様々な技術を触ることができて学びも多く、とても楽しく開発できました*6。
今回の記事ではまだまだ紹介できていない部分があるので、今後は詳細な記事を出していく予定です!