web-dev-qa-db-fra.com

Comment définir un partitionneur personnalisé pour Spark RDD de partition de taille égale où chaque partition a un nombre égal d'éléments?

Je suis nouveau chez Spark. J'ai un grand ensemble de données d'éléments [RDD] et je veux le diviser en deux partitions de taille exactement égale en maintenant l'ordre des éléments. J'ai essayé d'utiliser RangePartitioner comme

var data = partitionedFile.partitionBy(new RangePartitioner(2, partitionedFile))

Cela ne donne pas un résultat satisfaisant car il divise grossièrement mais pas exactement la même taille en maintenant l'ordre des éléments. Par exemple, s'il y a 64 éléments, nous utilisons Rangepartitioner, puis il se divise en 31 éléments et 33 éléments.

J'ai besoin d'un partitionneur tel que j'obtienne exactement les premiers 32 éléments dans une moitié et l'autre moitié contient le deuxième ensemble de 32 éléments. Pourriez-vous s'il vous plaît m'aider en suggérant comment utiliser un partitionneur personnalisé de telle sorte que j'obtienne deux moitiés de taille égale, en maintenant l'ordre des éléments?

26
yh18190

Partitioners fonctionne en affectant une clé à une partition. Vous auriez besoin d'une connaissance préalable de la distribution des clés, ou regardez toutes les clés, pour faire un tel partitionneur. C'est pourquoi Spark ne vous en fournit pas.

En général, vous n'avez pas besoin d'un tel partitionneur. En fait, je ne peux pas proposer un cas d'utilisation où j'aurais besoin de partitions de taille égale. Et si le nombre d'éléments est impair?

Quoi qu'il en soit, disons que vous avez un RDD composé de Ints séquentiels, et vous savez combien au total. Ensuite, vous pouvez écrire un Partitioner personnalisé comme ceci:

class ExactPartitioner[V](
    partitions: Int,
    elements: Int)
  extends Partitioner {

  def getPartition(key: Any): Int = {
    val k = key.asInstanceOf[Int]
    // `k` is assumed to go continuously from 0 to elements-1.
    return k * partitions / elements
  }
}
24
Daniel Darabos

Cette réponse est inspirée de Daniel, mais fournit une implémentation complète (en utilisant pimp mon modèle de bibliothèque) avec un exemple pour les besoins de copie et de collage des gens :)

import RDDConversions._

trait RDDWrapper[T] {
  def rdd: RDD[T]
}

// TODO View bounds are deprecated, should use context bounds
// Might need to change ClassManifest for ClassTag in spark 1.0.0
case class RichPairRDD[K <% Ordered[K] : ClassManifest, V: ClassManifest](
  rdd: RDD[(K, V)]) extends RDDWrapper[(K, V)] {
  // Here we use a single Long to try to ensure the sort is balanced, 
  // but for really large dataset, we may want to consider
  // using a Tuple of many Longs or even a GUID
  def sortByKeyGrouped(numPartitions: Int): RDD[(K, V)] =
    rdd.map(kv => ((kv._1, Random.nextLong()), kv._2)).sortByKey()
    .grouped(numPartitions).map(t => (t._1._1, t._2))
}

case class RichRDD[T: ClassManifest](rdd: RDD[T]) extends RDDWrapper[T] {
  def grouped(size: Int): RDD[T] = {
    // TODO Version where withIndex is cached
    val withIndex = rdd.mapPartitions(_.zipWithIndex)

    val startValues =
      withIndex.mapPartitionsWithIndex((i, iter) => 
        Iterator((i, iter.toIterable.last))).toArray().toList
      .sortBy(_._1).map(_._2._2.toLong).scan(-1L)(_ + _).map(_ + 1L)

    withIndex.mapPartitionsWithIndex((i, iter) => iter.map {
      case (value, index) => (startValues(i) + index.toLong, value)
    })
    .partitionBy(new Partitioner {
      def numPartitions: Int = size
      def getPartition(key: Any): Int = 
        (key.asInstanceOf[Long] * numPartitions.toLong / startValues.last).toInt
    })
    .map(_._2)
  }
}

Ensuite, dans un autre fichier, nous avons

// TODO modify above to be implicit class, rather than have implicit conversions
object RDDConversions {
  implicit def toRichRDD[T: ClassManifest](rdd: RDD[T]): RichRDD[T] = 
    new RichRDD[T](rdd)
  implicit def toRichPairRDD[K <% Ordered[K] : ClassManifest, V: ClassManifest](
    rdd: RDD[(K, V)]): RichPairRDD[K, V] = RichPairRDD(rdd)
  implicit def toRDD[T](rdd: RDDWrapper[T]): RDD[T] = rdd.rdd
}

Ensuite, pour votre cas d'utilisation, vous voulez juste (en supposant qu'il est déjà trié)

import RDDConversions._

yourRdd.grouped(2)

Avertissement: non testé, un peu écrit juste dans la réponse SO

12
samthebest