learning spaerk chapter03

47
Learning Spark Chapter03 Programming with RDD

Upload: akimitsu-takagi

Post on 10-Aug-2015

389 views

Category:

Data & Analytics


0 download

TRANSCRIPT

Learning SparkChapter03

Programming with RDD

参考資料

• http://www.slideshare.net/hadoopxnttdata/apache-spark

• http://www.ne.jp/asahi/hishidama/home/tech/scala/spark/RDD.html

• http://d.hatena.ne.jp/kimutansk/20130924/1379971579

• http://www.slideshare.net/data_sciesotist/2-learning-spark-3

• http://dev.classmethod.jp/etc/apache-spark_rdd_investigation/

• http://qiita.com/FKuro_/items/3b34f9f64a17c73ccdd4

• 今回の資料は、Learning Spark自体や書籍に関する勉強会資料、更にはSparkに関して公開している資料を参考にしながら、社内の勉強会向けに作成したものです

• もし、この資料に関して、内容の訂正・削除の必要がある場合は、こちらのメールアドレスまで連絡をお願いします– [email protected]– 資料に関するアドバイス、誤訳の指摘など、大歓迎です

• 今回のこちらの資料は会社の活動とは無関係ですので、会社名は 伏せますが、資料を見たうえで勉強会に興味をもたれた方は、同様にこちらのアドレスに連絡ください– 特に、弊社は機械学習に力を入れておりますので、MLlibに関し

て興味がある方は、大歓迎ですので、宜しくお願い致します

• 当資料では、RDDとRDDsに関する違いは無いと判断し、文書中では元の書籍の表現がどうあれ、RDDで統一しています– 使い分けが必要である場合は、もう一度見直しますので、

ご指摘をお願いします

• 本文書のサンプルは、基本Scalaに関してのみ記載しています。元の書籍にPythonしか無いものも、一部はScala版のサンプルも用意しています

• 基本的に元の書籍と1対1で訳文を用意していますが、一部、意味の無い文書は省略しています– 重要な文書を省略していると思われる場合は、ご連絡い

ただければ、対応いたします

本編

This Chapter Introduces

• Sparkにおけるデータ(RDD)の取り扱いを説明する

• Sparkにおける全ての操作は、RDDの生成、RDDの変換、RDDの計算で表される

• データサイエンティストもエンジニアも、この章を読んでほしい

• 出来れば、手元で動作確認をしながら読んで欲しい

• コードはGitHubで公開している

RDD Basics(1)

• RDD(Resilient Distributed Datasets)はイミュータブル(不変)な分散コレクションである

• RDDは複数のパーティションに分割され、それぞれ異なるノードで処理される

• RDDは、ユーザオブジェクトを含む、Python、Java、Scalaのオブジェクトを含むことが出来る

• 外部のデータセットを読み込むか、ドライバを用いてコレクションオブジェクトを配布することで生成出来る

• 遅延計算される

– 計算を行うタイミングをスケジューリングされたあと、計算される

val lines = sc.textFile(“README.md”)

例3-1:

RDD Basics(2) 補足①

• Sparkアプリケーションを起動する側(メインプログラム)をdriverdriverdriverdriverと呼ぶ

– ユーザがRDDの返還を記述したプログラム

• 各マシン(ワーカーノード)上で分散して実際に稼動する側(プロセス)をexecutorexecutorexecutorexecutorと呼ぶ

– ワーカ上で動作し、実際の計算処理を担当する

• 共有変数

– Sparkでは、各executorに定数を転送したり、各executorで集計した値をdriverで受け取ったりする機能がある

• Sparkの実行はexecutorで行われるので、Sparkで変数に対して行われた変更はdriver側には反映されない

• 当然、executor間で参照し合うことも出来ない

• そのため、driverとexecutorとの間で値を共有するための仕組みがSparkには用意されている

– ブロードキャスト変数は、driverで定義した定数(固定値)を各executorに転送する為の変数

– Scalaで記述する場合は、定数を共有することが出来るが、非効率

val rdd = sc.makeRDD(Seq(123, 456, 789)) val CONSTANTCONSTANTCONSTANTCONSTANT = sc.broadcastbroadcastbroadcastbroadcast(123) val filter = rdd.filter(_ != CONSTANT.valueCONSTANT.valueCONSTANT.valueCONSTANT.value) filter.foreach(println)

RDD Basics(3) 補足②

• RDDがイミュータブルである必要がある理由

– Sparkでは、大量おデータをオンメモリで分散処理するので、各サーバに分散配置されたデータが処理中に欠損した場合に備えた仕組みが必要

– 欠損に備えつつ、ネットワーク転送を出来る限り避けたい

• 得たいデータが失われていたら、前のデータから再作成する、という アプローチをとっている

– このため、RDDはイミュータブルである必要がある

RDD Basics(4) 補足③

RDD Basics(5)

• 作成したRDDには、二つの操作が適用できる

– Transformations:RDDから別の新しいRDDを生成出来る

– Actions:RDDに対して処理を適用した結果を出力する

• Transformationsの例として、条件に一致する要素を抽出して、新しいRDDを作成するフィルタリングがある

• 例3-2に示すように、単語のPythonが含まれている文字列だけを保持する新しいRDDを作成するためにこれを使用することができます

