Upgrade to Pro
— share decks privately, control downloads, hide ads and more …
Speaker Deck
Features
Speaker Deck
PRO
Sign in
Sign up for free
Search
Search
ZIOでサクッとFunctionalにETL
Search
wakye5815
September 06, 2024
2.4k
1
Share
Embed
Copy iframe code
Copy JS code
Copy link
Start on current slide
ZIOでサクッとFunctionalにETL
wakye5815
September 06, 2024
More Decks by wakye5815
See All by wakye5815
LintのみでAIに開発スタイルを叩き込めるのか?
wakye5815
3
4.3k
sbt-assemblyにハマってDB接続できず時間が溶けた話
wakye5815
1
1.8k
Featured
See All Featured
The innovator’s Mindset - Leading Through an Era of Exponential Change - McGill University 2025
jdejongh
PRO
1
190
Principles of Awesome APIs and How to Build Them.
keavy
128
17k
GraphQLの誤解/rethinking-graphql
sonatard
75
12k
What’s in a name? Adding method to the madness
productmarketing
PRO
24
4.1k
Paper Plane (Part 1)
katiecoart
PRO
0
8.6k
Building Applications with DynamoDB
mza
96
7.1k
The Curse of the Amulet
leimatthew05
1
13k
Designing for humans not robots
tammielis
254
26k
Evolution of real-time – Irina Nazarova, EuRuKo, 2024
irinanazarova
9
1.4k
Digital Ethics as a Driver of Design Innovation
axbom
PRO
1
300
Skip the Path - Find Your Career Trail
mkilby
1
140
Test your architecture with Archunit
thirion
1
2.3k
Transcript
ZIOでサクッとFunctionalにETL 2024/09/06 Scalaわいわい勉強会 #3 wakye5815 1
自己紹介 Scalaでバックエンド書いたりEM見習いを始めたり色々やってます 名前:脇田悠介 働いてるところ:株式会社Flinters X :@wakye5815 悩み:ZIOの読み方がZIOかZIOなのかわからない 2
とある日 3
実装依頼はいつも突然に 大量の広告配信データを〜 ちょろっと圧縮して〜 ストレージに出力する〜 使い捨ての〜 スクリプトお願いします! 4
欲しいのは ストリーミング操作が優秀でサクッと書ける 軽量に動かせて 並行処理もいい感じ あわよくばFunctionalな そんなScalaライブラリ 5
そんなScalaライブラリあるわけ.... 6
7
と言うことで今日は軽量ETL by ZIOに ついて話します 8
他ライブラリ候補 9
Akka/Pekko Stream メリット 様々なデータソースを接続できるAlpakka優秀 ストリーミング操作のオペレーターもかなり揃っている 腐っているので情報が多い デメリット ActorSystemが付いてくる チームでは脱Akkaしていたので改めて採用するのも... 10
Spark メリット 分散処理できてスケールする Alpakkaと同じくデータソースサポートが手厚い デメリット クラスタ管理はちょっとしたくない 11
そしてZIO ZIOはEffectライブラリ 誤解を恐れずに表現するとZIO文脈のEffectは R => Either[E,A] であり ZIO[R,E,A] として扱われる ScalaらしくFunctionalに設計されているがモナモナしていないので
とっつきやすい、またドキュメントも十分 並行処理はJVMのvirtual threadをベースとしたFiberを提供してい る 12
Streamingはどうなの? ZIOを元にしたZIO Streamsが提供されている Akka/Pekkoに負けない抽象化され且つ豊富なストリーミング操作 ZIO Streamsはプッシュベースストリームではなくプルベースストリ ームのためバッファリング要らず、バックプレッシャーしなくてOK ただしAlpakkaほどの豊富なデータソースサポートはないので要件 とご相談 13
顧客が本当に求めていたもの 14
結果としてZIOを採用 Httpな広告媒体APIからデータをとるだけなのでデータソースサポ ートが豊富でなくてもOK 使い捨てなのでSparkではオーバーキル チーム背景も含めてAkka/Pekkoはごめん 15
実装していく 16
まずは今回のETLについて Extract 広告媒体APIから取得できる配信データをExtract Transform 取得したデータを一定粒度でgzipに圧縮 Load 外部のシステムが読みにくるGCSにLoad 17
ZIO Streamsで表現するには ワークフロー定義に3つの抽象化されたモジュールを利用する ZStream[Env,Err,Out] ストリームワークフローのデータソースとして機能 ZPipeline[Env, Err, In, Out] 実態は
ZStream[Env,Err,In] => ZStream[Env,Err,Out] ZSink[Env,Err,In,Left,Consumed] ZSink[Env,Err,In,Left,Consumed] => ZIO[Env,Err,Consumed] な ZStream#run に渡して消費する 18
組み合わせると def extractionStream(apiEndpoint: URL): ZStream[Any, Throwable, ApiResponse] def transformationPipeline: ZPipeline[Any,
Throwable, APIResponse, (String, Array[Byte])] def loadingSink: ZSink[Any, IOException, (String, Array[Byte]), Nothing, Unit] object Main extends ZIOAppDefault override def run = val effects = apiEndpoints.map { apiEndpoint => extractionStream(apiEndpoint) >>> transformationPipeline >>> loadingSink } ZIO.collectAllPar(effects).unit 19
Extractの実装 java.util.concurrent.FutureあるいはBlockingなAPI実行をZIO Effectに変換する APIはpagination形式 paginateしつつ変換したZIO EffectをさらにStreamへ変換 20
Extractの実装 def extractionStream(apiEndpoint: URL): : ZStream[Any, Throwable, ApiResponse] = ZStream.paginateZIO(apiEndpoint)
{ apiEndpoint => for response: Task[ApiResponse] <- if(isBlocking(apiEndpoint)) then ZIO.attemptBlocking(callBlockingApi(apiEndpoint)) else ZIO.fromFutureJava(callApi(apiEndpoint)) nextEndpoint = Option(response.nextEndpoint) yield (response, nextEndpoint) } 21
Transformの実装 ZStreamで流れてくるApiResponseをArray[Byte]に変換 ArrayButeをgzip圧縮する ついでにuploadする粒度毎にファイル名をつけておく 22
Transformの実装 def transformationPipeline: ZPipeline[Any, Throwable, APIResponse, (String, Array[Byte])] = val
gzipPipeline = ZPipeline.mapZIO { (response: APIResponse) => ZIO.scoped { for byteArrayOs <- ZIO.fromAutoCloseable(ZIO.attempt(new ByteArrayOutputStream())) gzipOs <- ZIO.fromAutoCloseable(ZIO.attempt(new GZIPOutputStream(byteArrayOs))) _ <- ZIO.attemptBlockingIO(gzipOs.write(response.getRawResponse.getBytes)) yield byteArrayOs }.map(_.toByteArray) } val fileNamePipeline = ZPipeline.mapAccum[Array[Byte], Int, (String, Array[Byte])](1)( (index, data) => (index + 1, (s"data_$index.json.gz", data)) ) gzipPipeline >>> fileNamePipeline 23
Loadの実装 あとはGCSに放り込むだけ 24
Loadの実装 def loadingSink: ZSink[Any, IOException, (String, Array[Byte]), Nothing, Unit] =
ZSink.foreach { (pathWithData: (String, Array[Byte])) => val blobId = BlobId.of(BUCKET_NAME, pathWithData._1) val blobInfo = BlobInfo.newBuilder(blobId).build() ZIO.attemptBlockingIO(storage.create(blobInfo, pathWithData._2)) } 25
ZIO Streams万歳 26
他にも Streamに対するretry stream.retry(Schedule.exponential(1.second)) timeoutしたら他のStreamに切り替える firstStream.timeoutTo(10.seconds)(secondStream) 27
まとめ モナモナせずにFunctionalにETLを組めた ZStream(むしろZIO)の豊富なオペレータでデータ処理のロジックが スマートに Akka/Pekko Streamを使っていた人はZStreamの操作自体は似てい るので移行しやすそう 28
We're hiring 絶賛採用中なのでZIOやりたい人、大規模広告データと戯れたい人い らっしゃいましたらお話ししましょう〜 https://www.wantedly.com/companies/flinters 29