web-dev-qa-db-ja.com

RDDを2つ以上のRDDに分割するにはどうすればよいですか?

RDDを2つ以上のRDDに分割する方法を探しています。私が見た中で最も近いのはScala Spark:コレクションを複数のRDDに分割しますか?まだ1つのRDDです。

SASに精通している場合、次のようなものです。

data work.split1, work.split2;
    set work.preSplit;

    if (condition1)
        output work.split1
    else if (condition2)
        output work.split2
run;

その結果、2つの異なるデータセットが作成されました。意図した結果を得るには、すぐに永続化する必要があります...

35

単一の変換から複数のRDDを生成することはできません*。 RDDを分割する場合は、分割条件ごとにfilterを適用する必要があります。例えば:

def even(x): return x % 2 == 0
def odd(x): return not even(x)
rdd = sc.parallelize(range(20))

rdd_odd, rdd_even = (rdd.filter(f) for f in (odd, even))

バイナリ条件のみがあり、計算が高価な場合は、次のようなものを好むかもしれません。

kv_rdd = rdd.map(lambda x: (x, odd(x)))
kv_rdd.cache()

rdd_odd = kv_rdd.filter(lambda kv: kv[1]).keys()
rdd_even = kv_rdd.filter(lambda kv: not kv[1]).keys()

これは、単一の述部計算のみを意味しますが、すべてのデータに対する追加のパスが必要です。

入力RDDが適切にキャッシュされ、データ分散に関する追加の仮定がない限り、繰り返しフィルターとネストされたif-elseを使用したforループの時間の複雑さに関しては大きな違いはないことに注意することが重要です。

N個の要素とM個の条件を使用すると、実行する必要がある操作の数は明らかにN回Mに比例します。forループの場合、(N + MN)/ 2に近く、繰り返しフィルタは正確にNMしかし、一日の終わりにはO(NM)以外の何ものでもありません。 Jason Lenderman で私の議論**を見て、賛否両論について読むことができます。

非常に高いレベルでは、2つのことを考慮する必要があります。

  1. RDDが具体化されないアクションを実行するまで、スパーク変換は遅延します

    なぜそれが重要なのですか?私の例に戻って:

    rdd_odd, rdd_even = (rdd.filter(f) for f in (odd, even))
    

    後でrdd_oddのみが必要であると判断した場合、rdd_evenを具体化する理由はありません。

    SASの例を見てwork.split2を計算する場合、入力データとwork.split1の両方を具体化する必要があります。

  2. RDDは宣言型APIを提供します。 filterまたはmapを使用する場合、この操作の実行方法は完全にSparkエンジンまでです。変換に渡される関数に副作用がない限り、パイプライン全体を最適化する複数の可能性が生まれます。

結局のところ、このケースは、独自の変換を正当化するのに十分な特別なものではありません。

このフィルターパターンのマップは、実際にはコアSparkで使用されます。 Sparks RDD.randomSplitは実際にRDDをどのように分割するか およびrandomSplitメソッドの 関連部分 に対する私の答えを参照してください。

唯一の目標が入力の分割を達成することである場合、partitionByのテキスト出力形式にDataFrameWriter句を使用することができます。

def makePairs(row: T): (String, String) = ???

data
  .map(makePairs).toDF("key", "value")
  .write.partitionBy($"key").format("text").save(...)

* Sparkには、3つの基本的な変換タイプのみがあります。

  • RDD [T] => RDD [T]
  • RDD [T] => RDD [U]
  • (RDD [T]、RDD [U])=> RDD [W]

ここで、T、U、Wは、アトミックタイプまたは products /tuples(K、V)のいずれかです。その他の操作は、上記の何らかの組み合わせを使用して表現する必要があります。詳細については、 元のRDDペーパー を確認してください。

** http://chat.stackoverflow.com/rooms/91928/discussion-between-zero323-and-jason-lenderman

***関連項目 Scala Spark:コレクションを複数のRDDに分割しますか?

58
zero323

上記の他のポスターのように、RDDを分割する単一のネイティブRDD変換はありませんが、RDDのさまざまな「分割」を効率的にエミュレートできる「多重」操作があります複数回の読み取り:

http://silex.freevariable.com/latest/api/#com.redhat.et.silex.rdd.multiplex.MuxRDDFunctions

ランダム分割に固有のいくつかの方法:

http://silex.freevariable.com/latest/api/#com.redhat.et.silex.sample.split.SplitSampleRDDFunctions

メソッドは、オープンソースsilexプロジェクトから入手できます。

https://github.com/willb/silex

仕組みを説明するブログ投稿:

http://erikerlandson.github.io/blog/2016/02/08/efficient-multiplexing-for-spark-rdds/

def muxPartitions[U :ClassTag](n: Int, f: (Int, Iterator[T]) => Seq[U],
  persist: StorageLevel): Seq[RDD[U]] = {
  val mux = self.mapPartitionsWithIndex { case (id, itr) =>
    Iterator.single(f(id, itr))
  }.persist(persist)
  Vector.tabulate(n) { j => mux.mapPartitions { itr => Iterator.single(itr.next()(j)) } }
}

def flatMuxPartitions[U :ClassTag](n: Int, f: (Int, Iterator[T]) => Seq[TraversableOnce[U]],
  persist: StorageLevel): Seq[RDD[U]] = {
  val mux = self.mapPartitionsWithIndex { case (id, itr) =>
    Iterator.single(f(id, itr))
  }.persist(persist)
  Vector.tabulate(n) { j => mux.mapPartitions { itr => itr.next()(j).toIterator } }
}

他の場所で述べたように、これらの方法は、「遅延」ではなく「熱心に」パーティション全体の結果を計算することで動作するため、メモリと速度のトレードオフを伴います。したがって、これらの方法では、従来の遅延変換では発生しない大きなパーティションでメモリの問題が発生する可能性があります。

7
eje

1つの方法は、カスタムパーティショナーを使用して、フィルター条件に応じてデータをパーティション分割することです。これは、Partitionerを拡張し、RangePartitionerに似たものを実装することで実現できます。

マップパーティションを使用すると、すべてのデータを読み取らずに、パーティションRDDから複数のRDDを構築できます。

val filtered = partitioned.mapPartitions { iter => {

  new Iterator[Int](){
    override def hasNext: Boolean = {
      if(rangeOfPartitionsToKeep.contains(TaskContext.get().partitionId)) {
        false
      } else {
        iter.hasNext
      }
    }

    override def next():Int = iter.next()
  }

フィルタリングされたRDDのパーティションの数は、パーティション化されたRDDの数と同じになるため、合体を使用してこれを減らし、空のパーティションを削除する必要があることに注意してください。

3
Jem Tucker

randomSplit APIコール を使用してRDDを分割すると、RDDの配列が返されます。

5つのRDDを返すには、5つの重み値を渡します。

例えば.

val sourceRDD = val sourceRDD = sc.parallelize(1 to 100, 4)
val seedValue = 5
val splitRDD = sourceRDD.randomSplit(Array(1.0,1.0,1.0,1.0,1.0), seedValue)

splitRDD(1).collect()
res7: Array[Int] = Array(1, 6, 11, 12, 20, 29, 40, 62, 64, 75, 77, 83, 94, 96, 100)
1
Ewan Leith