val lineWithSpark = lines.filter( line => line.contains("Spark") )

例3-2:

RDD Basics(6)

• Actionsは、RDDに基づいて計算し、結果をドライバプログラムに戻すか、外部記憶装置(例えば、HDFS)に保存するか、いずれかを行う

• Actionsの例として、RDDから最初の1要素を抽出するfirst()がある

lineWithSpark.first()

例3-3:

• TransformationsとActionsは、RDDを処理する際の動作が異なる

– RDDをいつでも定義することができるが、最初に使用される時まで処理は実行されない(遅延実行)

– このアプローチは一般的では無い様に見えるが、ビッグデータで作業しているとき、多くの意味を持つかもしれない

RDD Basics(7)

– フィルタを行う際にファイルを読み込む際、ファイル内のすべての行をロードすることは、スペースの多くを無駄に消費することになる

– スパークはTransformationsのチェーン全体で必要なデータだけ処理することができる

– first()アクションにおいて、Sparkは最初の1行が見つかるまでファイルをスキャンするが、ファイル全体を読み取ることは無い

RDD Basics(8)

• RDDはActionsの実行の度に処理される

• RDDを複数のActionsで再利用したい場合は、RDD.persist()で永続化する必要がある

• Sparkは、最初に計算した後にメモリ内のデータを保存し、将来Actionsで再利用することが出来る

• デフォルトでは永続化しない仕様が珍しいかもしれないが、大きなデータセットの場合に意味ある仕様となる。RDDを再利用せず、処理に必要なデータはストリーム経由で取得できる場合は、ストレージスペースを無駄にする理由は無い

• データの一部をメモリ上に永続化し、繰り返しクエリを発行するときに使うことが多いだろう

lineWithSpark.persist()lineWithSpark.count()lineWithSpark.first()

例3-4:

RDD Basics(9)

• まとめ

– 外部データから入力用RDDを作る

– filter()のようなtransformationsを使い入力用RDDを変換することで、新しいRDDを作成する

– 再利用するRDDをpersist()で永続化出来る

– ActionsはRDDを分散処理する

• TIPS– cache()もデフォルト設定でpersist()を実行したのと同じ結果を得られる

Creating RDD

• RDDの作り方には2種類存在する– 外部データセットを読み込む方法

– コレクションを並列化(Parallelizing)させる方法

• 外部データセットの読み込みに関する説明は既に実施済み

• 簡単なのは、sc.parallelize()を使用する– 既に存在するコレクションをSparkContextの並列化メソッドに渡すことである

– シェルの中で独自のRDDを作成し、実行することが出来るので、学習の際には便利

– 1マシン上のメモリに全体のデータセットを持っていることを必要とすることは認識しておく必要があります(よって、プロトタイプやテスト以外では、広く使用されない)

– RDDを作成する、より一般的な方法は、外部記憶装置からデータをロードすることである

val lines = sc.parallelize(List("pandas", "i like pandas"))

例3-6:

val lines = sc.textFile("/path/to/README.md")

例3-9:

RDD Operations(1)

• RDDは2種類の操作をサポートしている

– TransformationsとActionsがある

• Transformationsは新しいRDDを返す

– map() や filter() など

• Actionsはドライバーに計算結果を返すか、ストレージに結果を書き込む操作である

– count() や first()など

• TransformationsとActionsは非常に違うので、使う操作の種類を理解することは非常に重要

– 種類に関して混乱してしまった場合は、戻り値の型を見ればよい

RDD Operations(2) Transformations①

• Transformationsは新しいRDDを生成する

• RDDに対してActionsが実行されるまで、transformationsは実行されない(遅延評価)

• 多くのtransformationsは“要素指向”で、各要素に対して作用する

– 例えば、MATLABの配列同士の乗算は、要素同士の乗算になる(C = A.*B)

• ログファイルlog.txtからエラーメッセージを抽出する例を見る– 以前に見たfilter()を使用する

• filter()が既存のinputRDDを変更しないことに注意してください。

• 代わりに新しいRDDが生成されます

• inputRDDを後でプログラム内で再利用することができる(例えば、他の単語を検索するなど)

• 再度、"warning"を検索する為に、inputRDDを使用してみるとしましょう

val inputRDD = sc.textFile("log.txt")val errorsRDD = inputRDD.filter(line => line.contains("error"))

例3-12:

RDD Operations(3) Transformations②

• そこで、"error"もしくは"warning"を含む行を検索する為に、transformation:union()を使う

• union()は2つのRDDを処理するところがfilter()と違う

• transformationは、任意の数のRDDをInputに処理することが出来る

• TIP

– 例3-14と同じ結果を得るための良い方法は、単純に"error"もしくは"warning"を一度検索し、同様に残りの一方でフィルタすることである

– 系統グラフについて

• Sparkは、異なるRDD間の依存関係のセットを追跡する(系統グラフ)

• 必要に応じて各RDDを計算する際に永続したRDDの一部が失われた場合に、回復するためにこの情報を使用する

• 図3-1は、例3-14のための系統グラフを示している

• DAG(Directed Acyclic Graph:有向非循環グラフ)ともいう

例3-14:

val errorsRDD = inputRDD.filter(line => line.contains("error"))val warningsRDD = inputRDD.filter(line => line.contains("warning"))val badLinesRDD = errorsRDD.union(warningsRDD)

