web-dev-qa-db-ja.com

(なぜ)キャッシュを呼び出す必要があるのか​​、それともRDDに固執する必要があるのか

回復力のある分散データセット(RDD)がテキストファイルまたはコレクション(または別のRDD)から作成された場合、RDDデータをメモリに格納するために明示的に "cache"または "persist"を呼び出す必要がありますか?それともRDDデータはデフォルトでメモリに分散して格納されていますか?

val textFile = sc.textFile("/user/emp.txt")

私の理解によれば、上記のステップの後、textFileはRDDであり、ノードのメモリの全部または一部で利用可能です。

もしそうなら、なぜtextFile RDDで "cache"や "persist"を呼び出す必要があるのでしょうか。

150
Ramana

ほとんどのRDD操作は遅延します。一連の操作の説明としてRDDを考えてください。 RDDはデータではありません。だからこの行:

val textFile = sc.textFile("/user/emp.txt")

何もしません。 「このファイルをロードする必要があります」というRDDが作成されます。この時点ではファイルはロードされていません。

データの内容を観察する必要があるRDD操作は遅延することはできません。 (これらはactionsと呼ばれています。)例はRDD.countです - ファイル内の行数を伝えるには、ファイルを読み込む必要があります。 textFile.countを書いた場合、この時点でファイルが読み込まれ、行が数えられ、数が返されます。

textFile.countをもう一度呼び出すとどうなりますか?同じこと:ファイルが再び読み込まれてカウントされます。何も格納されていません。 RDDはデータではありません。

それでRDD.cacheは何をしますか?上記のコードにtextFile.cacheを追加すると、

val textFile = sc.textFile("/user/emp.txt")
textFile.cache

何もしません。 RDD.cacheも怠惰な操作です。ファイルはまだ読み取られていません。しかし、今やRDDは「このファイルを読んでからその内容をキャッシュする」と言っています。その後、初めてtextFile.countを実行すると、ファイルがロードされ、キャッシュに入れられてカウントされます。 textFile.countをもう一度呼び出すと、操作はキャッシュを使用します。キャッシュからデータを取り出して行数を数えるだけです。

キャッシュの動作は利用可能なメモリによって異なります。たとえば、ファイルがメモリに収まらない場合、textFile.countは通常の動作に戻り、ファイルを再読み込みします。

267
Daniel Darabos

私は質問がよりよく定式化されるだろうと思います:

キャッシュを呼び出す必要があるのはいつですか。

スパークプロセスは怠惰です。つまり、必要になるまでは何も起こりません。質問に簡単に答えるために、val textFile = sc.textFile("/user/emp.txt")が発行された後、データには何も起こらず、ファイルをソースとして使用してHadoopRDDのみが構築されます。

そのデータを少し変換するとしましょう。

val wordsRDD = textFile.flatMap(line => line.split("\\W"))

繰り返しますが、データには何も起こりません。 wordsRDDへの参照と必要に応じて適用される関数を含む新しいRDD testFileがあります。

wordsRDD.countのように、アクションがRDDに対して呼び出された場合にのみ、lineageと呼ばれるRDDチェーンが実行されます。つまり、パーティションに分割されたデータはSparkクラスタのエグゼキュータによってロードされ、flatMap関数が適用され、結果が計算されます。

この例のように線形系統では、cache()は必要ありません。データがエグゼキュータにロードされ、すべての変換が適用され、最後にcountが計算されます。データがすべてメモリに収まる場合は、すべてメモリに格納されます。

cacheは、RDDの系統が分岐するときに便利です。前の例の単語をプラスとマイナスの単語の数にフィルタしたいとしましょう。あなたはそのようにすることができます:

val positiveWordsCount = wordsRDD.filter(Word => isPositive(Word)).count()
val negativeWordsCount = wordsRDD.filter(Word => isNegative(Word)).count()

ここで、各ブランチはデータのリロードを発行します。明示的なcacheステートメントを追加すると、以前に行われた処理が確実に保持され、再利用されます。仕事は次のようになります。

val textFile = sc.textFile("/user/emp.txt")
val wordsRDD = textFile.flatMap(line => line.split("\\W"))
wordsRDD.cache()
val positiveWordsCount = wordsRDD.filter(Word => isPositive(Word)).count()
val negativeWordsCount = wordsRDD.filter(Word => isNegative(Word)).count()

そのため、cacheはチェックポイントを作成し、それをさらなる処理に再利用できるため、「系列を壊す」と言われています。

経験則:RDDの系統が分岐するの場合、またはループ内でRDDが複数回使用される場合は、cacheを使用します。

169
maasg

RDDデータをメモリに格納するために明示的に "cache"または "persist"を呼び出す必要がありますか?

はい、必要な場合に限ります。

RDDデータはデフォルトでメモリに分散して格納されていますか?

いいえ!

そして、これらが理由です:

  • Sparkは2つのタイプのシェア変数をサポートします。すべてのノードのメモリに値をキャッシュするために使用できるブロードキャスト変数と、カウンタや合計のように「追加」されるだけの変数であるアキュムレータです。

  • RDDは、既存の操作から新しいデータセットを作成する変換と、データセットで計算を実行した後にドライバプログラムに値を返すアクションの2種類の操作をサポートしています。たとえば、mapは各データセット要素を関数に渡して結果を表す新しいRDDを返す変換です。一方、reduceは、関数を使用してRDDのすべての要素を集約し、最終結果をドライバプログラムに返すアクションです(ただし、分散データセットを返すパラレルreduceByKeyもあります)。

  • Sparkのすべての変換は、結果をすぐには計算しないという点で、怠惰です。その代わりに、彼らはただいくつかの基本データセット(例えばファイル)に適用された変換を覚えているだけです。変換は、アクションが結果をドライバプログラムに返す必要がある場合にのみ計算されます。この設計により、Sparkをより効率的に実行できます。たとえば、mapで作成されたデータセットはreduceで使用され、より大きなマップデータセットではなくreduceの結果のみをドライバーに返すことがわかります。

  • デフォルトでは、変換された各RDDは、アクションを実行するたびに再計算されます。 ただし、persist(またはcache)メソッドを使用してRDDをメモリに永続化することもできます。その場合、Sparkは要素をクラスタ上にずっと早く保持します。次回照会したときにアクセスするディスク上にRDDを永続化したり、複数のノードにまたがって複製したりすることもできます。

詳しくは Sparkプログラミングガイド をご覧ください。

27
eliasah

以下は、RDDをキャッシュする必要がある3つの状況です。

rDDを何度も使用する

同じRDDで複数のアクションを実行する

長い(または非常に高価な)変換チェーン

6
rileyss

cacheメソッド呼び出しを追加する(または一時的に追加する)ための別の理由を追加する。

デバッグメモリの問題

cacheメソッドを使用すると、sparkはRDDのサイズに関するデバッグ情報を提供します。そのため、spark統合されたUIでは、RDDのメモリ消費情報が得られます。これはメモリの問題を診断するのに非常に役立ちました。

5
zinking