web-dev-qa-db-fra.com

Comment fonctionne HashPartitioner?

J'ai lu sur la documentation de HashPartitioner . Malheureusement, rien n'a été expliqué, sauf pour les appels d'API. Je suppose que HashPartitioner partitionne le jeu distribué en fonction du hachage des clés. Par exemple si mes données sont comme

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

Donc, le partitionneur mettrait cela dans différentes partitions avec les mêmes clés tombant dans la même partition. Cependant, je ne comprends pas la signification de l'argument du constructeur

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

Pour le jeu de données ci-dessus, en quoi les résultats seraient-ils différents si je le faisais?

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

Alors, comment fonctionne HashPartitioner en réalité?

74
Sohaib

Eh bien, rendons votre jeu de données légèrement plus intéressant:

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

Nous avons six éléments:

rdd.count
Long = 6

pas de partitionneur:

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

et huit partitions:

rdd.partitions.length
Int = 8

Définissons maintenant un petit assistant pour compter le nombre d'éléments par partition:

import org.Apache.spark.rdd.RDD

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

Puisque nous n'avons pas de partitionneur, notre jeu de données est distribué uniformément entre les partitions ( Schéma de partitionnement par défaut dans Spark ):

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

inital-distribution

Partons maintenant notre jeu de données:

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

Puisque le paramètre passé à HashPartitioner définit le nombre de partitions que nous avons prévu, une partition:

rddOneP.partitions.length
Int = 1

Comme nous n'avons qu'une seule partition, elle contient tous les éléments:

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

hash-partitioner-1

Notez que l'ordre des valeurs après le remaniement n'est pas déterministe.

De la même manière si nous utilisons HashPartitioner(2)

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

nous aurons 2 partitions:

rddTwoP.partitions.length
Int = 2

Puisque rdd est partitionné par clé, les données ne seront plus distribuées uniformément:

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

Parce qu'avec avec trois clés et seulement deux valeurs différentes de hashCode mod numPartitions il n'y a rien d'inattendu ici:

(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))

Juste pour confirmer ce qui précède:

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

hash-partitioner-2

Enfin, avec HashPartitioner(7), nous obtenons sept partitions, trois non vides avec 2 éléments chacune:

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

Résumé et notes

  • HashPartitioner prend un seul argument qui définit le nombre de partitions
  • les valeurs sont assignées aux partitions en utilisant hash of keys. La fonction hash peut varier en fonction de la langue (Scala RDD peut utiliser hashCode, DataSets utiliser MurmurHash 3, PySpark, portable_hash ).

    Dans un cas simple comme celui-ci, où key est un petit entier, vous pouvez supposer que hash est une identité (i = hash(i)).

    L’API Scala utilise nonNegativeMod pour déterminer la partition en fonction du hachage calculé,

  • si la distribution des clés n’est pas uniforme, vous pouvez vous retrouver dans des situations où une partie de votre cluster est inactive

  • les clés doivent être hashable. Vous pouvez consulter ma réponse pour ne liste en tant que clé de la clé reduction de PySpark pour en savoir plus sur les problèmes spécifiques à PySpark. Un autre problème possible est mis en évidence par documentation HashPartitioner :

    Les tableaux Java ont des hashCodes basés sur les identités des tableaux plutôt que sur leur contenu. Tenter de partitionner un RDD [Array []] ou RDD [(Array [], _)] à l'aide d'un HashPartitioner produira un résultat inattendu ou incorrect.

  • Dans Python 3, vous devez vous assurer que le hachage est cohérent. Voir Qu'est-ce que Exception: le caractère aléatoire d'une chaîne de hachage doit être désactivé via PYTHONHASHSEED dans pyspark?

  • Le partitionneur de hachage n'est ni injectif ni surjectif. Plusieurs clés peuvent être attribuées à une seule partition et certaines partitions peuvent rester vides.

  • Veuillez noter que les méthodes actuellement basées sur le hachage ne fonctionnent pas dans Scala lorsqu'elles sont combinées avec REPL classes de cas définies ( égalité de classe de cas dans Apache Spark) ).

  • HashPartitioner (ou tout autre Partitioner) mélange les données. À moins que le partitionnement ne soit réutilisé entre plusieurs opérations, cela ne réduit pas la quantité de données à mélanger.

134
zero323

RDD est distribué, cela signifie qu'il est divisé en plusieurs parties. Chacune de ces partitions est potentiellement sur une machine différente. Le partitionneur de hachage avec l'argument numPartitions choisit sur quelle partition placer la paire (key, value) De la manière suivante:

  1. Crée exactement numPartitions partitions.
  2. Place (key, value) Dans la partition portant le numéro Hash(key) % numPartitions
5
abalcerek

La méthode HashPartitioner.getPartition Prend comme argument une clé et renvoie l'index de la partition à laquelle appartient la clé. Le partitionneur doit savoir quels sont les index valides. Il renvoie donc les nombres dans la plage correcte. Le nombre de partitions est spécifié via l'argument de constructeur numPartitions.

L'implémentation retourne approximativement key.hashCode() % numPartitions. Voir Partitioner.scala pour plus de détails.

3
Daniel Darabos