広告配信システムでのトラフィック計測と実装方法 #ScalaAkiMatsuri

1f745ff900e1be51aedae18cae76593c?s=47 Kurochan
September 16, 2019

広告配信システムでのトラフィック計測と実装方法 #ScalaAkiMatsuri

Scala秋祭りで発表した資料です

1f745ff900e1be51aedae18cae76593c?s=128

Kurochan

September 16, 2019
Tweet

Transcript

  1. 広告配信システムでのトラフィック計測と
 実装⽅法 #ScalaAkiMatsuri 株式会社サイバーエージェント AI事業本部
 黒崎 優太 (@kuro_m )

  2. 黒崎 優太 • Dynalyst 開発責任者 • 業務は Scala + AWS

    • イラスト図解でよくわかる ITインフラの基礎知識 書きました • AWSのAZ(アベイラビリティーゾーン)とは?AZ障 害が起きたときどうすればよいのか 最近ちょっとバズりました @kuro_m @kurochan
  3. Scala Matsuri • Wi-Fiを担当しました • かなり好評だったようでよかったです • ネットワークの⼀部にScalaを導⼊ • 発表

    https://speakerdeck.com/kurochan/wi-fi-x-scala-implementing-captive-portal-in-scala-and- deploy-into-number-scalamatsuri
  4. 最近事業部名がかわりました • アドテク本部 → AI事業本部

  5. インターネット広告

  6. RTBのしくみ • RTB = Real Time Bidding 441 %41T

  7. RTBのしくみ • Webページがロードされ、広告タグが発⽕ 441 %41T

  8. RTBのしくみ • 枠情報やユーザ情報とともにbid requestがくる 441 %41T CJESFRVFTU

  9. RTBのしくみ • ⼊札額と表⽰する広告を決定 441 %41T "% "% "% CJESFRVFTU ԁ

    ԁ ԁ
  10. RTBのしくみ • Open RTBというプロトコルで⼊札 441 %41T "% "% "% ԁ

    ԁ ԁ
  11. RTBのしくみ • ⼊札額が⼀番⾼い事業者が勝利する 441 %41T "% "% "% ԁ ԁ

    ԁ XJO
  12. RTBのしくみ • SSPに広告クリエイティブが送られる 441 %41T "% "% "% ԁ ԁ

    ԁ "% XJO
  13. RTBのしくみ • SSP経由で落札した事業者の広告が展開される 441 %41T "% "% "% ԁ ԁ

    ԁ "% XJO "%
  14. RTBのしくみ • これを⾼速に繰り返す 441 %41T "% "% "% ԁ ԁ

    ԁ "% XJO "% औҾ͸NTҎ಺ʹ׬ྃ͠ͳ͚Ε͹ͳΒͳ͍
  15. Dynalystとは • Dynamic Retargeting for Games スマホ向けリターゲティング広告配信プラットフォーム トップセールス @⽇本のスマホゲームの中でも⾼いシェア ⽇本、アメリカを含む7カ国に配信中

    ユーザごとに最適化した広告を配信 IUUQXXXEZOBMZTUJP
  16. 開発している広告システム概況 • ⼊札リクエスト量: 数⼗万リクエスト / 秒 • ⼊札トラフィック: 約8Gbps •

    レスポンスタイム: 100ms以内 • ログの量: 数TB / Day(圧縮状態) ೔ຊͷೖࡳϦΫΤετඵ ೔ຊΞϝϦΧͷϨεϙϯελΠϜ NTFD https://logmi.jp/tech/articles/
  17. 最近⾒ていて楽しい画⾯ • オートスケーリング系のメトリクス

  18. Contents • 計測サーバの実装 計測の種類 Akka HTTPを利⽤した計測サーバの実装 • 計測パラメータの引き回し 広告で起こる困ったこと パラメータの引き回しを楽にする⼯夫

    • ログの転送‧集計 Amazon Kinesisを活⽤した⾃作ログ転送システム Amazon EMRを活⽤した集計システム
  19. 計測サーバの実装

  20. 計測の種類

  21. 計測の種類 • 広告の表⽰の計測 • 広告のクリックの計測/遷移 • Dynalystはリターゲティング広告配信システムのため外部の計測ツール経由 でユーザの情報を同期しないといけない • 例:

    ユーザがある広告主のゲームをインストールした、ログインした、課⾦した
  22. Akka HTTPを⽤いた計測サーバの
 実装

  23. Akka HTTP • HTTPサーバのフレームワーク • Akka Streamsで実装されている • HTTPで受けたリクエストからパラメータをパースしてファイルに吐き出すの が基本

    • 特に変わった使い⽅はしていません • Dynalystではもともとsprayを使っていたのでAkka HTTPに移⾏しました
  24. Akka HTTPの実装例 https://doc.akka.io/docs/akka-http/current/introduction.html

  25. 外部計測ツールとの連携 "% DMJDL ֎෦ܭଌπʔϧ

  26. 外部計測ツールとの連携 DMJDLαʔό "% DMJDL ֎෦ܭଌπʔϧ

  27. 外部計測ツールとの連携 DMJDLαʔό "% DMJDL ֎෦ܭଌπʔϧ DMJDL௨஌

  28. 外部計測ツールとの連携 DMJDLαʔό "% DMJDL ֎෦ܭଌπʔϧ DMJDL௨஌ 0,

  29. 外部計測ツールとの連携 DMJDLαʔό "% DMJDL ֎෦ܭଌπʔϧ SFEJSFDU DMJDL௨஌ 0,

  30. 外部計測ツールとの連携 DMJDLαʔό "% ֎෦ܭଌπʔϧʹ௨஌͔ͯ͠ΒϦμΠϨΫτ͢Δ DMJDL ֎෦ܭଌπʔϧ SFEJSFDU DMJDL௨஌ 0,

  31. 外部計測ツールとの連携 • 多くの広告主は外部の計測ツールを使ってインストールや起動を計測している • 広告のクリックを計測ツールに連携すると広告主は複数の広告配信事業者を横断して
 成果を⽐較することができるため、Dynalystの広告経由のクリック発⽣時に
 計測ツールに通知する必要がある • Akka Http

    Clientを使って通知を送信する
  32. DynamoDBへのアクセスを多重化する • AWS SDK for Java . が公開され、nettyベースの実装が追加された • nettyを使うとリクエスト部分がノンブロッキングになる

    • Dynalystでは端末情報はDynamoDBに保存しているため、
 リクエストの処理の並列度を⾼めるのは重要 • レスポンスがJavaのCompletableFutureで返ってくるので変換が必要 • scala-java -compatを使ってFutureに変換 • CompletableFutureはfail時にExceptionをCompletionExceptionでラップするので注意
  33. 計測パラメータの引き回し

  34. 計測パラメータの引き回し

  35. 計測パラメータとは • レポート作成、分析、外部システム連携のためのパラメータがたくさんあります • トランザクションID • 端末ID • 広告主ID •

    キャンペーンID • 通貨レート • ⼊札時の各種予測値(CTR, CVR, CPM等) • 全部で40〜50種類程度
  36. パラメータを引き回さないといけない理由 • ⼊札時のログがあるならばトランザクションIDのみURLパラメータで引き回 して、表⽰計測のログと⼊札時のログで集計時にJOINすればよいのでは? • 理論的にはそう • 実際はログの量が多く、あまりやりたくない… • ⼊札のログ:

    1⽇5TBくらい • 広告成果(アプリ起動や課⾦等)について、過去1週間分の⼊札ログと紐付けてレポートを作成 するといったようなユースケースがあるとすぐつらくなる • 集計で必要になるパラメータはURLパラメータを使って表⽰計測サーバまで引き回す
  37. パラメータの引き回しを楽にする
 ⼯夫

  38. 困っていたこと • パラメータ数が多い • パラメータを追加するごとに計測URLを⽣成/パースする部分のテストコードに修正が… • 気軽にA/Bテストしたいので追加するハードルを下げたい • URLが⻑い •

    使われなくなったパラメータが残りがち(3ヶ⽉前に⼊札した広告の計測が⾶んできたり, 後⽅互換性…) • 実装上の都合でパラメータ名をわかりやすくしようとするとURLが⻑くなる • 暗号化処理 • 機械学習の予測値等、内部パラメータはひとつひとつ暗号化していた • URLパラメータ改ざんへの耐性 • 不正なリクエストを送って不正に広告収益をあげようとする⼈たちがいる • 改ざん検知のコードのメンテが⾯倒だった
  39. Protocol Buffers • Googleが開発したプログラミング⾔語/プラットフォーム⾮依存のデータシ リアライズ/デシリアライズのフォーマットとその実装 • protoファイルでデータ構造を記述すると各種⾔語向けのクラス定義やシリ アライズ/デシリアライズのコードが⾃動⽣成できる • 後⽅互換性が保ちやすい設計

    https://developers.google.com/protocol-buffers/
  40. Scala x Protocol Buffers • コードを⾃動⽣成したかったのでScalaPBを採⽤

  41. Protocol Buffersのシリアライズ • 標準でバイナリに変換することはできる • toByteArray(), parseFrom(bytes: Array[Byte]) • URLパラメータに埋め込みたいのでバイナリはそのまま扱えない

  42. Protocol BuffersをURLパラメータに埋め込む • Base を採⽤ • 他⾔語でも容易に扱えるため • バイナリを毎回Base に変換するコードを書くのは避けたい、


    できればあまり意識したくない
  43. Protocol BuffersにScalaPBの拡張を⽤いる • ⽣成されるcase class/objectに⾃作traitを継承させる

  44. Protocol BuffersにScalaPBの拡張を⽤いる ࣗ࡞USBJU

  45. URLを短くしたい • ProtocolBuffersにしただけでも可変⻑変数やURLパラメータ名分短くなる 等、以前よりURLが短くなるような要素はある • ProtocolBuffersのwire formatからすると同じような表現が繰り返されそう なので何かしら圧縮はできそうである • Deflateを採⽤

    • 動作が軽めで他⾔語にもライブラリが存在しそうな汎⽤的なアルゴリズム • 前述のtraitにBase に変換する前にDeflateするメソッドを追加(その逆も)
  46. 暗号化したい • AES / Cipher Block Chaining / PKCS Padding

    • 多くのJVMはIntel CPUのAES-NIが使えるので⼗分な速度で処理が可能 • 前述のtraitにDeflateして暗号化してからBase に変換するメソッドを実装(その逆も) https://ja.wikipedia.org/wiki/ %E % A% %E % F%B %E % %A %E % %A %E % %A %E % %BC%E % %
  47. リプレイ攻撃に対する対処 • AES- はInitialization Vectorに128bit必要 • Base にエンコードすると22⽂字くらい必要 • IVはデコード側にも渡さないといけない

    • IVは固定する, 先頭にnonceを⼊れる • URLパラメータでIV分のデータ量を消費せず済む(衝突確率を考慮した上で128bit以下にする) • 前⽅にトランザクションIDが含まれているのでなくても⼤丈夫かもしれない • 例: 現在時刻のミリ秒を16bitで切り捨てて⼊れる
  48. 値が⼊っていない状態を表現する • Protocol Buffers(ver )だとoptional/requiredという概念が存在しない • プリミティブ値に値がセットされていない場合はプリミティブのデフォルト 値で埋められる (例: uint

    => ) • google/protobuf/wrappers.proto を使って解決
  49. ログの転送

  50. Amazon Kinesisを活⽤した
 ⾃作ログ転送システム

  51. ログ転送の仕組み ܭଌαʔό qVFOUE ,JOFTJT ετϦʔϜ ࠓճ࡞ͬͨͷ͸͜͜ 4 ετϨʔδ

  52. Amazon Kinesis Data Firehose • これでできることとほぼ⼀緒では… https://aws.amazon.com/jp/kinesis/data-firehose/

  53. ⾃作したかった理由 • Firehoseは「書き込まれる Amazon S オブジェクトに含まれる最も古いレ コードのおおよその到達タイムスタンプ」を⾒てファイルのアップロード先 パスを決定する • /path/to/

    / / / /に配置されるログには2019/09/15 23:59のログが混ざっている可 能性がある • リカバリ作業等で古いログをストリームに投げても
 Firehoseは最新の時刻のパーティションにログを配置する • ログのアップロード時刻ではなく、ログ中のタイムスタンプを⾒て
 パーティショニングがしたい
  54. Akka Streams • Source: メッセージを吐き出すコンポーネント(始点) • Flow: メッセージを受け取ってメッセージを送るコンポーネント • Sink:

    メッセージを受け取るコンポーネント(終点)
  55. Akka Streams • それぞれのinとoutの型が⼀致している • Source,Sinkの2番⽬, Flowの3番⽬の型パラメータは省略

  56. Akka Streams https://doc.akka.io/docs/akka/ . . /scala/stream/stream-quickstart.html

  57. 処理の流れ • それぞれのinとoutの型が⼀致している • Source,Sinkの2番⽬, Flowの3番⽬の型パラメータは省略

  58. 処理の流れ • Kinesisからレコードを取り出す • ログをバッファリングする • (⼀定のサイズ/時間が経過したら)S にレコードをアップロードする • Kinesisのチェックポイントを更新する

    • チェックポイント = ストリームをどこまで読み進めたか
  59. Kinesis Client LibraryをSourceとして使う • Kinesis Client Library = KCL •

    Kinesisを使ったストリーミング処理が簡単に書けるAmazon公式Javaライブラリ • KCLをAkkaStreamのSourceとして使うことができるライブラリがある • AkkaStreamsのおかげでKinesisのキャパシティを
 詰まることなくぴったり使い切れる https://github.com/StreetContxt/kcl-akka-stream
  60. kcl-akka-streamの使い⽅ • 最後にmarkProcessed()を
 呼ぶことでKCLのDynamoDBの チェックポイントを更新する • 処理前に呼ぶ: at most once

    • 処理後に呼ぶ: at least once
  61. ログをバッファリングするFlowの⾃作

  62. • パーティションごとにLocalのストレージにログをバッファリングする • ファイルにバッファリングさせることでメモリの消費量を抑えたかった • パーティションはログの中⾝のタイムスタンプで決定する • ⼀定容量/⼀定時間経過するとS にPutObjectする •

    KinesisRecordを引き回す必要がなければ半分くらいメモリ消費を抑えられ たが… • PutObjectが成功したらアップロードされたオブジェクトのS Pathと関係す るcontextのリストをpushする ログをバッファリングするFlowの⾃作
  63. Amazon EMRを活⽤した
 集計システム

  64. Amazon EMR • Apache Spark on Amazon EMR • クラスタコンピューティングフレームワーク

    • 分散共有メモリモデル • ノードを増やすことでスケールする • Spark MLibという機械学習ライブラリも &.3$MVTUFS .BTUFS 4MBWF 4MBWF 4MBWF 4MBWF
  65. Apache Sparkで集計を実装する • 例: 広告主 x ⽇時の粒度で
 クリック数をカウントする 42-Ͱॻ͍ͨ৔߹ 4QBSLͰॻ͍ͨ৔߹

  66. いいところ • スケールする • サーバを増やせば… (多い時でCPU Core / Memory TBくらいのクラスタが稼働しています)

    • テストコードが書ける • テストコードがないと複雑な集計を実装するのは厳しい • 例: 広告主やSSPの国に応じて外部DBからタイムゾーンを取得し、
 通貨単位も変換して集計する • 可⽤性をあまり落とさずにスポットインスタンスを活⽤して
 格安でクラスタ運⽤ができる • Sparkのおかげで⼀部のワーカーが落ちても
 ⾃動でリカバリ & 集計は中断されない https://developers.cyberagent.co.jp/blog/archives/ /
  67. ありがとうございました