Upgrade to Pro — share decks privately, control downloads, hide ads and more …

推測するな、計測せよ。

CyberAgent
PRO
February 22, 2019

 推測するな、計測せよ。

CA BASE CAMP 2019
推測するな、計測せよ。
〜広告乱択アルゴリズムのボトルネックを探して〜
鳥越 貴智

CyberAgent
PRO

February 22, 2019
Tweet

More Decks by CyberAgent

Other Decks in Technology

Transcript

  1. 推測するな、計測せよ。
    〜広告乱択アルゴリズムのボトルネックを探して〜

    View Slide

  2. ⿃越 貴智
    • アドテクスタジオ AMoAd
    中途 3 年⽬
    データ‧機械学習エンジニア
    • 量⼦ゼミ
    ScalaMatsuri で登壇しました ( ・ㅂ・)و
    早稲⽥の⽥中宗先⽣とアニーリングの話をしてます><
    piyo
    piyo z

    View Slide

  3. プロダクト紹介

    View Slide

  4. • CyberAgent と DeNA の合弁会社
    2011 年 4 ⽉に設⽴
    開発はアドテクスタジオ
    アドネットワーク専業
    • 多彩な広告フォーマット
    AMoAd ネットワーク
    AMoAd インフィード
    AfiO(動画)
    D AD

    View Slide

  5. 8 年⽬って技術的負債ヤバくない?

    View Slide

  6. \技術的アンチエイジング∕
    • 広告配信システムのマイクロサービス化
    おおむね Kubernetes へ移⾏
    詳しくは和⽥さんの「レガシーシステムのコンテナ化に挑戦した話」
    • データ基盤のマネージド化
    Dataproc (Spark)
    BigQuery
    • ロジックの機械学習化
    タイムラグのあるインストール率推定
    広告乱択アルゴリズムの刷新

    View Slide

  7. 広告乱択アルゴリズム

    View Slide

  8. 探索と活⽤のトレードオフ
    • 探索:⾊んなスロットを回して当たりやすいものを探す
    • 活⽤:当たりやすいスロットだけを回す
    ※ 探索しないと⼀番当たりやすいスロットを⾒逃すかも
    ※ 探索しすぎるとコストが嵩むかも

    View Slide

  9. 広告乱択アルゴリズムを変えたい
    広告選択のアルゴリズムは、乱数によって探索と活⽤のバランスを取る。
    古いものは探索コストをかけすぎていたため、⼊れ替えることに。
    • 古いの
    ソフトマックスっぽい⽅策
    ⼀様分布から乱数をサンプリングする
    • 新しいの
    トンプソンサンプリング⽅策
    ベータ分布から乱数をサンプリングする

    View Slide

  10. 確率分布
    • ⼀様分布 :0 以上 1 以下の値が均等に出現
    • ベータ分布:0 以上 1 以下の値が偏って出現(偏り⽅のパラメータがある)

    View Slide

  11. do {
    final double u1 = random.nextDouble();
    final double u2 = random.nextDouble();
    final double v = beta * (FastMath.log(u1) - FastMath.log1p(-u1));
    w = a * FastMath.exp(v);
    final double z = u1 * u1 * u2;
    r = gamma * v - 1.3862944;
    final double s = a + r - w;
    if (s + 2.609438 >= 5 * z) {
    break;
    }
    t = FastMath.log(z);
    if (s >= t) {
    break;
    }
    } while (r + alpha * (FastMath.log(alpha) - FastMath.log(b + w)) < t);
    ベータ分布のサンプリング実装

    View Slide

  12. ベータ分布のサンプリング実装
    . ⼀様分布から 2 回サンプリング
    . ややこしそうな計算
    . よくわからない条件を満たすまでループ
    ※ Apache Commons Math の BetaDistribution.java からコードを抜粋

    View Slide

  13. do {
    final double u1 = random.nextDouble();
    final double u2 = random.nextDouble();
    final double v = beta * (FastMath.log(u1) - FastMath.log1p(-u1));
    w = a * FastMath.exp(v);
    final double z = u1 * u1 * u2;
    r = gamma * v - 1.3862944;
    final double s = a + r - w;
    if (s + 2.609438 >= 5 * z) {
    break;
    }
    t = FastMath.log(z);
    if (s >= t) {
    break;
    }
    } while (r + alpha * (FastMath.log(alpha) - FastMath.log(b + w)) < t);
    ベータ分布のサンプリング実装

    View Slide

  14. もしかして停⽌する保証ない?

    View Slide

  15. 当時の推測
    org.apache.commons.math .distribution.BetaDistributionの実装は、
    ⼀様分布を使って条件を満たすまでwhileループしつづけるものなので、
    最悪ケース終わらないかもしれない。
    そこでベータ分布の期待値は乱数使わずに計算できるため、
    期待値の多い順からサンプリングをし、
    タイマーが切れたところで最⼤のものを取る仕組みを⼊れると良さそう。

    View Slide


  16. ͔
    ͘
    ݟ

    Δ

    ۀ




    ܭ
    Λ
    վ

    ͠
    ͯ
    ͘
    Ε
    ͨ


    ʮ͋ɺ͜Εແཧ͔΋ʯͱ਒͑Δࢲ

    View Slide

  17. 負荷テスト

    View Slide

  18. 性能要件
    • ⼀般的にアドテクの許容 Latency はトータル 100ms 以内が⽬安
    • 当然その⼀部であるマイクロサービスの許容 Latency はもっと短い
    • 現状ピーク時間帯 1500 qps の 95% を Latency ms 以内でさばいている
    • Latency ms 以内を要件に

    View Slide

  19. 負荷テスト
    . テスト環境に広告選択マイクロサービス (Finagle) を⽴てて本番 DB に繋ぐ
    . 本番ログからテスト⽤シナリオを⽣成する
    . Gatling で負荷をかけて response time を計測する
    . Finagle のプロファイリング⽤エンドポイントを叩く

    View Slide

  20. 計測結果(チューニング前)
    • Gatling で計測した response time は明らかに性能要件を満たせてない
    • Finagle のプロファイルを⾒ると、
    • BetaDistribution は呼んでるけど、
    • AbstractWell とか Well c (AbstractWell の⼦クラス) とか覚えない……
    $ pprof -cum —text profile | grep math3
    … 29.3% org.apache.commons.math3.distribution.BetaDistribution.
    … 29.3% org.apache.commons.math3.random.Well19937c.
    … 29.3% org.apache.commons.math3.random.AbstractWell.
    … 29.3% org.apache.commons.math3.random.AbstractWell.setSeed

    View Slide

  21. ボトルネックのコード
    AbstractWell.java からコードを抜粋
    • JDK の System.identityHashCode のバグを踏んでいるっぽい
    JDK- : Performance problem with System.identityHashCode in client
    compiler
    public void setSeed(final int[] seed) {
    if (seed == null) {
    setSeed(System.currentTimeMillis() + System.identityHashCode(this));
    return;
    }

    View Slide

  22. 原因と対策
    • リクエストごとに異なるベータ分布から乱数をサンプリングしたい
    • リクエストごとにベータ分布乱数⽣成器をコンストラクト
    • その際に呼ばれる⼀様乱数⽣成器のコンストラクタがボトルネック
    • ベータ分布乱数⽣成器のコンストラクタで⼀様乱数⽣成器を注⼊可能
    • その⼀様乱数⽣成器は使いまわしてよいがスレッドセーフでない
    • スレッドごとに⼀様乱数⽣成器を⼀度だけコンストラクトして使いまわす

    View Slide

  23. 改修コード
    • Java 標準の ThreadLocal で⼀様乱数⽣成器をスレッドローカル変数化
    class PickStage {
    val threadLocalRng: ThreadLocal[Well19937c] =
    ThreadLocal.withInitial(() => new Well19937c())
    ……
    def sampleBetaDistribution(alpha: Double, beta: Double): Double =
    new BetaDistribution(threadLocalRng.get, alpha, beta).sample()
    }

    View Slide

  24. 計測結果(チューニング後)
    • 再度 Finagle のプロファイルを⾒ると、ボトルネック消失
    • キャッシュアクセスなどもチューニングして性能要件を満たすように
    • 懸念していたベータ分布のサンプリング箇所はプロファイルに現れずじまい
    • それはそれで不安を覚えながらリリースへ……
    $ pprof -cum —text profile | grep math3
    (no output)

    View Slide

  25. リリース

    View Slide

  26. View Slide

  27. リリース前後の監視メトリクス

    View Slide

  28. View Slide

  29. 種明かし
    この発表にあたって調べてみたら、
    ベータ分布のサンプリング性能は提案論⽂に書かれていた orz
    たしかに最悪計算量は無限⼤だけど、
    while ループの期待値はたかだか 4 回でしかない ヽ(*゚▽゚)ノ
    詳しくは Qiita 「ベータ分布のサンプリングアルゴリズムを読みとく」

    View Slide

  30. それはまた別の物語…
    システム的なチューニングの後には、
    ロジック的なチューニングが待っていて、より難しかったり Σ(゚д゚;)
    • 予測精度は?
    • 指標は改善した?
    • 運⽤に問題起きてない?

    View Slide

  31. まとめ
    • 事前に負荷テストしてよかった
    • 推測でタイマー実装しなくてよかった
    • プロファイラで計測してボトルネックを探そう
    • アルゴリズムの計算量が気になるなら⽂献に当たろう

    View Slide

  32. これまで遭遇したボトルネック
    • 配列に⼤量の要素を⼀つずつ追加 ➡ 事前にメモリ確保
    • スパースな巨⼤多重配列の確保 ➡ 連想配列に置きかえ
    • コンストラクタの引数で巨⼤なコピー ➡ 右辺値参照渡し
    • 多重ループで結合 ➡ マージ結合に切りかえ
    • 巨⼤な⾏列演算 ➡ 線形代数ライブラリに投げる
    • 全ワーカーが全マスターデータ取得 ➡ データをID分割
    • 膨⼤な中間ファイル読み込み ➡ 中間出⼒時にパーティションを纏める
    • リソースロックのリトライが衝突しつづける ➡ 乱数で揺らぎを⼊れる

    View Slide

  33. これまで遭遇したボトルネック
    • 配列に⼤量の要素を⼀つずつ追加 ➡ 事前にメモリ確保
    • スパースな巨⼤多重配列の確保 ➡ 連想配列に置きかえ
    • コンストラクタの引数で巨⼤なコピー ➡ 右辺値参照渡し
    • 多重ループで結合 ➡ マージ結合に切りかえ
    • 巨⼤な⾏列演算 ➡ 線形代数ライブラリに投げる
    • 全ワーカーが全マスターデータ取得 ➡ データをID分割
    • 膨⼤な中間ファイル読み込み ➡ 中間出⼒時にパーティションを纏める
    • リソースロックのリトライが衝突しつづける ➡ 乱数で揺らぎを⼊れる




    View Slide

  34. :PVDBOUUFMM
    XIFSFBQSPHSBNJT
    HPJOHUPTQFOEJUTUJNF
    .FBTVSF
    “Notes on Programming in C”

    View Slide