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

推測するな、計測せよ。

2016ba6b977a2e6691811fa66d5f4336?s=47 CyberAgent
PRO
February 22, 2019

 推測するな、計測せよ。

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

2016ba6b977a2e6691811fa66d5f4336?s=128

CyberAgent
PRO

February 22, 2019
Tweet

More Decks by CyberAgent

Other Decks in Technology

Transcript

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

  2. ⿃越 貴智 • アドテクスタジオ AMoAd 中途 3 年⽬ データ‧機械学習エンジニア •

    量⼦ゼミ ScalaMatsuri で登壇しました ( ・ㅂ・)و 早稲⽥の⽥中宗先⽣とアニーリングの話をしてます>< piyo piyo z
  3. プロダクト紹介

  4. • CyberAgent と DeNA の合弁会社 2011 年 4 ⽉に設⽴ 開発はアドテクスタジオ

    アドネットワーク専業 • 多彩な広告フォーマット AMoAd ネットワーク AMoAd インフィード AfiO(動画) D AD
  5. 8 年⽬って技術的負債ヤバくない?

  6. \技術的アンチエイジング∕ • 広告配信システムのマイクロサービス化 おおむね Kubernetes へ移⾏ 詳しくは和⽥さんの「レガシーシステムのコンテナ化に挑戦した話」 • データ基盤のマネージド化 Dataproc

    (Spark) BigQuery • ロジックの機械学習化 タイムラグのあるインストール率推定 広告乱択アルゴリズムの刷新
  7. 広告乱択アルゴリズム

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

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

    ベータ分布から乱数をサンプリングする
  10. 確率分布 • ⼀様分布 :0 以上 1 以下の値が均等に出現 • ベータ分布:0 以上 1

    以下の値が偏って出現(偏り⽅のパラメータがある)
  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); ベータ分布のサンプリング実装
  12. ベータ分布のサンプリング実装 . ⼀様分布から 2 回サンプリング . ややこしそうな計算 . よくわからない条件を満たすまでループ ※

    Apache Commons Math の BetaDistribution.java からコードを抜粋
  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); ベータ分布のサンプリング実装
  14. もしかして停⽌する保証ない?

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

  16. ஆ ͔ ͘ ݟ क Δ ࣄ ۀ ੹ ೚

    ऀ ू ܭ Λ վ म ͠ ͯ ͘ Ε ͨ ಉ ྅ ʮ͋ɺ͜Εແཧ͔΋ʯͱ਒͑Δࢲ
  17. 負荷テスト

  18. 性能要件 • ⼀般的にアドテクの許容 Latency はトータル 100ms 以内が⽬安 • 当然その⼀部であるマイクロサービスの許容 Latency

    はもっと短い • 現状ピーク時間帯 1500 qps の 95% を Latency ms 以内でさばいている • Latency ms 以内を要件に
  19. 負荷テスト . テスト環境に広告選択マイクロサービス (Finagle) を⽴てて本番 DB に繋ぐ . 本番ログからテスト⽤シナリオを⽣成する .

    Gatling で負荷をかけて response time を計測する . Finagle のプロファイリング⽤エンドポイントを叩く
  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
  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; }
  22. 原因と対策 • リクエストごとに異なるベータ分布から乱数をサンプリングしたい • リクエストごとにベータ分布乱数⽣成器をコンストラクト • その際に呼ばれる⼀様乱数⽣成器のコンストラクタがボトルネック • ベータ分布乱数⽣成器のコンストラクタで⼀様乱数⽣成器を注⼊可能 •

    その⼀様乱数⽣成器は使いまわしてよいがスレッドセーフでない • スレッドごとに⼀様乱数⽣成器を⼀度だけコンストラクトして使いまわす
  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() }
  24. 計測結果(チューニング後) • 再度 Finagle のプロファイルを⾒ると、ボトルネック消失 • キャッシュアクセスなどもチューニングして性能要件を満たすように • 懸念していたベータ分布のサンプリング箇所はプロファイルに現れずじまい •

    それはそれで不安を覚えながらリリースへ…… $ pprof -cum —text profile | grep math3 (no output)
  25. リリース

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

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

    詳しくは Qiita 「ベータ分布のサンプリングアルゴリズムを読みとく」
  30. それはまた別の物語… システム的なチューニングの後には、 ロジック的なチューニングが待っていて、より難しかったり Σ(゚д゚;) • 予測精度は? • 指標は改善した? • 運⽤に問題起きてない?

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

  32. これまで遭遇したボトルネック • 配列に⼤量の要素を⼀つずつ追加 ➡ 事前にメモリ確保 • スパースな巨⼤多重配列の確保 ➡ 連想配列に置きかえ • コンストラクタの引数で巨⼤なコピー ➡ 右辺値参照渡し • 多重ループで結合 ➡ マージ結合に切りかえ •

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

    巨⼤な⾏列演算 ➡ 線形代数ライブラリに投げる • 全ワーカーが全マスターデータ取得 ➡ データをID分割 • 膨⼤な中間ファイル読み込み ➡ 中間出⼒時にパーティションを纏める • リソースロックのリトライが衝突しつづける ➡ 乱数で揺らぎを⼊れる ਪ ଌ ෆ ೳ
  34. :PVDBOUUFMM XIFSFBQSPHSBNJT HPJOHUPTQFOEJUTUJNF .FBTVSF “Notes on Programming in C”