図3-1

RDD Operations(4) Actions①

• Actionsは、データセットに対して何かを実行し、ドライバに値を戻すか、ストレージに何かを出力する操作である

• Actionsが呼ばれた際に、RDDのために必要なTransactionsの評価を強制する

• 前のセクションから続いているログ処理を続けて、badLinesRDDに関する情報を出力する方法を見ていく

– count():データの件数を返す

– take():

• 指定した件数の要素を返す

• ローカルで反復した後、ドライバに情報を出力する

例3-16:

println("Input had " + badLinesRDD.count() + " concerning lines")println("Here are 10 examples:")badLinesRDD.take(10).foreach(println)

RDD Operations(5) Actions②

• Collect()について

– RDD全体を取得するために、 collect()というactionsも用意されている

– プログラムがRDDを非常に小さいサイズにフィルタし、ローカルでフィルタしたRDDを取り扱いたい場合に非常に便利です

– collect()を使用する場合、全体のデータセットが単一のマシン上のメモリに収まらなければならない点に注意してください。collect()を大規模なデータセットで使用すべきではありません

– ほとんどの場合RDDは大きすぎる為、collect()で収集しきれない

– これらのケースでは、HDFSやAmazon S3などの分散ストレージシステムにデータを書き込むことが一般的

• saveAsTextFile()やsaveAsSequenceFile() を用いてRDDの内容を保存することが出来る

• 第5章にて、データをエクスポートするためのさまざまなオプションを説明する

• 新しいactionを呼び出す度にRDD全体が最初から再計算されなければならないことに注意する必要がある

• この非効率性を回避するために、persist()もしくはcache()を使って、中間結果を永続化することができる

RDD Operations(6) Lazy Evaluation(遅延評価)①

• 既に述べた通り、transformationsは遅延評価される。それはSparkはactionを認識するまで実行しないことを意味する

• 慣れないユーザからすると直観に反していますが、Haskell・LINQのようなのような関数型言語を使用している人には慣れていることでしょう

• 遅延評価は、RDDに対するtransformationを呼び出す時(例えば、map())、操作はすぐに実行されないことを意味します

• 代わりに、Sparkは内部的にメタデータをこの操作が要求されたことを示すために記録します

• RDDに特定のデータが含まれていると考えるのではなく、transformationsを通じてデータを計算する方法の手順が各RDDから構築されていると考えるのがベストです

• RDDへのデータのロードはtransformationsと同様に遅延評価が行われます

• sc.textFile()を呼び出した際、必要になるまでデータはロードされません。transformations同様に、操作が複数回発生する可能性があります

• TIP– transformationsは遅延評価されるが、count()のようなactionを実行することで、実行を強制することが出来る

– これは、プログラムの一部だけをテストする簡単な方法だ

– Sparkは一緒の操作をグループ化することによって、データの引継ぎを行うパスの数を減らすために、遅延評価を使用する

– HadoopのMapReduceのようなシステムでは、開発者は多くの場合、MapReduceのパスの数を最小限に抑えるために、操作のグループ化への考慮に多くの時間を費やす必要がある

– Sparkでは多くの簡単な操作のチェーン化を行う代わりに、単一の複雑なMapを記述することに、実質的な利点は無い

– このため、ユーザ(プログラマ)は彼らのプログラムを管理できる単位に、自由に細分化することが出来る

Passing Functions to Spark(1)

• 殆どのSparkのtransformationsや一部のactionsは、データを計算するためにSparkによって使用される関数を渡すことに依存している

• コア言語(Python, Scala, Java)はそれぞれ、僅かに異なるSparkに関数を渡すためのメカニズムを保有する

• Python

– Pythonでは3つの形式を用いることが出来る(ラムダ式、トップレベル、ローカル)

– 関数を渡す時に注意する必要がある問題の一つは、誤って関数を含むオブジェクトをシリアル化することである

– オブジェクトのメンバーである、またはオブジェクト内のフィールドへの参照を含む関数を渡すと(例:self.field)、Sparkはワーカーノードに全体のオブジェクト(全ての データ)を送信する為、必要とする情報のビット数よりもはるかに大きくなってしまう

– もしクラスがPythonがpickle化することが出来ないオブジェクトを含んでいる場合、プログラムが失敗することがある

例3-18:

word = rdd.filter(lambda s: "error" in s)def containsError(s):

return "error" in sword = rdd.filter(containsError)

Passing Functions to Spark(2)

例3-19:Passing a function with field references (don’t do this!)

class SearchFunctions(object):def __init__(self, query):

self.query = querydef isMatch(self, s):

return self.query in sdef getMatchesFunctionReference(self, rdd):

# Problem: references all of "self" in "self.isMatch"return rdd.filter(self.isMatch)

def getMatchesMemberReference(self, rdd):# Problem: references all of "self" in "self.query"return rdd.filter(lambda x: self.query in x)

代わりにローカル変数にオブジェクトから必要な項目を抽出し、Python関数を渡す

例3-20:Python function passing without field references

class WordFunctions(object):...def getMatchesNoReference(self, rdd):

# Safe: extract only the field we need into a local variablequery = self.queryreturn rdd.filter(lambda x: query in x)

Passing Functions to Spark(3)

