ScalaでSparkのプログラムを作る作業を高速化する.

SparkのScalaプロジェクトのビルドを爆速にする

Scalaを気に入っています.Javaはとにかくコードが冗長になり,気持ち悪かったです.Cはシンプルだし大好きです.なのでObjective-CC++はそれなりに好きだったのですが,Cのコードがそのまま書けてしまうのは流石に今どきだとどうかと思ったり,型についてアバウト過ぎるのでデバグが大変だったりしました.Rubyは大好きでラピッドプロトタイピングしたりしてアルゴリズムを検証するには最高なのですが,ライブラリ化なんかを考えると使いにくかったり.その辺り,Scalaなら便利だし楽しいし好きです.

ただしScalaは難しくて学習コストが高い.が,これは頑張ればいい話なのでScalaのせいじゃないです.しかし,Scalaで作っているプログラムがデカくなってくると,ビルドに時間がかかる.これは努力じゃどうにもならなくて,生産性や効率を考えるととても問題です.しかし解決策はたくさんあります.

ScalaでSparkのプログラムを書くことを考えます.Scalaは各種のライブラリを色々使って便利にコーディングできるので,大量のライブラリをリンクすることになります.そして,Sparkを使う時は,sbt-assemblyなどでuber-jar/fat-jarを作るのが普通で,全てのライブラリをリンクするので時間がかかるしjarのサイズがデカくなる.

私のdeeplearning4jを使ったプログラムは,jarのサイズはこんな感じです.

-rw-r--r-- 1 tullio docker 799M Feb  6 15:37 DeepZip-assembly-0.1.0-SNAPSHOT.jar

799Mバイト.デカイよ.1Gバイトにもなろうとするファイルを作らないとならないとは.sbt-assemblyでビルドしてuber-jarを作ると,これくらい時間がかかります.

sbt:DeepZipTest> assembly
[info] Strategy 'deduplicate' was applied to 3 files (Run the task at debug level to see details)
[info] Strategy 'discard' was applied to 11 files (Run the task at debug level to see details)
[info] Strategy 'filterDistinctLines' was applied to 14 files (Run the task at debug level to see details)
[info] Strategy 'first' was applied to 2672 files (Run the task at debug level to see details)
[info] Strategy 'last' was applied to 14 files (Run the task at debug level to see details)
[success] Total time: 169 s (02:49), completed Feb 6, 2021 3:37:58 PM

cleanしてassemblyすると,こんな感じ.

sbt:DeepZipTest> clean
[success] Total time: 11 s, completed Feb 6, 2021 3:59:37 PM
sbt:DeepZipTest> assembly
[info] Updating
[info] Strategy 'deduplicate' was applied to 3 files (Run the task at debug level to see details)
[info] Strategy 'discard' was applied to 11 files (Run the task at debug level to see details)
[info] Strategy 'filterDistinctLines' was applied to 14 files (Run the task at debug level to see details)
[info] Strategy 'first' was applied to 2672 files (Run the task at debug level to see details)
[info] Strategy 'last' was applied to 14 files (Run the task at debug level to see details)
[info] Strategy 'rename' was applied to 102 files (Run the task at debug level to see details)
[success] Total time: 263 s (04:23), completed Feb 6, 2021 4:04:04 PM

3分から5分かかるんですが,長いです.タイプミスして一文字間違えただけでビルドし直し3分.イラつくだけじゃなく,本当に効率が落ちます.作業が遅くなる.

これを高速化する方法はたくさんあるのですが,単純に全てのリンクを止めるというのが簡単で効果的です.
そのマジックは,sbt-assemblyに仕込まれています.

GitHub - sbt/sbt-assembly: Deploy fat JARs. Restart processes. (port of codahale/assembly-sbt)

Splitting your project and deps JARs

To make a JAR file containing only the external dependencies, type

> assemblyPackageDependency

This is intended to be used with a JAR that only contains your project

assemblyOption in assembly := (assemblyOption in assembly).value.copy(includeScala = false, includeDependency = false)

自分のプロジェクトと依存ライブラリのjarを分離する

外部の依存ライブラリだけのJARファイルを作るには,このようにタイプして下さい:

> assemblyPackageDependency

自分のプロジェクトのファイルだけのJARファイルを作るには,このように出来ます:

assemblyOption in assembly := (assemblyOption in assembly).value.copy(includeScala = false, includeDependency = false)

使い方は簡単で,自分が作っているプロジェクト以外の依存ライブラリのjarを作っておく.

sbt:DeepZipTest> assemblyPackageDependency
[info] Strategy 'deduplicate' was applied to 3 files (Run the task at debug level to see details)
[info] Strategy 'discard' was applied to 11 files (Run the task at debug level to see details)
[info] Strategy 'filterDistinctLines' was applied to 14 files (Run the task at debug level to see details)
[info] Strategy 'first' was applied to 2672 files (Run the task at debug level to see details)
[info] Strategy 'last' was applied to 14 files (Run the task at debug level to see details)
[success] Total time: 178 s (02:58), completed Feb 6, 2021 4:14:04 PM

ls -lh
-rw-r--r-- 1 tullio docker 799M Feb  6 16:14 DeepZip-assembly-0.1.0-SNAPSHOT-deps.jar

依存ライブラリ一式のjarを作るのに3分かかってますが,これは一度作っておけばOKなので,問題ありません.jarのサイズは799Mバイトで,作りたいfat-jarの大部分を占めていることが分かります.

さて,依存ライブラリjarを作っておくと,自分で作っているコードのビルドは,ほとんど一瞬で終わります.
ビルドコマンドは,assemblyじゃなくてpackageでOK.

sbt:DeepZipTest> package
[success] Total time: 0 s, completed Feb 6, 2021 4:18:25 PM

ls -lh
-rw-r--r-- 1 tullio docker  92K Feb  6 16:18 deepziptest_2.12-0.1.0-SNAPSHOT.jar

ビルド時間は0秒(笑),jarのファイルサイズは92Kバイト

ビルド時間が5分だったのが0秒になります.これは効率化,向上でしょう.

Sparkで実行するには,完全なubar-jarだったら

spark-submit --class DeepZip DeepZip-assembly-0.1.0-SNAPSHOT.jar

となるところを,

spark-submit --class DeepZip --jars DeepZip-assembly-0.1.0-SNAPSHOT-deps.jar deepziptest_2.12-0.1.0-SNAPSHOT.jar

と--jarsで依存ライブラリのjarをアップロードするだけです.

ビルドが一瞬になって,Scalaの快適ライフをどうぞ!
全てのScalaプログラマーは,eed3si9n | works by eugene yokotaさんに感謝しつつsbt-assemblyを使うのが良いと思います.