Successfully reported this slideshow.
We use your LinkedIn profile and activity data to personalize ads and to show you more relevant ads. You can change your ad preferences anytime.

ストリーム処理エンジン「Zero」の開発と運用

3,097 views

Published on

オレシカナイト第3回での発表資料です。
https://cyberagent.connpass.com/event/64176/

Published in: Software
  • Be the first to comment

ストリーム処理エンジン「Zero」の開発と運用

  1. 1. ストリーム処理エンジン「Zero」 開発と運用 佐藤栄一 株式会社サイバーエージェント 技術本部 秋葉原ラボ オレシカナイト第3回 @ 渋谷プライムプラザ (2017/9/27)
  2. 2. 技術本部 :: 秋葉原ラボ ● @秋葉原 ● 現在30名強が在籍 ○ エンジニア ○ データサイエンティスト ● 共通機能を提供 ○ ログ解析基盤 ○ レコメンド ○ 検索 ○ スパムフィルタ ○ データマイニング ※画像は弊社HPより引用
  3. 3. What is Zero ? SELECT count(*) AS foo, sum(`...`) AS bar FROM flume(`.datetime`) DISTINCT ON `.uuid` FOR 10m WHERE ` ... ` GROUP BY `{ user_id: ... }` PARTITION BY user_id INTERVAL 1d, NONE クエリ(ZeroQL)を登録すると ... … 結果をリアルタイムに取得できます。 GET /v1/report.json?column.user_id=1 HTTP/1.1 200 OK Content-type: application/json { "rows": [ { "user_id": 1, "foo": 100, "bar": 200 } ] } ログの発生から結果に反映されるまで数秒程度。 結果のレスポンス時間は平均数ミリ秒 ~ 数十ミリ秒程度。
  4. 4. What is Zero ? COUNT(*) イベントの件数 SUM(…) 合計
  5. 5. What is Zero ? MAX(…) 最大値 MIN(…) 最小値
  6. 6. What is Zero ? LATEST(…) 最新値 LATEST_N(…,n) 最新のn件 LATEST_SET_N(…,n) 最新のn件(重複を除く)
  7. 7. What is Zero ? WHERE ` .name == "jackson-jq" and .language == "java" and .url == "https://github.com/eiiches/jackson-jq" ` ● JSON形式のイベント(ログ)処理のためにjqに対応 SELECT SUM(`if .action == "click" then 100 else 1 end`) AS score ライブラリ(Java)としてjackson-jqを開発 GROUP BY `{ user_id: .user_id, ad_id: .ads[].id }`
  8. 8. 利用例 ● 広告配信システム @ MDH ○ 実績値のリアルタイムレポート (sum/count) ○ フリクエンシーキャップ & リターゲティング (sum/count) ○ オンライン予測に用いる素性 (sum/count/latest) ● その他、各種サービスで利用中 ○ レコメンド、プッシュ通知等での配信制御 (sum/count/latest/latest_set_n) ○ リアルタイムレポート (sum/count) 本番環境では計70弱のクエリの登録がありました
  9. 9. Architecture Monoid Storage (Apache HBase, Amazon DynamoDB, ...) Cache (Redis)Dedup (Redis) SELECT COUNT(*) AS total, SUM(`if .type == "click" then 1 else 0 end`) AS click, LATEST_N(`.url`, 3) AS urls FROM flume(`.datetime`) DISTINCT ON `.uuid` FOR 10m WHERE `headers.service = "foo"` GROUP BY `{ user_id: .user_id }` ApacheZookeeper ServiceDiscovery Configuration Event Processor Query Server Event (JSON) Apache Flume, HTTP, ... Result (JSON) Event ProcessorとQuery Serverの中身は同一 SpringBoot (Java) で実装
  10. 10. 前提 ● イベントは重複して届くことがある (at least once) ● イベントが届く順序は保証されない ● 一定時間内に到着した同一のイベントを除外する ● Redisに処理したイベントIDを記録しておく(厳密ではない) ● どのような順序で到着しても問題ないように処理する ● 可換モノイドとしてデータ構造と処理を抽象化
  11. 11. モノイド ● 条件を満たす「値」と「二項演算」の組み合わせ ○ 結合則: a + (b + c) = (a + b) + c ○ 単位元: a + e = e + a = a 「交換則: a + b = b + a」を追加すると 「可換」モノイドに。 (int, +) (int, ×) (int, max) (int, min) (int, ÷)
  12. 12. モノイド (Pair<Time, T>, newer) (List<Pair<Time, T>>, newer_n) (List<Pair<Time, T>>, newer_set_n) プリミティブ型に限らず、定義できる
  13. 13. Event Processor 1. イベントを受信 SUM(`.quantity`) { "action": "buy", "quantity": 10, "user_id": "sato" } 2. モノイドに変換 4. 書き込みキューにいれる Apache HBase 5. ストレージに書く Increment Put CheckAndPut Amazon DynamoDB UpdateItem Redis Redis Redis 6. キャッシュを無効化 Redis Redis Redis 3. 重複チェック +10 +5
  14. 14. 書き込みキュー ● 条件を満たすまでtake()出来ない ○ 最大サイズ(設定値)の90%以上のキュー長になった場合 ○ または、put()されてから、最小滞在時間(設定値)経過した場合 ● put()時に、キーが重複する場合にモノイドが(+)される A imp: 1 put() take() 最大サイズ(設定値) ☠ B click: 3, imp: 9 A click: 1, imp: 2 B click: 3, imp: 9 初期の実装: 「1秒貯めてから書き込み」or「貯めずに直接書き込み」 → 貯めて書いての繰り返しなので負荷が安定しない
  15. 15. Query Server 1. リクエストを受信 3. ストレージから読み込み Get Scan Query 2. キャッシュに問い合わせ (ネガティブキャッシュあり) Apache HBase Amazon DynamoDB Redis Redis Redis { "user_id": "sato" } Rate Limiter Request Coalescing 503 Service Unavailable Circuit Breaker
  16. 16. Rate Limiting ● クエリ毎に「同時リクエスト数」が一定を超えた場合に503を返却 ○ 障害時の影響範囲を限定。 ○ クライアント側は“必要に応じて”リトライ ■ 指数バックオフ + ジッタ推奨 ● クエリ毎に「秒間リクエスト数」が一定を超えた場合に503を返却 ○ 障害時に一時的に設定する、などを想定。平常時は設定していない。 ☠ 例えば、HBaseのサーバーダウンなど一部の障害が、本来関係のな いサービスに影響することをを防ぎます。 ☠ 後からだと503を返して問題ないか確認が必要なので、最初からいれておいたほうが良いです。 ・・・まだ対応が終わっていない箇所が・・・
  17. 17. Request Coalescing ● ストレージへの同一のREADリクエストは束ねる Storage READ 10 READ 10 READ 10{id: 10} {id: 10} wait Request Coalescing ほぼ同一のリクエスト ☠ 例えば、リクエスト元にキャッシュがあり、そのキャッシュがexpireした場 合に、実装次第で同一のリクエストが届く。 また、レスポンス時間が伸びるほど問い合わせが来る(悪循環) ※ thundering herd もしくは cache stampede として知られています。
  18. 18. キャッシュ ● Redisは速いが、1台では限界がある => Consistent Hashingで分散 ○ ノードの追加・削除時、キャッシュの再配置をなるべく避ける。 Node 1 Node 2 Node 3 0 Key B Key A Key C 事前にNodeのハッシュ値を計算しておく。 1. Keyのハッシュ値を計算。 2. (反)時計回りにNodeを探す。 ハッシュ値域 [0, 256) ☠ ※ 実際には、割り当ての偏りを少なくするために、仮想Nodeを使います。 最初は1台でしたが、負荷が上がっ て急いで対応しました><
  19. 19. キャッシュ (オートスケーリング) ● キャッシュサーバーもオートスケーリングしたい! ○ => キャッシュノード自身が Zookeeperに自分を登録 ○ ZBeacon ■ Zookeeperにエフェメラルノードを書くだけの小さなデーモン( Goで150行程度) ■ キャッシュノードで常駐させておく ○ Redisは1コアしか使用できないので、複数プロセス立ち上げる zbeacon -hosts zk01.example.com,zk02.example.com,zk03.example.com -path /zero/cache-servers/`hostname -f`:6379 -data `hostname -f`:6379 ☠ 以前は小さなインスタンスをたくさん並べていたので、 IPアド レスを無駄に消費してしまい・・・
  20. 20. キャッシュ (エラー処理) ● エラーの場合はキャッシュミス同様、ストレージから読み込み ○ タイムアウトは100msで設定。 ○ キャッシュ読み込みは継続し、 read()自体は1000msでタイムアウト。 ● キャッシュノード毎にCircuitBreaker処理 ○ キャッシュへの同時リクエスト数が一定以上になった場合は最初から弾く。 ☠ 当初は setSoTimeout() していたが、ライブラリがエラーになったコネクションを再利用しない ため、後続のリクエストで瞬間的に再接続が頻発して不安定に・・・
  21. 21. 運用 ● 現在3クラスタ: プライベートクラウド / AWS (HBase用、DynamoDB用) ● AWS環境 ○ オートスケーリングを活用 ○ デプロイは独自ツール (Git-versioned Red-black Deployment) ■ ELB以外のリソース(ASG, LC, etc.)は毎回新規作成 ○ アプリに特化したAMIは作っていない(共通AMIにdocker追加) ■ インスタンス起動時に ECRのイメージをdocker run ■ --security-opt seccomp=unconfined --network=host --iptables=false ● ほぼCPUオーバーヘッドなし (conntrack, seccomp, etc.) ● プライベートクラウド環境 ○ Query Server : docker-compose, kubernetes ○ Event Processor : docker-compose ○ Redis Cache / Dedup : ansible ※ zk, redisは共用
  22. 22. 監視 ● InfluxDB + Grafana alerting ○ Dropwizard Metricsでアプリケーションメトリクスを JMXで公開 ○ Javaエージェントでほぼ全ての JMXメトリクスをまとめて InfluxDBに10秒おきに送信 ■ https://github.com/eiiches/java-influxdb-metrics-agent ● Zabbix ○ 自動登録機能 + シャットダウン時にホスト無効化、でオートスケーリングに対応 ○ 基本的なインスタンスの監視 ■ ディスク容量, etc. ○ Redis (cache, dedup) の監視 ○ APIの死活監視 これはGrafana移行予定
  23. 23. Apache HBase Hadoop上の分散NoSQL Column Family 1 Column Family 2 ... A B A B C ... aaaa 7 5 23 bbbb 3 11 29 ... zzzz 17 13 1 19 ... 昇順 新 旧 Region Region Server 1 Region Server 2
  24. 24. HBase Storage ● スキーマ設計 ○ 後から変えるのはしんどい。 ○ 理由がない限り、行キーの先頭にはハッシュ値 (1,2byte)を付ける。 ■ 負荷が偏ると運用が厳しい。事前にハッシュ値境界で分割 (split)しておく。 ● CheckAndPut操作 ○ リトライするときは指数バックオフだけでなくジッタもいれる。 ○ 同じサーバーからは同じ行への変更が同時にされないようにロックを掛ける。 ● ASYNC_WAL (WAL = Write-Ahead Log) ○ 障害時にごく短時間の書き込みロストが許容できる場合は有効に。
  25. 25. Tuning Apache HBase ● -XX:+UseG1GC ○ CMS等に比べてYouAreDeadExceptionで死ぬ頻度を減らせる。 ● RegionServer Group based Assignment ○ 本来はHBase 2(未リリース)の機能。一部のディストリビューションで使える。 ○ 「このテーブルはこのサーバーに配置したい」を指定できる。 ■ 障害時の影響範囲を限定するのに有効。 ● CallQueueの分割 (write/read/scan) ○ 「書き込みの負荷が読み込みに影響する」を防止できる。 ● hbase.assignment.zkevent.workers = 1 ○ HMasterがデッドロックするバグの回避。 ● hbase.ipc.server.reservoir.enabled = false ○ バッファの確保サイズは大きくなる一方なのである時死ぬバグの回避。
  26. 26. Patching Apache HBase ● [HBASE-14460] スレッドの競合でread-modify-writeが遅くなる ○ HBaseチームでパッチ作成 (HBASE-14460のパッチとは別) ○ Fix: 忘れました ● [HBASE-17072] ThreadLocal内のハッシュテーブルで負荷高騰 ○ ハッシュテーブルの primary clustering ○ 不要なキャッシュロジックをごっそり削除(乱暴) ○ Fix: 2.0.0, 1.4.0, 0.98.24, cdh5.10.0 ● [HBASE-18042,...] Scannerをサーバー側で早期クローズ ○ 何故かScannerが大量に残ってFullGCで死ぬことがあり、そもそも早期クローズで回避。 ○ branch-1.3のコミットをバックポート ○ Fix: 1.3.2
  27. 27. まとめ ● ストリーム処理エンジン「Zero」の紹介 ○ == 手軽にリアルタイムにデータを活用できる汎用的な仕組み ○ jqを組み合わせたSQLライクなクエリ言語 ○ モノイドによる処理の抽象化 ● 安定性向上のための工夫の紹介 ○ 他のシステムでもある程度一般的に適用できるはず (?) ● デプロイ・監視などの運用の紹介 ● 今後の開発予定 ○ Apache Phoenix / Redis / Apache Kafka / Amazon Kinesis ○ 集約関数追加 / Join機能(?)

×