例3-21:Scala function passing

class SearchFunctions(val query: String) {def isMatch(s: String): Boolean = {

s.contains(query)}def getMatchesFunctionReference(rdd: RDD[String]): RDD[String] = {

//注意:全データを参照してしまうrdd.map(isMatch)

}def getMatchesFieldReference(rdd: RDD[String]): RDD[String] = {

//注意:全データを参照してしまうrdd.map(x => x.split(query))

}def getMatchesNoReference(rdd: RDD[String]): RDD[String] = {

// Safe: extract just the field we need into a local variableval query_ = this.queryrdd.map(x => x.split(query_))

}}

• Scala

– Scalaでは、Scalaの他の機能的なAPIの為に、インラインに定義された関数内のメソッド、関数への参照、静的な関数を渡すことが出来る

– いくつかの他の考慮事項は、関数を渡し、その中で参照されているデータはSerializableである(JavaのSerializableインタフェースを実装する)必要がある

– さらにPythonのように、オブジェクトのメソッドやフィールドを渡すと、そのオブジェクト全体への参照が含まれてしまう

– 例3-20のPython同様に例3-21に示すように、我々はローカル変数に必要項目を抽出することで、オブジェクト全体を渡すことから回避することが出来る

– もしNotSerializableExceptionがScalaで発生した場合は、シリアル化不可能なクラスのメソッドやフィールド への参照は通常問題になる

– トップレベルのオブジェクトのメンバーである、シリアル化可能なローカル変数や関数を渡すことは常に安全であることに注意する必要がある

• Java

– Javaでは、関数はSparkの機能に関するInterface(org.apache.spark.api.java.functionパッケージ)の一つを実装することで、機能は提供されます

– 関数の戻り型に基づいて、異なるインターフェイスの数がある

– 表3-1にて最も基本的な機能のインターフェースを示す

– Javaでは、Key/Valueのような特別な型を戻す必要がある時のために、他のInterfaceの数をカバーしている

Passing Functions to Spark(4)

Function nameFunction nameFunction nameFunction name Method to implementMethod to implementMethod to implementMethod to implement UsageUsageUsageUsage

Function<T, R> R call(T) Take in one input and return one output, for use with operations like map() and filter().

Function2<T1, T2, R> R call(T1, T2) Take in two inputs and return one output, for use with operations like aggregate() or fold().

FlatMapFunction<T, R> Iterable<R> call(T) Take in one input and return zero or more outputs, for use with operations like flatMap().

表3-1

– インライン匿名内部クラス(例:3-22)として機能クラスを定義する、または名前付きクラスを作成することが出来る

Passing Functions to Spark(4)

例3-22:Java function passing with anonymous inner class

RDD<String> errors = lines.filter(new Function<String, Boolean>() {public Boolean call(String x) { return x.contains("error"); }

});

例3-23:Java function passing with named class

class ContainsError implements Function<String, Boolean>() {public Boolean call(String x) { return x.contains("error"); }

}RDD<String> errors = lines.filter(new ContainsError());

– 選択するスタイルは個人的な好みであるが、トップレベルの名前付きの関数は、多くの場合大規模なプログラムを編成するためのクリーナーであることを見出した

– 例3-24に示すように、トップレベルの機能の利点の一つは、コンストラクタのパラメータを与えることが出来ることである

例3-24:Java function class with parameters

class Contains implements Function<String, Boolean>() {private String query;public Contains(String query) { this.query = query; }public Boolean call(String x) { return x.contains(query); }

}RDD<String> errors = lines.filter(new Contains("error"));

– Java8では、簡潔に関数のインターフェースを実装する為にラムダ式を使用することが出来ます

– Java8は、この文章を書いている時点では比較的新しく、当書の例では、以前のバージョンのJavaの構文を用いてクラスを定義しします

– ラムダ式を用いた例を例3-25で例示します

例3-25:Java function passing with lambda expression in Java 8

RDD<String> errors = lines.filter(s -> s.contains("error"));

– もし、Java8のラムダ式を使用することに興味がある場合は、OracleのマニュアルとSparkでラムダ式を使用する方法について記述されたDatabricksのブログ記事を参照して下さい

– TIP

• 匿名内部クラスとラムダ式の両方において、どのようなfinal変数でもそれを囲む方法で参照することが出来るので、PythonやScalaのようにこれらの変数を渡すことが出来る

Passing Functions to Spark(5)

• 序文

– この章では、Sparkの中で最も一般的なtransformationsとactionsを見ていく

– 追加の操作は、特定のタイプのデータを含むRDDで利用可能

• 例:統計関数によりRDDを操作する/KeyValueのRDDをキー毎に集計する

– 後のセクションにおいて、RDDの種類とこれらの特別な操作の間の変換をカバーします

• Basic RDD

– データに関係なく全てのRDDに対して実行できるtransformationsとactionsについて、記載することとする

– Element-wise transformations(要素毎のtransformations )

• おそらく、最も一般的で使用されるtransformationsはmap()とfilter()です

• map()は関数を取りこみ、RDDの各要素に対して関数を適用し、その結果を新しいRDDの各要素に適用します

• filter()は関数を取りこみ、関数を用いてフィルタ条件に合致する要素で新しいRDDを戻します

Common Transformations and Actions(1)

