23. 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 s
word = rdd.filter(containsError)
24. 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 = query
def isMatch(self, s):
return self.query in s
def 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 variable
query = self.query
return rdd.filter(lambda x: query in x)
25. 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 variable
val query_ = this.query
rdd.map(x => x.split(query_))
}
}
• Scala
– Scalaでは、Scalaの他の機能的なAPIの為に、インラインに定義された関数内のメソッド、関数への参照、静的
な関数を渡すことが出来る
– いくつかの他の考慮事項は、関数を渡し、その中で参照されているデータはSerializableである(Javaの
Serializableインタフェースを実装する)必要がある
– さらにPythonのように、オブジェクトのメソッドやフィールドを渡すと、そのオブジェクト全体への参照が含
まれてしまう
– 例3-20のPython同様に例3-21に示すように、我々はローカル変数に必要項目を抽出することで、オブジェク
ト全体を渡すことから回避することが出来る
26. – もし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
27. – インライン匿名内部クラス(例: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"));
28. – 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)
45. 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”を追加することで、二台のマシン上のデータを複製する
ことが出来ます
46. • 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(","))