Gunosy Tech Blog

Gunosy Tech Blogは株式会社Gunosyのエンジニアが知見を共有する技術ブログです。

広告のスコアリングサーバをフルリプレイスしました

こんにちは、 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

tech.gunosy.io

Istio を用いたサーバ移行

サーバ移行は弊社の A/B テスト基盤に加えて、Istio を用いることにしました。 そうすることで、一気に移行するのではなく、段階的に移行することができ、移行中に発生した問題を検知し、対応することができます。

ここでは、A/B テストの設定と Istio の設定について説明します。 全体的な流れとしては A/B テストのパラメータにどちらのサーバにリクエストを送るかというパラメータを設定することで、どちらのサーバにリクエストを送るかを決定できるようにするといったものです。

A/B テスト基盤の設定

弊社では独自の A/B テスト基盤を使用しています。こちらの基盤では yaml ファイルに記述することで、A/B テストの設定を行うことができます。 また、CI で設定の validation をおこなっており、(トラフィックの総和が 100% になっていない等)誤りがある場合は検知することができます。

A/B テスト基盤についてはこちらの記事を参考にしてください。

tech.gunosy.io

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 テストのグループの設定をおこないます。ここの controlstreatments には 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

Istio によるルーティングのイメージ
このように設定することによって、トラフィックを徐々に新しいサーバの方に移行することができます。

実際、トラフィックを大きくした時に負荷が高まり、レイテンシが悪化するといったことがありました。 そういった時にトラフィックを減らし、どの部分がボトルネックになっているかトレーシングによって特定し、改善する(必要であれば負荷試験を実施)といったサイクルを回すことができました。

具体的にレイテンシを改善した方法に関してはこの記事では割愛し、別の記事で紹介したいと思います*5。 最終的にはレイテンシを改善することができ、A/B テストの結果も悪くなく、完全移行することができました。

さいごに

広告のスコアリングサーバをリプレイスした話について紹介しました。 インフラからサーバサイドまで、様々な技術を触ることができて学びも多く、とても楽しく開発できました*6

今回の記事ではまだまだ紹介できていない部分があるので、今後は詳細な記事を出していく予定です!

*1:既存のスコアリングサーバでは LightGBM でスコアリングをおこなっていました

*2:既存のスコアリングサーバはすべて Go で実装

*3:https://github.com/awslabs/aws-sdk-rust

*4:色々と苦労した点もあるので、詳細な記事も書けたらいいなと思っています

*5:個人的には ISUCON みたいな感じで楽しかったです

*6:特にモブプロで設計について深く議論した経験ができたのは良かったです