• 自分がコレクションしているURLに関連づいているウェブサイトを取得するかのように、任意の数字の二乗を行うためにmap()を使用することが出来ます(?)

• map()の戻り値の型は、入力の型と一致する必要はありません

Common Transformations and Actions(2)

例:3-27. Scala squaring the values in an RDD

val input = sc.parallelize(List(1, 2, 3, 4))val result = input.map(x => x * x)println(result.collect().mkString(","))

• 自分がコレクションしているURLに関連づいているウェブサイトを取得するかのように、任意の数字の二乗を行うためにmap()を使用することが出来ます(?)

• map()の戻り値の型は、入力の型と一致する必要はありません

• 時折、各入力要素に対応した複数の出力を必要とすることがあります

• この操作はflatMap()と呼ばれています

• Map()同様、flatMap()に渡された関数は、入力RDD内の要素毎に個別に呼ばれます

• 単一の要素を返す代わりに、戻り値とイテレータを戻す

• イテレータのRDDを生成するよりはむしろ、イテレータの全ての要素から構成されるRDDを得る

• flatMapの単純な使用例は、入力された文字列を分割するものである

例3-30:flatMap() in Scala, splitting lines into multiple words

val lines = sc.parallelize(List("hello world", "hi"))val words = lines.flatMap(line => line.split(" "))words.first() // returns "hello"

Common Transformations and Actions(3)

• flatMap()とmap()の違いを説明します

• リスト形式のRDDではなく、リスト形式の要素を持つRDDを戻すことが出来るので、flatMap()についてイテレータを「フラット化するもの」のように考えることが出来ます(?)

– Pseudo set operations(擬似集合演算(操作))• RDD自身について、要素に欠落がある場合でも、RDDはunion()やintersection()などの多くの数学的な操

作をサポートしています(?)

• 4つの操作について、図3-4に示します

• これらの操作は、操作対象のRDDが全て同じタイプである必要がある点に注意してください

• Distinct()

– 最も頻繁に発生するRDDの欠落は、我々もよくデータを重複させるように、要素の一意性に関してです

– ユニークな要素が必要な場合、ユニークな要素と新しいRDDを生成するRDD.distinct() transformationを使用することが出来ます

– 各要素の1つのコピーのみを受け取ることを保証するために、ネットワーク上(クラスタ上?)の全データをシャッフルする必要があるため、distinct()は負担が大きいことに留意してください

– シャッフルに関して、およびそれをどのように回避するかは、第4章で詳しく説明されています

• union()

– 最も単純な集合演算(操作)はunion()です、それは両方のソースデータから構成されるRDDを戻すものです

– これは、多くのソースからログファイルを処理するようなユースケースの場合に有用です

– 数学的なunion()と異なり、入力のRDDに重複が存在する場合、Sparkのunion()の結果には重複が含まれます(必要に応じて、distinct()を併用することで修正することが可能です)

• intersection()

– 最も単純な集合演算(操作)はunion(other)である、それは両方のソースデータから構成されるRDDを戻すものです

– Sparkは両方のRDDから要素だけを戻すintersection(other)関数を提供します

– intersection()は全ての重複を排除します(シングルRDDの重複を含む)

– intersection() and union()は類似した概念でありますが、intersection()は共通の要素を識別するために、ネットワークを介したシャッフルを必要とするので、パフォーマンスは遥かに悪いです

– 時々、パフォーマンスへの考慮からデータを削除する必要があります

Common Transformations and Actions(4)

• subtract()

– subtract()関数は、二つのRDDの差分を返します。subtract()関数は別のRDDを引数にとり、引数に指定したRDDには存在せず、最初のRDDにだけ存在するRDDを戻します

– intersection()のようにシャッフルを行います

• cartesian()

– 二つのRDD間の直積を計算する事が出来ます

– cartesian() transformationは全ての対を返します

– 各々のオファーに対する全てのユーザの期待興味を計算するような、全ての可能なペアの類似性を考慮したい場合に、cartesian()は役に立ちます

– RDD自身とのcartesian()を実行することが出来ます、それはユーザ類似のようなタスクの際に役立ちます

– しかし、巨大なRDDの場合、cartesian()は非常に高価であることに注意してください

Common Transformations and Actions(5)

Common Transformations and Actions(6)

関数名関数名関数名関数名 目的目的目的目的 実装例実装例実装例実装例 結果結果結果結果

map() 関数をそれぞれの要素に 適用し、結果のRDDを返す

rdd.map(x=>X+1)

{2,3,4,4}

flatMap() RDDの各要素に関数を適用し、イテレータの内容のRDDを戻す。しばしば、単語を抽出する為に使用される

rdd.flatMap(x=>x.to(3))

{1,2,3,2,3,3,3}

filter() フィルタ条件に合致する要素だけから構成されるRDDを返す

rdd.filter(x=>!=1)

{2,3,3}

distinct() 重複を排除する rdd.distinct() {1,2,3}

sample(WithReplacement, fraction, [seed] )

置換の有無に関係なく、サンプルを抽出する rdd.sample(false, 0.5)

Nondeterministic

• 表3-2:基本的なRDD transformations

※ 実装例のRDDの内容:{1, 2, 3, 3}

Common Transformations and Actions(7)

