システムソリューション部の佐藤奏です。
並列分散処理フレームワークApache Sparkがホットな昨今。サンプルコードや活用事例もいろいろと公開されていますが、では実際にSparkを利用してシステムを構築しようとするとき、どのような考慮が必要なのでしょうか。
今回は「SparkとAWS EMRを使ったシステム構築」を念頭に、開発の初期段階――技術選定や開発スケジュール検討、外部設計、プロトタイプ作成・評価――において有用な情報や開発の進め方のポイントをいろいろとご紹介してみようと思います。結構、地味な話が多いですがお付き合いください。
本記事執筆時点でのSparkの最新バージョン1.6.1をベースとした記述です。
Apache Spark 超概要
Spark自体の紹介は既に多くの記事がありますので、ここでは簡単にとどめます。
特徴
- Scala製の、大規模データ並列分散処理フレームワーク
- 処理はオンメモリで実施
- Scala, Java, Python, R で利用可能
- 機械学習ライブラリMLlibが使える
- お洒落なロゴ(私見)
処理がオンメモリで行われるところがHadoopとの大きな違いで、機械学習とも相性がいいと言われます。先日バージョン2.0.0のプレビューリリースが行われるなど活発に開発が行われています。
もう少し詳しい紹介資料としては
が分かりやすく、かつ新しい内容です。NTTデータ社の他のスライドにもSparkがテーマのものが多数あります。
導入/利用方法
クラウドベンダー提供のプラットフォーム
Amazon Web Service (AWS) Elastic MapReduce (EMR) にSparkが標準搭載され、自前でインストール作業をすることなく利用できます1。
そのほか、筆者は試していませんが Microsoft Azure や Google Cloud Platform でも利用できます。
スタンドアロン環境への導入
つまり、分散処理させずに手元のPCで動かすということです。 Windows では、以下の記事が大変参考になりました。
- Apache Spark入門 – Windowsでサンプルを動かす – サラリーマンIT技術者の調査レポート
- 加えて、環境変数 PATH の末尾に ;C:\dev\hadoop-winutils-2.6.0\bin の追加が必要かもしれません。
Mac ではHomebrewを利用し brew install apache-spark で導入できます。
チュートリアル
具体的なコード例は本記事の趣旨から離れるため掲載しませんが、比較的新しく非常に分かりやすいスライドがありますのでご紹介します。ずいぶん簡潔な書き方で使えることが分かると思います。
よく使うのは下記のコマンドですね。
- spark-shell: SparkのREPL(対話型評価環境)。Spark風味のScala REPLが起動する。
- spark-submit: jarファイルを渡してSparkの処理をキックする。
Tips & Traps
ここからが本題です。筆者が実際にSparkでアプリのプロトタイプ作成を行った際に「考えどころ」になったポイントを列挙します。
技術選定(Scala, Java, Python, R, どれを使うべきか)
システム構築という観点からいうと、現時点では Scalaが順当 です。他の言語は以下のデメリットがあります。
- Java:コードが冗長になる。
- そもそも言語自体の特徴としてScalaよりコードが長くなりがちだが、Sparkでは RDD#map() など引数に関数をとる関数を頻繁に使うので、より顕著。特にJava 8が使えない(=ラムダ式が使えない)とつらい。
- というかJava 8でも結構大変だったよ、とは同僚談。「Tupleをいちいちnewしたり、パターンマッチングが無かったり、RDDをJavaRDDに変換したり」いろいろ苦労がある、とのことです。
- Python:パフォーマンスの点で難あり。
- JVMとの間でのデータ構造の変換がオーバーヘッドとなって、Sparkのポテンシャルを生かし切れない。参考:大規模並列処理:PythonとSparkの甘酸っぱい関係~PyData.Tokyo Meetup #3イベントレポート (1/2):CodeZine(コードジン)
- ほかに、Scalaと比べてSparkのAPIのバージョンアップが遅れるというデメリットもあり。
- R:どちらかというとインタラクティブな分析作業に向いた言語であり、システム構築にはなじまない。
一方でScalaは、ウェブ上の情報量はJava等に比べると少ないですし、技術者の数も限られる、というデメリットがあります。もっともSparkに関していえば、処理対象範囲が限定的なことが多く、Scalaの深い知識は必ずしも必要ないと思います。むしろSpark自体のチューニング手法を研究することの方が重要です。
上記はあくまでシステム構築の観点から見た場合の話です。普段データ分析業務に従事し、PythonやRに慣れている方が、アドホックな分析で大規模データ処理の効率化のためにPython, RでSparkを使うということは全然アリですし、有効に利用できると思います。
また、RDDよりも新しいAPIである DataFrame API では、開発言語の違いによる著しい性能劣化は起こらないそうです!(前掲スライドp.51参照) 現段階ではまだ情報が少ないですが、PySparkでのシステム構築が容易になることが見込まれ、要注目です。
外部設計
Sparkで 実施しない 処理を明確にしましょう。
Sparkを使ったコードの中に、分散処理させる必要のないような処理を混ぜ込むことも出来てしまいますし、アドホックに実行する分にはそれでも構わないのですが、システム化の際にはそのようなコードは切り出して区別した方が全体として合理的な構成になり、開発も運用保守もしやすいです。
また、そもそもメモリに乗り切らないようなデータセットを扱う場合、Sparkでは対応できないので、Hadoop MapReduceなど別の方法が必要になります。
いずれにしても、実際のプロジェクトではSparkだけで処理が完結しないことの方が多いと思います。Sparkはあくまでシステムの構成要素の一部と位置付け、他の処理との間のインタフェースを明確に定義しておくと開発作業がスムーズに進むでしょう。
デバッグ
実行方法
本番環境では spark-submit にfat jar2を渡す形で起動することになります。
開発においては spark-submit ではなくIDE上でのデバッグ、テストコードでのテスト実施も可能です。その際、 spark-submit へオプションで与える各種の値(マスターのURLなど)は、代わりに SparkConf#setMaster() 等で与えてやる必要があります。
また、EMRでテスト実行を行う前に PCローカル環境で spark-submit で実行し、うまくいくことを確認しておくべきです 。後述のようにEMRですんなりいかない場合も多いので、問題の切り分けに役立ちます。
注意点
RDDの操作は「変換」「アクション」の2つに大別されますが、遅延評価の仕組みにより「アクション」で初めて実処理が実行されることから、 デバッグがやりにくい場合がある ことを知っておきましょう。
Sparkのメソッドは基本的にlazy evaluation(遅延評価)なので、デバッグがやりにくく、メモリ消費量を予測するのも難しいです。また、並列処理の中でprintデバッグとかをしようとしてもコンソールには出力されず、それぞれのスレーブ上の指定のstdoutファイルに出力されてしまいます。
【後編】Apache Sparkを使って、メモリ使用量が大きいバッチ処理をスケールアウト | ADN LAB’s Blog
また、PCローカルで動かすのとクラスターで動かすのとでは、当然ながら環境の差異が大きいです。ローカルではできるのに、EMRに乗せると動かない、なぜだろう……といったことがよくあり、調査にそれなりの時間がかかります。しかもクラスターの起動・終了が伴ったりすると時間的オーバーヘッドがどうしても発生します3。 動作環境によらないロジック自体のテストはなるべくローカルで実施 すること、EMRの習熟含め クラスター上でテスト実行するための作業期間を十分に確保 することが重要です。
便利な関数ふたつ
全く恣意的なチョイスですが、あまり目立たないけれど便利だなーと思った関数を紹介します。
各ノードに渡す定数は SparkContext#broadcast() で包む(性能向上)
読み取り専用の定数を使う場合、 SparkContext#broadcast() でラップしましょう。ノードへの値の配布が効率化されます。例えばあるRDDと別のテーブル(メモリに載る範囲でそこそこ大きいサイズ)とを突合するロジックがあるとして、そのテーブルを非RDD化して List や Map で保持する……といった場合に効果的です。
実際、定数をbroadcastしただけでパフォーマンスが2倍ほどになった経験があります。社内の別のプロジェクトでも、テーブルの「非RDD化&broadcast」で10倍以上高速化しました。SparkというとRDDにばかり気を取られがちですが、定数で、かつ適切なデータ構造(例えばキーでランダムアクセスをするなら Map )で持たせることによっても高速化が可能だったりします。
RDD#sample() でランダム抽出(テスト時に便利)
本番データを使って性能評価をするような場合、全量を相手にすると時間がかかりすぎるなら RDD#sample() を使いましょう。指定の確率でランダムにレコードを抽出して処理させることができます。例えば全体の約1%を抽出するなら sample(false, 0.01) などとします。
第2引数 fraction はあくまで確率なので、例えば全量が100件のRDDに sample(false, 0.01) したとして毎回ちょうど1件だけ拾われるとは限らず、多少のブレがあります。
関連書籍
- Holden Karau et al.(Sky株式会社 玉川竜司・訳) 『初めてのSpark』(オライリー・ジャパン)
- 基礎的な内容が網羅されていておすすめです。
- Sandy Ryza et al.(石川有・監訳、Sky株式会社 玉川竜司・訳)『Sparkによる実践データ解析――大規模データのための機械学習事例集』(オライリー・ジャパン)
- Scalaで書かれたデータ分析事例集というのはけっこう貴重なのではないかと思います。
おわりに
Spark、面白いです。簡潔な記述で並列分散処理が実現できるのが良いですね。ついでにScalaの経験がほとんどなかった筆者としては、Scalaの良い練習にもなりました。
紹介した各種資料・サイトの作成者の皆様に感謝申し上げます。
また、ALBERTでは、データサイエンティストを積極募集しています。
ぜひ採用ページをご覧ください。
- 参考:Amazon Web Services ブログ: Apache Spark on Amazon EMR ↩
- sbtのプラグイン sbt-assembly を入れて sbt assembly で作成。 ↩
- デバッグ時はEMRは起動しっぱなしにしておき、随時ステップ追加で処理させた方が作業効率がいいと思います。 ↩
The post Apache Spark を使ったシステム構築のための Tips first appeared on ALBERT Official Blog.