Spark MLlibでリコメンドエンジンを作った話

569 views

Published on

2016/12/21の「市ヶ谷Geek★Night#11【Spark勉強会】ChristmaSpark」の登壇資料です。

Published in: Engineering
0 Comments
2 Likes
Statistics
Notes
  • Be the first to comment

No Downloads
Views
Total views
569
On SlideShare
0
From Embeds
0
Number of Embeds
211
Actions
Shares
0
Downloads
6
Comments
0
Likes
2
Embeds 0
No embeds

No notes for slide

Spark MLlibでリコメンドエンジンを作った話

  1. 1. Spark MLlibで リコメンドエンジンを 作った話 2016/12/21 株式会社オプト 柴田 幸輝
  2. 2. Agenda ・自己紹介 ・SparkをEMRで動かす話 ・MLlibでリコメンドエンジン作った(チューニングした)話 ・作ってみての振り返り 話さないこと:  ・Sparkのアーキテクチャ(みなさんご存知ですよね?)  ・MLlib以外のリコメンドロジックや各種パラメータ
  3. 3. 前置き 注意事項:  ・記載している内容は一部古いかもしれません  ・プロダクトでの内部ロジックを含む箇所は適当にぼかす&省略しています  ・Spark自体の話でない周辺のシステムの話も含んでいます 単語について:  ・Spark → ○Apache Spark ☓Spark Framework  ・YARN→ ○Yet Another Resource Negotiator ☓npmのアレ
  4. 4. 自己紹介 名前:柴田 幸輝(@uryyyyyyy) 会社:株式会社オプト 役割:遊撃手(Spark / スクラムマスター / React / 組織改善 etc...) 趣味:副業でReact Native書いてる。来年はKubernetesやりたい。 その他:「それって何が嬉しいんですか?」おじさん
  5. 5. 「それって何が嬉しいんですか?」とは 「D言語をプロダクトに突っ込もうぜ」などと言われた時に、 「それって何が嬉しいんですか?」と聞くだけの簡単なお仕事。   社内のD言語マンの記事 http://qiita.com/cedretaber/items/90b7b34ee710eb5cc965 (投入したくないとは言っていません。本当に価値があるのかを考えるだけ。)
  6. 6. Opt Technologiesについて オプトのエンジニア組織の総称。カッコイイ括りにしたかった ・Geek Night定期的にやっています。  https://ichigayageek.connpass.com/ ・技術マガジンも書いています。  http://tech-magazine.opt.ne.jp/ ・エンジニア採用中です。  (僕からの紹介ということにして頂ければ、お互いに得しますヨ!)
  7. 7. Sparkを EMRで動かす話
  8. 8. AWS EMR 概要:  HadoopクラスタをSaaSとして提供してくれるサービス  ほぼ最新のHadoop / Spark / Ganglia / Hiveなどが使えて便利   http://docs.aws.amazon.com//ElasticMapReduce/latest/ReleaseGuide/emr-release-components.html  インスタンスタイプも調整可。パラメータ調整済み        http://docs.aws.amazon.com/ElasticMapReduce/latest/ReleaseGuide/emr-hadoop-daemons.html  AWSに乗っかれる(CLIツール・権限制御 etc...) →とりあえず試す環境としては最適だと思っています。  (Google Cloud Dataprocは試してない)
  9. 9. EMR上でSparkを動かす ・Spark2.0をEMRで試す http://qiita.com/uryyyyyyy/items/15f2e8f153aa86375227 ↑に全部書いてあります。
  10. 10. EMRの特徴(S3連携) データストレージとしてS3が使える →HDFSを使わなくて良くなるので、Hadoopクラスタをいつでも落とせる。 →普通のHadoopクラスタでもaws-hadoopパッケージがあれば動くが、  EMR上のHadoopにはデフォルトで入ってる ※LocalでSparkを動かす際には、spark-submit / shellの引数に  `--packages org.apache.hadoop:hadoop-aws:2.7.2` などを付けます。 ※EMR 4系当時、HadoopからS3へ出力する際には   `DirectOutputCommitter` 相当のハックを用意する必要がありました。    http://qiita.com/uryyyyyyy/items/e9ec40a8c748d82d4bc4
  11. 11. EMRの特徴(自動でログ集約) EMRでは、自動で定期的にログを集約してS3に送ってくれます。 どのようにログが格納されるかはこちら、 http://docs.aws.amazon.com/ElasticMapReduce/latest/ManagementGuide/emr-manage-view-web-log-files.html エラー時にどう追うかはこちらをご参照ください。 ・EMRのログをS3/Localから追う http://qiita.com/uryyyyyyy/items/8bf386da45bcd8fde387 ※ログがS3に置かれるまでにはタイムラグ(5分毎)があるため、  S3にまだ上がってきてないときは、sshして中身を確認する必要があります。
  12. 12. EMRの特徴(Spot Instance) EMRは内部的にEC2で構成されますが、Spot Instanceの調達が容易です。 →ただしMaster Nodeに関してはオンデマンドを使うことをオススメします  (落ちたらクラスタ自体が死んでしまうので)  (大量にアプリ起動したりしなければm1.mediumで十分です)
  13. 13. EMRのツラミ(Black Box) Spark 1系はassemblyがscala 2.10でビルドされている(2系では2.11)  →Scala2.11で作ったアプリが動かないので、自前でビルドして上書きした Hadoop系がLog4Jに依存していて、SLF4Jでログ出力出来なかった。 →色々依存をいじりましたが、EMRの中が覗けないので諦めてLog4Jに。。 (書いてて思いましたが、これEMR特有の話じゃないですね。。。)
  14. 14. Spark MLlibで リコメンドシステムを 作った話
  15. 15. 求められる要件の概要 概要  ・協調フィルタリングを用いてリコメンドエンジンを作る  ・データ量(500k Product, 10M User)規模  ・応答速度 100ms以下  ・秒間●リクエストを想定  ・Feed毎に異なる推薦ロジックの適用  ・少なくとも1回/日でモデルを作り直して鮮度を保つ プロダクト:unis → http://lp.unis.tokyo/ Criteoとだいたい同じことをしてます。 http://www.slideshare.net/RomainLerallut/recsys-2015-largescale-realtime-product-recommendation-at-criteo
  16. 16. 協調フィルタリングの概要 疎な行列を再現できるように要素分解し、空欄に入る値を推測する。  青い四角 -> 縦 10M * 横 500k のデータを  赤い四角 -> 縦 10M * 横 100(rank)  緑の四角 -> 縦 100(rank) * 横 500k にする(モデル化) from http://www.slideshare.net/jeykottalam/mllib
  17. 17. 初期に考えた構成 配信 サーバ 生ログ収集 広告アクション 推薦 サーバ 格納 前処理 モデル生成 マスタデータ 学習結果のキャッシュ 内部LB (~100ms) リコメンド ロジック
  18. 18. 初期に考えた構成 1. S3に格納されたデータをRedShiftで前処理 2. Sparkでモデル生成して、全ユーザーの推薦結果をCacheに格納 3. 推薦サーバは、Cacheのデータに独自ロジックを掛けて返却 進め方:  ・参照実装で性能要件・予算感を満たせるかを調査する  ・最初はランダム生成したデータで作っていき、   上手く行ったら本番のデータを使っても実現できるかを確認する
  19. 19. 作っていく中で起きた問題 問題1:データサイズが増えるとOoMEで落ちる 問題2:RedShiftからの読み込みで失敗する 問題3:全てを事前計算はムリ 問題4:推薦サーバが立てられない 問題5:推薦が100ms以上かかる 問題6:大きいデータでSpark Jobが急に死ぬ 問題7:処理中に突然のエラー 問題8:stagingに乗せるとSparkが重くなった 順番に見ていきます→
  20. 20. 問題1:データサイズが増えるとOoMEで落ちる 事象:  一定規模以上のデータで試すと、OoMEが出た。  (超巨大クラスタで数時間かけたら通ったが、明らかに予算超過)
  21. 21. 問題1:データサイズが増えるとOoMEで落ちる 原因:  recommendProductsForUsersがクッソ遅い https://github.com/apache/spark/blob/branch-1.6/mllib/src/main/scala/org/apache/spark/mllib/recommendation/MatrixFactoriz ationModel.scala#L283  何をしてるかというと、Product * Userの要素数のArrayを毎回作っている  →500k * 10Mサイズのデータを何回も作っていてGCが発生しまくっていた。 対策:  recommendProductsを何度も何度も呼ぶ形に変更
  22. 22. 問題2:RedShiftからの読み込みで失敗する 事象:  データ量が大きいと発生。どうやら10分程度経つと落ちている 原因:  redshift側のクエリの結果が待ちきれなかった場合に起きる様子。  spark-redshiftパッケージのバグっぽい 対策:  前処理の中でS3への格納までやってしまい、SparkからはS3だけ見る  (そもそも毎回RedShift見る形は、処理時間が増えて動作確認も面倒だった)
  23. 23. 配信 サーバ 生ログ収集 広告アクション 推薦 サーバ 格納 前処理 モデル生成 マスタデータ 学習結果のキャッシュ 内部LB (~100ms) リコメンド ロジック
  24. 24. 小ネタ:恥ずかしいTypo 推薦サーバの死活監視用のエンドポイントを作ったのですが、 稼働させた後に、URLが/heathCheckになっていたのが発覚しました。 稼働後だったので直すのに時間がかかったため、 チーム内ではしばらく「へぁすちぇっくしてる?」とバカにされました orz… お気をつけ下さい...
  25. 25. 問題3:全てを事前計算はムリ 事象:  10Mユーザー分全部を事前計算しようとするとむちゃくちゃ重い。  (このうち大半のユーザーはもうアクセスしてこない)
  26. 26. 問題3:全てを事前計算はムリ 原因:  事前計算が済んだとしても、  10M(user) * 100(product) * 1kB(record) = 1000GBくらいの容量が 必要になる。計算時間もバカにならず、データの鮮度が失われる。 対応:  とても全部計算しきれないので、  推薦サーバ側にモデルを読ませてリアルタイム推薦を試みる
  27. 27. 配信 サーバ 生ログ収集 広告アクション 推薦 サーバ 格納 前処理 モデル生成 マスタデータ 学習結果&モデル のキャッシュ 内部LB (~100ms) リコメンド ロジック
  28. 28. 問題4:推薦サーバが立てられない 事象:  Sparkでモデルを生成したものの、それが分散して保存される。  分散されたデータを読み込むために  Finagle上でSparkをlocal Modeで動かして返すという無茶を試みる...  (手法はトリッキーすぎるので割愛します。使わないでください!)  案の定めちゃくちゃ遅い  運用もめっちゃ辛そう。。
  29. 29. 問題4:推薦サーバが立てられない 原因:  RDDでリアルタイム処理させてるから遅い← 対応:  RDDをただの行列になおして自前で計算してみると、  10倍くらい速くなる。しかしまだ足りない。  そもそもただの行列に直してしまうなら最初からそうすればいいので、  Sparkでのモデル生成の結果をただの配列に直してから保存することにする。  serializeの際に、kryoのみだとネストしたクラスをうまく扱えなかったので、  chill-bijectionを使用。
  30. 30. 小ネタ:異論・激論・Bijection タイトルに特に意味は無いです... 逃げ恥ファンなら伝わるはず 使い方はこんな感じです。 また、モデルの他にBiMapのserializeにも使いました。 MLlibのAPIはproductとuserのIDにIntを要求するのですが、 元データは両方ともStringで持っていたのでBiMapを使って変換しました。
  31. 31. 問題5:推薦が100ms以上かかる 事象:  小さいデータなら余裕だが、大きいデータで呼ばれるとまだ遅い。  手元PCでpython(numpy)で同様の処理をしたところ、  800k商品 * 100rankでも内積計算は110msくらい で動く  推薦サーバのスペックならもっと速くできるはず 原因:  Scala上で処理していて効率が悪かった ※計測は、内訳を見たいのとuserIdを列挙して叩きたかったため、abでなくgatlingを利用しました ※100msは大規模モデルでの都度計算の場合の最大値です。普通は10msくらいです。
  32. 32. 問題5:推薦が100ms以上かかる 対応:  netlib-javaを使って行列計算を行うことで高速化。  boxingコストが気になったので全部Javaで書いてチューニング!  同時リクエスト時にGCで詰まったので、さらにチューニング!!  Scalaのみで書いた場合と比べ10倍以上高速化。QPSも大幅向上
  33. 33. 問題6:大きいデータでSpark Jobが急に死ぬ 事象:  なぜか処理中にSparkアプリが死ぬ。  ログを見てもDriver NodeにError系のログが出てない。。。  →DriverNodeがYARNにkillされてる?  Ganglia上でみるとDriverNodeのメモリが毎回●GBのところで死んでいる。  →MLlibで変にキャッシュされてる?(そんなことなかった)  https://github.com/apache/spark/blob/branch-2.0/mllib/src/main/scala/org/apache/spark/ml/recommendation/ALS.scala#L690
  34. 34. 問題6:大きいデータでSpark Jobが急に死ぬ 原因:  上述の通り、Driver Nodeにデータを集約しているせいだった。 対応:  ・Driver Nodeのメモリ割り当てを増やす。   ・--conf spark.driver.maxResultSize=●g を設定し、   Driver Nodeへのデータ集約を許容する。
  35. 35. 小ネタ:リコメンドエンジンの名前 プロダクト名がunisなので、それっぽい名前を考えました unitem(ゆにてむ) uniquery(ゆにーくりー) unicorn(ゆにこーん) etc... (unis + unique + queryから来ています。) 外向けに使おうと思ったら既に会社名であったorz → http://uniquery.jp/ (外には出さずチーム内でひっそり使ってます)
  36. 36. 問題7:処理中に突然のエラー 事象: Kryo serialization failed: Buffer overflow. Available: 0, required: 10. To avoid this, increase spark.kryoserializer.buffer.max value. 原因:  kryoのシリアライズでデータサイズ超過で落ちている 対応:  --conf spark.kryoserializer.buffer.max=1296m で解決
  37. 37. 問題8:stagingに乗せるとSparkが重くなった 事象:  dev環境では速かったのに  staging環境に載せて前処理合わせて実行すると遅くなった。    違いは入力のみのはず,,,と思い、  一度ファイル出力して読み込み直すと何故か速くなった  さらに調査すると、CPU振り切ってるNodeと途中で遊んでるNodeがあった。
  38. 38. 問題8:stagingに乗せるとSparkが重くなった 原因:  staging環境ではS3にunloadされたファイルが2つしか置かれてなく、  適切にpartitionが分けられていなかった。 対応:  データ読み込み後にHashPartitionを掛けることで解消  (180は適当に決めました。。)
  39. 39. 振り返り
  40. 40. 振り返り ・推測するな、計測しろ  →JVM系ツールや可視化ツールを使って特定するの大事  特にGangliaとSpark WebUIは重宝しました。 ・リソース試算  とりあえず問題なく動くレベルで予算を抑えておきましょう。  それで予算超過なら、設計や仕様を見直します。  (インスタンスサイズの調整程度で変わるコストは数割くらいです)  落ち着いてからオンプレ検討するなど
  41. 41. 振り返り ・小さく試す  →ボトルネックの特定がしやすいように、  また繰り返し試しやすいように、最小構成を持っておくと良いです。  (そういうときにEMRおすすめです) ・早めに本番データで試す  →机上で上手く行っても、本番データでは結構バグります。  これはデータサイズだけでなく、データのばらつき具合も影響します。 ・有識者に聞く(居なければ外部から招聘する)  →めっちゃ重要。行き詰まったり、明後日の方向に進むのを防げます。
  42. 42. Appendix
  43. 43. 分散処理基盤としてSparkを活用した際のTips Spark on EMR(YARN)の個人的Tips http://qiita.com/uryyyyyyy/items/f8bb1c4a4137e896de7f リソースチューニング例 http://www.slideshare.net/hadooparchbook/top-5-mistakes-when-writing-spark-applications EMRでのリソース割り当て例 http://qiita.com/uryyyyyyy/items/5cc7fa8957ad5953f111

×