関数名関数名関数名関数名 目的目的目的目的 実装例実装例実装例実装例 結果結果結果結果

union() 両方のRDDの要素を 含むRDDを生成する rdd.union(other) {1, 2, 3, 3, 4, 5}

intersection() 両方のRDDに含まれる要素だけを含んだRDDを生成する

rdd.intersection(other)

{3}

subtract() 対象のRDDから要素を削除する(例:トレーニングデータを削除する)→対象のRDDと引数で

指定したRDDの共通要素を対象のRDDから削除する

rdd.subtract(other)

{1, 2}

cartesian() 引数で渡したRDDとの直積のRDDを生成する rdd.Cartesian(other)

{(1, 3), (1, 4), … (3, 5)}

• 表3-3:二つのRDDによる transformations

※ 実装例のRDDの内容:{1, 2, 3}{3, 4, 5}

– Actions

• おそらく最も使用する、RDD上の最も一般的なActionはreduce()です。それはRDDの2つの要素を使用し、同じ型の新しい要素を返す機能です

• この機能の簡単な例は「+」であり、RDDの合計を計算するために使用できます

• reduce()は、要素の合計を計算し、要素の数をカウントし、他の種類の集計を実行することが出来ます

• reduce()と同様の処理としてfold()がありますが、これは各パーティション上の最初の呼出に使用される0値を必要とする点である

• 提供される0値は、操作における基準(=アイデンティティ要素)であるべきです。機能で複数回適用する事で、値を変更してはいけません(例えば、+の場合は0、*の場合は1、または連結の場合は空リスト)

• TIP

– fold()

» 2つのパラメータのうち、最初のオペレータを修正することで、fold()によるオブジェクトの生成を最小限に抑えることが出来ます

» ただし、第二のパラメータを変更してはなりません

» fold()やreduce()は共に、戻り値の型と操作するRDDの属性が同じ型である必要があります

» この仕様は合計を計算するような操作には適していますが、時には異なる型を必要とすることがあります

» 例えば、移動平均を計算するとき、数値の要素とカウントの経過を追跡し、ペアとして取得する必要があります

» 最初に全ての要素を(関数を適用することで)別の要素に変換するmap()を最初の変数に使用することで、必要とする型を戻し、reduce()はペアで作業できるようになります

Common Transformations and Actions(8)

例3-33: Scala における reduce()の例

val sum = rdd.reduce((x, y) => x + y)

Common Transformations and Actions(9)

– aggregate()

» aggregate() 関数は、戻り値の型がRDDと同じ型である制約から、私たちを解放します(fold()のように、戻り値で必要とする型を返す為に、「zero value」を指定する必要があるように)

» 次に、アキュムレータとのRDDの要素を結合する機能を提供します

» 最後に、各ノードに独自の計算結果を蓄積していることを考えると、2つのアキュムレータをマージする第二の機能を提供する必要があります

» aggregate()を使用して平均を計算する例を示します(map()を使用せずに、RDDの平均を計算する例を示します)

例3-36: Scala における aggregate() の例

val result = input.aggregate((0, 0))((acc, value) => (acc._1 + value, acc._2 + 1),(acc1, acc2) => (acc1._1 + acc2._1, acc1._2 + acc2._2))

val avg = result._1 / result._2.toDouble

Common Transformations and Actions(10)Accumulatorに関する補足

– アキュムレーター(accumulator)は、“追加”のみを行う変数

– driverでアキュムレーターを生成し、各executorでアキュムレーターに対して値の追加(加算・蓄積)を行い、driverでその結果(総計)を受け取ることが出来る

– アキュムレーターは、Hadoopのカウンターのようなもの(Hadoopでは、各タスクでカウンターに値を加算していく)

– ただし、Hadoopのカウンターの結果はアプリケーション内からは利用できない(Mapタスクで集計したカウンターをReduceタスクで読み込むことは出来ない)が、Sparkのアキュムレーターはアプリケーション内の後続処理に利用することが出来る

– アキュムレーターはvalueというフィールドを持っており、アキュムレーターの「+=」メソッドを使うと、valueに対して追加(加算)が実行される

val rdd = sc.makeRDD(Seq(1, 2, 3))val sum = sc.accumulator(0)rdd.foreach(sum += _)println(sum.value)

– collect()

» RDD上のいくつかのactionsは、driverにデータの一部または全部を通常のコレクションまたは値の形式で返します

» driverにデータを戻す最も単純で一般的な操作はcollect()で、それはRDD全体の内容を返します

» collect()は一般的に、RDDと期待結果を比較することが簡単に出来ることから、RDD全体がメモリに収まることを前提にユニットテストに使用されます

» collect()には、全てのデータをdriverにコピーする必要があるので、全てのデータが単一のマシンに収まる必要がある、という制限があります

– take(n)

» take(n)はn個の要素をRDDから取得しますが、それはアクセスするパーティションの数を最小限にするので、取得できるコレクションは偏ってしまうかもしれません

» この操作が期待する順序で要素を返さない点に注意することは重要です

» これらの操作(collect()、take(n))は、ユニットテスト及びデバッグには有用ですが、大量のデータを扱う場合、ボトルネックを引き起こすかも知れません

– top(n)

» もし、データに順序性がある場合は、top()を使用してRDDからトップ要素を抽出することが出来ます

