web-dev-qa-db-ja.com

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

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は実際にどのように機能しますか?

74
Sohaib

さて、データセットを少し面白くしましょう:

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)

inital-distribution

次に、データセットを再分割します。

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つのキーがあり、hashCode modの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)を使用して、それぞれ2つの要素を持つ3つの空でない7つのパーティションを取得します。

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はhashCodeを使用でき、DataSetsはMurmurHash 3、PySpark、 portable_hash を使用できます)。

    Keyが小さな整数であるこのような単純なケースでは、hashがアイデンティティ(i = hash(i))であると想定できます。

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

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

  • キーはハッシュ可能でなければなりません。 PySparkのreduceByKeyのキーとしてのリスト についての私の答えを確認して、PySpark固有の問題について読むことができます。別の考えられる問題は HashPartitioner documentation で強調されています:

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

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

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

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

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

134
zero323

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

  1. 正確にnumPartitionsパーティションを作成します。
  2. 番号Hash(key) % numPartitionsのパーティションに(key, value)を配置します
5
abalcerek

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

実装はおよそkey.hashCode() % numPartitionsを返します。詳細については、 Partitioner.scala を参照してください。

3
Daniel Darabos