Modern stream processing
by Spark Structured Streaming
2017/08/04
ストリームデータ処理技術勉強会
Kimura, Sotaro(@kimutansk)
https://www.flickr.com/photos/pargee/2753581063/
自己紹介
• Kimura, Sotaro(@kimutansk)
– データエンジニア雑用係 @ ドワンゴ
• オンプレ~クラウド、インフラ~個別機能
• バッチ~ストリーム、開発~プロマネ
– すなわち雑用係
– 好きな技術分野
• ストリーム処理(主にJVM上)
• 分散システム
– ストリーム処理で過去よくやらかした失敗
• 前段の処理性能>後段の処理性能となってOOME
• Verアップ時内部状態の互換性壊してロード失敗\(^o^)/
アジェンダ
• ストリーム処理システム構成の変遷
– バッチと並列でデータ処理を実行
– 単体でデータ処理を実行
– 統合されたデータ処理して抽象化し、実行
• 最近のストリーム処理の要求要素
– ストリーム処理固有の事情への対応
– バッチとの統合されたAPI提供
• Spark Structured Streamingの紹介
ストリーム処理システム構成の変遷
システム構成はどう変わってきた?
• OSSのストリーム処理システムは登場後、
以下のように構成が変遷している。
– 2011年~ Lambda Architecture
• バッチと並列で実行すればリアルタイムにも対応可能!
– 2013年~ Kappa Architecture
• データ処理の精度を高めてストリーム処理で完結!
– 2015年~ Unified Data Processing
• バッチ・ストリーム共通のAPIでデータ処理を実施!
バッチ処理と
ストリーム処理の
結果一致させるの
辛いよ・・・
実はバッチ処理は
ストリーム処理の
部分集合じゃね?
Lambda Architecture
• Twitter(当時)のNathan氏が挙げたアーキテクチャ
– How to beat the CAP theorem(※)
• バッチレイヤとリアルタイムレイヤを
並行して実行し、結果をマージして表示する構成
(※)http://nathanmarz.com/blog/how-to-beat-the-cap-theorem.html
Lambda Architecture
• Twitter(当時)のNathan氏が挙げたアーキテクチャ
– How to beat the CAP theorem(※)
• バッチレイヤとリアルタイムレイヤを
並行して実行し、結果をマージして表示する構成
(※)http://nathanmarz.com/blog/how-to-beat-the-cap-theorem.html
バッチレイヤとリアルタイムレイヤで
別々のアプリケーション開発をする必要がある
。
結果を一致させるのが非常に大変・ ・ ・
Kappa Architecture
• Linkedin(当時)のJay氏が挙げたアーキテクチャ
– Questioning the Lambda Architecture (※)
• ストリーム処理システムでバッチと同様の
精度を保証する対処をして構成をシンプル化
(※)https://www.oreilly.com/ideas/questioning-the-lambda-architecture
Unified Data Processing
• GoogleのTyler氏や、data Artisansの開発者が
Unified Stream and Batch Processing、
ストリーム処理とバッチ処理の統合を提唱
– The world beyond batch: Streaming 101 (※1)
– Stream and Batch Processing in a Single Engine(※2)
• 並行して実行されるデータ処理パイプライン
としてはこの2つは同じだという考え
– そのため、ストリーム処理でどちらも対応可能という
スタンスを取っている。
(※1)https://www.oreilly.com/ideas/the-world-beyond-batch-streaming-101
(※2)https://www.semanticscholar.org/paper/Apache-Flink-Stream-and-Batch-Processing-in-a-Sing-Carbone-Katsifodimos/2234129b45e9e2ea592e5f92c09623167ee44394
バッチとストリームの統合とは?
• バッチ処理の前提
– 実行するタイミングでデータは完全である必要がある。
• つまり、対象のデータは「有限」となる。
– バッチをまたぐ出力は困難
• 典型的なバッチ処理モデル
MapReduce
バッチとストリームの統合とは?
• 複数の結果を出力する場合は複数回バッチを実行
• 結果を区切る場合はすべて含むデータを入力
MapReduce
2/26
2/27
2/28
MapReduce
MapReduce
MapReduce
2/28
[00:00 ~ 06:00)
[06:00 ~ 12:00)
[12:00 ~ 18:00)
[18:00 ~ 24:00)
バッチとストリームの統合とは?
• ユーザのセッションを出力したい場合、日を跨ぐ。
– セッション=一定時間内の連続アクセス(ログが存在)
– もしユーザが2/27と2/28を跨いでアクセスした場合、
2/27の結果の再出力が必要となるが、それは困難。
– もし、それが続いた場合・・・どうなる?
MapReduce
2/282/27 2/282/27
Red
Yellow
Green
バッチとストリームの統合とは?
• 複数回バッチ実行は以下の図のように変形できる。
– これはすなわち・・?
2/26 2/27 2/28
2/282/272/26
Map
Reduce
Map
Reduce
Map
Reduce
バッチとストリームの統合とは?
• これは、すなわち無限のデータであるストリームデ
ータを一定時間ごとに区切ったものに他ならない。
時間で区切る
2/26 2/27 2/28
区切りのある有限のストリーム
区切りのない無限のストリーム
バッチとストリームの統合とは?
• つまり、バッチ処理とはストリーム処理の中の
限定的な処理のモデルであるということ。
時間で区切る
2/26 2/27 2/28
区切りのある有限のストリーム
区切りのない無限のストリーム
ストリーム
処理
バッチ
処理
ストリーム処理で実現可能なこと
• ストリーム処理におけるバッチの前提の扱い
• バッチの前提:入力データが完全
– 常に生成され続けるストリームデータのため満たされない
• バッチの前提:バッチを跨いだ出力が困難
– 常時処理で処理単位が小さいため、バッチより対応が容易
• データが完全な状況においては、
ストリーム処理はバッチ処理と同等の機能を実現可
– 「バッチ処理はストリーム処理の部分集合」のため
– 管理容易性や処理の区切りやすさ等の要素は当然異なる
最近のストリーム処理の要求項目
何が求められるのか?
• ストリーム処理固有の事情への対応
– データが発生した順に到着しない!
• どこまで処理が完了したと判断するか?
• いつ集計結果を出力するか?
• 集計結果出力時の、値はどう集計するか?
• バッチとの統合されたAPI提供
– SQLを用いてバッチ用のTable、Streamを活用
– 同体系のAPIで両方式のアプリケーションを記述
ストリーム処理固有の事情
• データは発生した順に到着しない!
– このことを“Out of order”と呼ぶ
– 例) 携帯電話を機内モードに切り替えて搭乗
• 時刻概念として、以下が代表的
– EventTime
• 実際にそのイベントが発生した時刻
– ProcessingTime
• 実際にそのイベントを処理した時刻
• (到着した時刻が記録される場合もある)
それで何が困るのか?
• もしデータ間に関連が無いのであれば、
“Out of order”であってもそれほど困らない。
– 結果が反映されるまで時間がかかるという問題はある。
• しかしながら、ストリーム処理の適用分野には、
データ間の関連を見る必要があるケースが多い。
– 例)異常・変化イベント検知の場合
1イベントで異常や変化を検知できるケースは少ない。
「短時間に特定アカウントに大量のログイン試行」など、
前後のイベントの関係が必要になる。
それで何が困るのか?
• データのグルーピングの概念としてWindowがある
Tumbling
Window
Time
Sliding
Window
Sesson
Window
それで何が困るのか?
• Windowを使用する際、
“Out of order”の性質で問題が発生する。
– 結果出力後に05:55のデータが到着したらどう扱う?
[00:00 ~ 06:00)
1. 出力2. 到着...?
これらの問題に対する対処
• このストリーム処理で発生する問題に対して、
下記の3つの対処の概念が挙げられている。
• Watermark
– どこまで処理が完了したと判断するかの指標
• Trigger
– 集計結果の出力タイミングを制御するための機構
• Accumulation(他Output Mode等とも)
– 集計結果を出力時の値をどう集計するか?
Watermark
• EventTimeベースでどこまで処理したかを示す概念
– 各時刻間に歪みが発生するため、それを示す。
Event Time
ProcessingTime
理想のシステム
実システム
(≒Watermark)
歪み
Trigger
• いつ集計結果を出力するかを定義する機構
– Triggerの存在によって、集計結果の出力タイミングを
柔軟に、複数回定義可能になる。
– 加えて、Watermarkより遅れたデータに対しても対応可能
• 出力タイミング例
– 一定時間毎
– データが到着する度に出力
– 上記2つの複合
• 一定間隔毎に、該当Windowにデータが到着したら出力
Accumulation(Output Mode)
• 集計結果出力時の集計方式
– 実行している集計次第で適用可能なモードは限られる。
• Discarding / Append Mode
– 前回の出力以降に入力されたデータの合計値を出力
• Accumulating / Complete Mode
– これまでのデータ入力の累算値を出力
• Update Mode
– 累算値のうち、前回の出力以降に入力されたデータに
よって更新されたレコードのみを出力
バッチとの統合されたAPI提供
• 標準SQL+Stream拡張でバッチ用Tableと
ストリーム用Streamを活用(Flink)
(※)https://flink.apache.org/news/2016/05/24/stream-sql.html
バッチとの統合されたAPI提供
• APIは統合されつつあるが、両方同時には扱えない
バッチと
ストリームの実行環境が
分離されており、
どちらか片方しか
使用できない。
バッチとの統合されたAPI提供
• SparkではDataFrame/DatasetのAPI体系で
バッチ、ストリーム両アプリケーションを記述可能
– Table登録することでSQLによる操作も可能
詳細はこの次の紹介部分で・ ・ ・
Spark Structured Streamingの紹介
Spark Structured Streamingとは?
• Spark SQL上でストリーム処理アプリケーションを
簡単に組むためのコンポーネント
– Spark2.2系でProduction Ready!
– バッチ処理と同様の方法でストリーム処理を記述可能
• バッチ処理で読み込んだデータとストリームのJoinも可能!
– Scala/Java/PythonのDataset/DataFrame APIで記述
– Dataset/DataFrameを用いることで
構造化データとして最適化された状態で動作
• メモリ使用量の節約
• ベクトル演算によるCPUリソースの有効活用
Spark Structured Streamingとは?
• ストリーム処理固有の事情を以下の通りサポート
– Watermark
• 特定のカラムの値をEventTimeと扱う機能をサポート
• 「入力レコード中で最新EventTime」をWatermarkとして使用
– Trigger
• 定期的に出力する方式のみサポート
• マイクロバッチ方式のため、
定期的に「バッチを全部実行して集計」しかできない
– Accumulation(Output Mode)
• Append、Complete、Updateの方式をサポート
– ただし、何かしらの集約関数を通さないとAppendのみ
Spark Structured Streamingとは?
• 注意点
– Spark Streamingと同様マイクロバッチ方式であり、
レコード単位で処理するストリーム処理ではない。
– Sparkの新実行エンジンDrizzleとは独立した別の機能
• https://github.com/amplab/drizzle-spark
– Spark2.3.0で継続的に実行する方式についての提案も
挙がっているが、現状の進み具合から
おそらくSpark2.3.0では無理?
• [SPARK-20928]
Continuous Processing Mode for Structured Streaming
何故Structured Streaming?
• マイクロバッチではあるものの、
何故Spark Structured Streamingを使用するのか?
– 学習コストの低さ
• チーム内でデータ整形・集計アプリケーションが
Spark・SparkSQLで記述されており、学習コストが低い
– Kafkaとの親和性の高さ
• Spark2.1系(この時点でStructured Streamingはα版)時点で
Kafka0.10系の公式コネクタが提供され、連携コストが低い
– システムの性質上、マイクロバッチの遅延は許容可能
• 規模の調整で数百ミリ秒くらいまでは短縮可能
簡単なアプリケーション例
// Sparkアプリケーション生成
val spark = SparkSession
.builder
.appName("StructuredNetworkWordCount")
.getOrCreate()
import spark.implicits._
// ローカルポート上にソケットを生成してデータを待ち受け
val lines = spark.readStream
.format("socket")
.option("host", "localhost")
.option("port", 9999)
.load()
簡単なアプリケーション例
// 入力データを単語毎に分割
val words = lines.as[String].flatMap(_.split(" "))
// 入力単語毎にカウント
val wordCounts = words.groupBy("value").count()
// 集計結果を毎回すべてコンソールに出力
val query = wordCounts.writeStream
.outputMode("complete")
.format("console")
.start()
// アプリケーションが外部から停止されるまで実行
query.awaitTermination()
簡単なアプリケーションイメージ
https://spark.apache.org/docs/latest/structured-streaming-programming-guide.html
実際のアプリケーション例
• 実際に組んでみて、簡単にできたものは?
– ストリーミングETL
• KafkaのTopic名称をデータのスキーマに対応させておけば、
Topicのパターン指定で1アプリケーションで一括処理可能
– 例:以下のTopicをリアルタイムで変換してHDFSに投入
– example_distributor_action
– example_audience_action
– example_general_action
• Topic毎に出力ディレクトリを分けたい
• 日、時間単位で出力ディレクトリを分けたい
• 1分おきに実行したい
実際のアプリケーション例
// Sparkアプリケーション生成
val sparkSession = SparkSession
.builder
.appName("StreamingETLExample")
.getOrCreate()
// example_で始まるKafka Topicを読み込む
val kafkaDs = sparkSession.readStream.format("kafka").
option(kafka.bootstrap.servers", "host1:port1,host2:port2,host3:port3").
option("subscribePattern", "example_.*").load()
// データの変換関数・時刻の抽出関数をUDFとして設定
val exampleConvertUdf = udf(funcExampleConvert)
val extractTimestampUdf = udf(funcExtractTimestamp)
実際のアプリケーション例
// データを変換し、グループ分けに必要 or 出力カラムに絞る
import sparkSession.implicits._
val convertedDs =
kafkaDs.selectExpr("topic", "CAST(value AS STRING) as value").
withColumn("converted_value", exampleConvertUdf('value)).
withColumn("data_timestamp_str", extractTimestampUdf('value)).
withColumn("data_timestamp",
unix_timestamp($"data_timestamp_str", "yyyy-MM-dd'T'HH:mm:ssXXX")).
withColumn("date", from_unixtime($"data_timestamp", "yyyyMMdd")).
withColumn("hour", from_unixtime($"data_timestamp", "HH")).
selectExpr("topic", "date", "hour", "converted_value")
実際のアプリケーション例
// データ変換結果をHDFSに出力
// 出力ディレクトリパス例は以下
// /data/converted/topic=example_general_action/date=20170706/hour=23
// ※実際は出力間隔次第でファイル数が膨れ上がるので注意
convertedDs.toDF().writeStream.
trigger(ProcessingTime("60 seconds")).
partitionBy("topic", "date", "hour").
outputMode("append").
option("compression", "snappy").
option("checkpointLocation", "/data/checkpoint").
format("parquet").
start("/data/converted").awaitTermination()
まとめ
• ストリーム処理システムは
最近は以下の要素が簡易に実現できることが重要
– ストリーム処理固有の事情への対応
– バッチ処理と統合されたAPIによる開発
• SQLによるバッチ用Table、ストリーム用Streamの解析
• 同体系のAPIで両方のアプリケーションが開発可能
– これらを満たすフレームワークの候補として
Spark Structured Streamingがある。
Thank you for your attention!
https://www.flickr.com/photos/savannahcorps/7409364642

Modern stream processing by Spark Structured Streaming