» top()はデータ自体の順序を使用しますが、トップ要素を抽出する為に、自分の比較関数を供給することが出来ます

– takeSample(withReplacement, num, seed)

» 時々、driverはサンプルデータを必要とします

» takeSample(withReplacement, num, seed)機能は交換することなく、データのサンプルを取得することが出来ます

Common Transformations and Actions(11)

– foreach()

» 時には、driverに結果を返すことなく、RDD内のすべての要素に対してactionを実行することが有用です

» この良い例は、WebサーバにJSONをPOSTしたり、DBにレコードを挿入することです

» いずれの場合も、foreach() actionは、ローカルに戻ることなく(driverに戻ることなく)、RDDの各要素の計算を行うことが出来ます

– その他の基本的なRDDの操作は、名前から想像されるように振る舞います

– count()は要素数を返し、countByValue()は固有値と固有値毎の要素数を紐づけて返します

Common Transformations and Actions(12)

関数名関数名関数名関数名 目的目的目的目的 実装例実装例実装例実装例 結果結果結果結果

collect() 全ての要素をRDDから戻す rdd.collect() {1, 2, 3, 3}

count() RDDの要素数を戻す rdd.count() 4

countByValue() 各々の要素のRDD内の数を戻す

rdd.countByValue() {(1, 1), (2, 1), (3, 2)}

take(num) RDDからnum個の要素を戻す

rdd.take(2) {1, 2}

• 表3-4:基本的なactions

※ 実装例のRDDの内容:{1, 2, 3, 3}

Common Transformations and Actions(13)

関数名関数名関数名関数名 目的目的目的目的 実装例実装例実装例実装例 結果結果結果結果

top(num) トップnum個の要素をRDDから戻す

rdd.top(2) {3, 3}

takeOrdered(num)(ordering)

指定された順序に基づいて、num個の要素を戻す

rdd.takeOrdered(2)(myOrdering)

{3, 3}

takeSample(withReplacement, num, [seed])

ランダムにnum個の要素を戻す

rdd.takeSample(false, 1)

Nondeterministic

reduce(func) 複数のRDDの要素を並行に組込む(例:合計する)(?)

rdd.reduce((x, y) => x + y)

9

fold(zero)(func) reduce()同様。ただし、0を指定する必要がある

rdd.fold(0)((x, y) => x + y)

9

Aggregate(zeroValue)(seqOp, combOp)

reduce()同様。ただし、違う型を戻す場合に使用する

rdd.aggregate((0, 0)) ((x, y) => (x._1 + y, x._2 + 1), (x, y) => (x._1 + y._1, x._2 + y._2))

(9, 4)

foreach(func) RDDの各要素に与えられた関数を適用する

rdd.foreach(func) Nothing

• Converting Between RDD Types(RDDの型の変換)

– いくつかの関数は、RDDの特定の型だけで利用可能です(例えば、Key/Valueのペアを使用したjoin()、数値のRDDを使用したmean()/variance())

– 第6章で数値のRDDでしか使用できない関数を、第4章でKey/ValueのペアのRDDでしか使用できない関数を説明します

– ScalaとJavaでは、これらの方法は標準的なRDD上では定義されていないので、この追加機能を利用する為に、正しい専用のクラスを得る必要があります

– Scala

• Scalaにおける特別な機能によるRDDへの変換は(例:RDD[Double]をnumericを必要とする機能に公開する際)、暗黙的な変換によって、自動的に行われます

• 「SparkContextの初期化」で述べた通り、これらの変換を動作させるために、「import org.apache.spark.SparkContext._ 」を追記する必要があります

• SparkContextオブジェクトのScalaDocに、暗黙的な変換に関する記述を見ることが出来ます

• mean()やvariance()のような追加機能を公開する為に、暗黙的にRDDを変換するラッパークラスを用意します(DoubleRDDFunctions(数値データのRDDのため)、PairRDDFunctions(Key/Valueペアのため)のような)

• 暗黙の変換は非常に強力ながら、時には混乱することがあります

• RDDのmean()のような関数を呼び出す場合は、RDDクラスのScaladocsを確認し、mean()が存在しないことに気づきます

• RDD[Double]とDoubleRDDFunctionsの暗黙的な変換の成功のために、呼出側が管理しています

• ScaladocでRDDの機能を検索する場合は、ラッパークラスで利用可能な機能を確認してください

Common Transformations and Actions(14)

– Java

• Javaにおける特殊なRDDへの変換は、もう少し明示的に行う必要があります

• 具体的には、これらの型用のメソッドを追加した、これらの型のRDDのためのJavaDoubleRDDとJavaPairRDDと呼ばれる特別なクラスがあります

• これは、正確に何が起きているのかをより深く理解できるという利点を持っていますが、少し厄介でもあります(ソースの可読性は高くなるが、実装者に負担を与える、ということかな?)

• これらの特別な型のRDDを作るために、Functionクラスを使用する代わりに、特殊なバージョンを使用する必要があります

• もし、型TのRDDからDoubleRDDを生成する場合は、Function<T, Double>よりむしろ、DoubleFunction<T>を使用します

• 表3-5にて、専門化された機能とそれらの用途を示します

• 私たちは、RDDの様々な関数を呼び出す必要があります(よって、単純にDoubleFunctionを生成し、map()に渡すことは出来ません)

