HashPartitionerはどのように機能しますか?


82

のドキュメントを読みましたHashPartitioner。残念ながら、API呼び出し以外は何も説明されていませんでした。私はHashPartitioner、キーのハッシュに基づいて分散セットを分割することを前提としています。たとえば、私のデータが次のような場合

(1,1), (1,2), (1,3), (2,1), (2,2), (2,3)

したがって、パーティショナーはこれを異なるパーティションに配置し、同じキーが同じパーティションに分類されます。しかし、コンストラクター引数の意味がわかりません

new HashPartitoner(numPartitions) //What does numPartitions do?

上記のデータセットの場合、私が行った場合、結果はどのように異なりますか

new HashPartitoner(1)
new HashPartitoner(2)
new HashPartitoner(10)

では、HashPartitioner実際にはどのように機能しますか?

回答:


162

さて、あなたのデータセットをわずかにもっと面白くしましょう:

val rdd = sc.parallelize(for {
    x <- 1 to 3
    y <- 1 to 2
} yield (x, None), 8)

6つの要素があります。

rdd.count
Long = 6

パーティショナーなし:

rdd.partitioner
Option[org.apache.spark.Partitioner] = None

および8つのパーティション:

rdd.partitions.length
Int = 8

次に、パーティションごとの要素数をカウントする小さなヘルパーを定義しましょう。

import org.apache.spark.rdd.RDD

def countByPartition(rdd: RDD[(Int, None.type)]) = {
    rdd.mapPartitions(iter => Iterator(iter.length))
}

パーティショナーがないため、データセットはパーティション間で均一に分散されます(Sparkのデフォルトのパーティショニングスキーム):

countByPartition(rdd).collect()
Array[Int] = Array(0, 1, 1, 1, 0, 1, 1, 1)

初期配布

次に、データセットを再パーティション化します。

import org.apache.spark.HashPartitioner
val rddOneP = rdd.partitionBy(new HashPartitioner(1))

に渡されるパラメーターHashPartitionerはパーティションの数を定義するため、1つのパーティションが必要です。

rddOneP.partitions.length
Int = 1

パーティションが1つしかないため、すべての要素が含まれています。

countByPartition(rddOneP).collect
Array[Int] = Array(6)

hash-partitioner-1

シャッフル後の値の順序は非決定論的であることに注意してください。

使用する場合も同じように HashPartitioner(2)

val rddTwoP = rdd.partitionBy(new HashPartitioner(2))

2つのパーティションを取得します。

rddTwoP.partitions.length
Int = 2

rddキーデータによってパーティション化されているため、均一に分散されなくなります。

countByPartition(rddTwoP).collect()
Array[Int] = Array(2, 4)

3つのキーがあり、hashCodemodの値が2つしかないため、numPartitionsここでは予期しないことは何もありません。

(1 to 3).map((k: Int) => (k, k.hashCode, k.hashCode % 2))
scala.collection.immutable.IndexedSeq[(Int, Int, Int)] = Vector((1,1,1), (2,2,0), (3,3,1))

上記を確認するだけです:

rddTwoP.mapPartitions(iter => Iterator(iter.map(_._1).toSet)).collect()
Array[scala.collection.immutable.Set[Int]] = Array(Set(2), Set(1, 3))

hash-partitioner-2

最後に、HashPartitioner(7)7つのパーティションを取得します。3つは空ではなく、それぞれ2つの要素があります。

val rddSevenP = rdd.partitionBy(new HashPartitioner(7))
rddSevenP.partitions.length
Int = 7
countByPartition(rddTenP).collect()
Array[Int] = Array(0, 2, 2, 2, 0, 0, 0)

hash-partitioner-7

まとめと注意事項

  • HashPartitioner パーティションの数を定義する単一の引数を取ります
  • 値はhash、キーを使用してパーティションに割り当てられます。hash機能は言語によって異なる場合があります(Scala RDDはhashCodeDataSetsMurmurHash 3、PySparkを使用する場合がありますportable_hash)。

    キーが小さな整数であるこのような単純なケースでhashは、それが単位元(i = hash(i))であると見なすことができます。

    Scala APIはnonNegativeMod、計算されたハッシュに基づいてパーティションを決定するために使用します。

  • キーの配布が均一でない場合、クラスターの一部がアイドル状態になる可能性があります

  • キーはハッシュ可能である必要があります。PySparkのreduceByKeyのキーとしてのAリストに対する私の回答を確認して、PySpark固有の問題について読むことができます。別の考えられる問題は、HashPartitionerのドキュメントで強調されています

    Java配列には、内容ではなく配列のIDに基づくhashCodeがあるため、HashPartitionerを使用してRDD [Array [ ]]またはRDD [(Array [ ]、_)]を分割しようとすると、予期しない結果または誤った結果が生成されます。

  • Python 3では、ハッシュが一貫していることを確認する必要があります。参照例外を何:文字列のハッシュのランダム性がPYTHONHASHSEEDを経由して無効にする必要がありpysparkに意味ですか?

  • ハッシュパーティショナーは単射でも全射でもありません。1つのパーティションに複数のキーを割り当てることができ、一部のパーティションは空のままにすることができます。

  • 現在、ハッシュベースのメソッドは、REPLで定義されたケースクラス(Apache Sparkのケースクラスの同等性)と組み合わせると、Scalaでは機能しないことに注意してください。

  • HashPartitioner(またはその他のPartitioner)データをシャッフルします。パーティショニングが複数の操作間で再利用されない限り、シャッフルされるデータの量が減ることはありません。


素晴らしい書き込み、ありがとう。しかし、私はあなたが持っているイメージに気づい(1, None)hash(2) % PPがパーティションです。そうではないhash(1) % Pですか?
javamonkey 7918

Spark 2.2を使用していますがpartitionBy、rddにAPIがありません。dataframe.writeの下にpartitionByがありますが、Partitionerを引数として取りません。
白波

恐ろしい返信...シャッフルはパーティショナーに基づいています。優れたパーティショナーはシャッフルされたデータ量を減らすことができます。
レオン

1
素晴らしい👌答え。インターネットでしっかりとした答えが得られない質問があります。df.repartition(n)を使用する場合、つまり、キーとして列を指定しない場合、ハッシュされるものがないため、ハッシュ関数は内部でどのように機能しますか?
DSK

@dsk、キーを指定しない場合、再パーティションはRoundRobinPartitioningを使用すると思います。ここでいくつかの議論。
MikeSouder20年

6

RDD配布されているということは、いくつかの部分に分割されていることを意味します。この各パーティションは、異なるマシン上にある可能性があります。引数付きのハッシュパーティショナーは、次の方法でnumPartitionsペアを配置するパーティションを選択し(key, value)ます。

  1. 正確にnumPartitionsパーティションを作成します。
  2. 場所(key, value)番号とパーティション内Hash(key) % numPartitions

3

ザ・ HashPartitioner.getPartitionメソッドは、引数としてキーを受け取り、そのキーが属するパーティションのインデックスを返します。パーティショナーは有効なインデックスが何であるかを知っている必要があるため、正しい範囲の数値を返します。パーティションの数は、numPartitionsコンストラクター引数を介して指定されます。

実装は大まかに戻​​りますkey.hashCode() % numPartitions。詳細については、Partitioner.scalaを参照してください。

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.