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
は実際にどのように機能しますか?
さて、データセットを少し面白くしましょう:
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)
シャッフル後の値の順序は非決定的であることに注意してください。
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))
最後に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)
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
)はデータをシャッフルします。パーティション化が複数の操作間で再利用されない限り、シャッフルされるデータの量は減りません。
RDD
は配布されます。つまり、いくつかのパーツに分割されます。このパーティションはそれぞれ異なるマシン上にある可能性があります。引数numPartitions
を持つハッシュパーティショナーは、次の方法でペア(key, value)
を配置するパーティションを選択します。
numPartitions
パーティションを作成します。Hash(key) % numPartitions
のパーティションに(key, value)
を配置しますHashPartitioner.getPartition
メソッドは、引数としてkeyを取り、キーが属するパーティションのindexを返します。パーティショナーは有効なインデックスが何であるかを知る必要があるため、正しい範囲の数値を返します。パーティションの数は、numPartitions
コンストラクター引数によって指定されます。
実装はおよそkey.hashCode() % numPartitions
を返します。詳細については、 Partitioner.scala を参照してください。