• DoubleRDDを戻してほしい場合、map()を呼ぶ代わりに以降の他の機能と同じパターンで、mapToDouble()を呼ぶ必要があります

Common Transformations and Actions(15)

関数 代替手段 用途DoubleFlatMapFunction<T> Function<T, Iterable<Double>> DoubleRDD from a

flatMapToDoubleDoubleFunction<T> Function<T, double> DoubleRDD from mapToDoublePairFlatMapFunction<T, K, V> Function<T, Iterable<Tuple2<K, V>>> PairRDD<K, V> from a

flatMapToPairPairFunction<T, K, V> Function<T, Tuple2<K, V>> PairRDD<K, V> from a mapToPair

• 序文

– 前述したように、Spark RDDは遅延評価され、時には同じRDDを複数回使用することがあるかもしれません

– 単純にRDDを複数回使用すると、Sparkは使用したRDDとその依存関係にあるRDDをactionを呼び出す度に再計算します

– データを多回数参照するような、反復アルゴリズムにおいて、特に高価になります

– 例3-39に示すように、別の簡単な例では、同じRDDをカウントし、出力しています

– RDDの複数回の計算を回避する為に、Sparkにデータを永続させることが出来ます

– RDDを永続化することをSparkに依頼する場合、RDDを実行しているノードはそのパーティションを格納します

– もし、データを永続化しているノードに障害が発生した場合、Sparkはデータを必要とされるタイミングで失われたパーティションを再計算します

– もし、ノード障害への対応によるスローダウンを避けたい場合、複数のノードにデータを複製することが出来ます

– Sparkは永続化に関して目的に合わせて選択できるように、多くのレベルを持っています(表3-6で確認することが出来ます)

– Scala(例3-40)やJavaでは、persist()のデフォルトの仕様は、シリアライズ化されていないオブジェクトとしてJVMヒープ領域にデータを格納します

– Pythonでは、常にデータを永続化・保存する際はシリアル化します、デフォルトは塩漬けされたオブジェクトとしてJVMのヒープ領域に格納されます

– データをディスクやヒープ以外のストレージに出力した場合は、そのデータは常にシリアル化されます

Persistence (Caching)①

例3-39:Scalaにおける重複実行

val result = input.map(x => x*x)println(result.count())println(result.collect().mkString(","))

Persistence (Caching)②

Level Space

used

CPU

time

In

memory

On

disk

Comments

MEMORY_ONLY High Low Y N

MEMORY_ONLY

_SER

Low High Y N

MEMORY_AND

_DISK

High Mediu

m

Some Some Spills to disk if there is too

much data to fit in memory.

MEMORY_AND

_DISK_SER

Low High Some Some Spills to disk if there is too

much data to fit in memory.

Stores serialized

representation in memory.

DISK_ONLY Low High N Y

• 表3-6:永続化レベル(org.apache.spark.storage.StorageLevelとpyspark.StorageLevel)

※必要に応じて、ストレージレベルの末尾に“_2”を追加することで、二台のマシン上のデータを複製する

ことが出来ます

• TIP

– ヒープ外のキャッシングは実験的で、Tachyonを使用しています

– もしSparkのヒープ外のキャッシングに興味を持っている場合、"Running Spark on Tachyon guide(*)"を見てみましょう

(*)http://tachyon-project.org/Running-Spark-on-Tachyon.html

(*)日本語の解説サイト:http://qiita.com/FKuro_/items/3b34f9f64a17c73ccdd4

– 最初のactionが実行される前に、呼び出したpersist()がRDDに通知されます

– persist()の呼出は、評価を強制するものではありません

– あまりにも多くのデータをキャッシュしようとすると、Sparkは自動的に最長未使用時間キャッシングポリシー(LRU)に従って、古いパーティションを追い出します

– ストレージレベルが”メモリのみ”の場合、これらのパーティションは再度アクセスされた際に再計算されます。ストレージレベルが”メモリとディスク”の場合、ディスクに出力されます

– いずれの場合も、Sparkに多くのデータをキャッシュさせたとしても、JOBの失敗を心配することが無いことを意味します

– しかし、キャッシュ不要なデータは、データを有用な状態から遠ざけ、更なる再計算時間を必要とします

– 最後にRDDには、unpersist()と呼ばれるキャッシュから手動で削除するメソッドが存在します

Persistence (Caching)③

例3-40: Scala におけるpersist()

val result = input.map(x => x * x)result.persist(StorageLevel.DISK_ONLY)println(result.count())println(result.collect().mkString(","))

• 本章では、RDDの実行モデルと多くの一般的な操作をカバーしました

• もしここまで読んできたのであれば、あなたはSparkにおける全てのコアコンセプトを学ぶことが出来ました

• 次の章では私たちは特別な操作のセットを可能にするKey/Valueのペアをカバーします。それは並列に集約するために最も一般的な方法です

• そのあと、我々は様々なデータソースとの入出力やSparkContextの挙動に関するより高度なトピックについて説明します

• RDDが何故弾力性があると言われているかというと、常にRDDを再計算する能力があるからです

• RDDデータを保持する機械に障害が発生すると、Sparkは行方不明になったデータをユーザに意識させずに再計算します

Conclusion(結論)