web-dev-qa-db-fra.com

Comment diviser un RDD en deux ou plusieurs RDD?

Je cherche un moyen de scinder un RDD en deux ou plusieurs RDD. Le plus proche que j'ai vu est Scala Spark: collection divisée en plusieurs RDD? qui est toujours un seul RDD.

Si vous connaissez SAS, quelque chose comme ceci:

data work.split1, work.split2;
    set work.preSplit;

    if (condition1)
        output work.split1
    else if (condition2)
        output work.split2
run;

qui a abouti à deux ensembles de données distincts. Il faudrait tout de suite le persister pour obtenir les résultats escomptés ...

35

Il n'est pas possible de générer plusieurs RDD à partir d'une seule transformation *. Si vous souhaitez fractionner un RDD, vous devez appliquer un filter pour chaque condition de fractionnement. Par exemple:

def even(x): return x % 2 == 0
def odd(x): return not even(x)
rdd = sc.parallelize(range(20))

rdd_odd, rdd_even = (rdd.filter(f) for f in (odd, even))

Si vous avez seulement une condition binaire et que le calcul est coûteux, vous préférerez peut-être quelque chose comme ceci:

kv_rdd = rdd.map(lambda x: (x, odd(x)))
kv_rdd.cache()

rdd_odd = kv_rdd.filter(lambda kv: kv[1]).keys()
rdd_even = kv_rdd.filter(lambda kv: not kv[1]).keys()

Cela ne signifie qu'un seul calcul de prédicat, mais nécessite un transfert supplémentaire de toutes les données.

Il est important de noter que tant qu'un RDD en entrée est correctement mis en cache et qu'il n'y a pas d'hypothèses supplémentaires concernant la distribution des données, il n'y a pas de différence significative en termes de complexité temporelle entre le filtre répété et la boucle for imbriquée avec if-else.

Avec N éléments et M conditions, le nombre d'opérations à effectuer est clairement proportionnel à N fois M. Dans le cas d'une boucle for, il devrait être plus proche de (N + MN)/2 et le filtre répété est exactement NM mais à la fin de la journée ce n’est rien d’autre que O (NM). Vous pouvez voir ma discussion ** avec Jason Lenderman pour en savoir plus sur certains avantages et inconvénients.

Au plus haut niveau, vous devriez considérer deux choses:

  1. Les transformations d'étincelles sont paresseuses jusqu'à ce que vous exécutiez une action que votre RDD n'est pas matérialisée

    Pourquoi est-ce important? Revenons à mon exemple:

    rdd_odd, rdd_even = (rdd.filter(f) for f in (odd, even))
    

    Si plus tard je décide que je n'ai besoin que de rdd_odd alors il n'y a aucune raison de se matérialiser rdd_even.

    Si vous jetez un oeil à votre exemple SAS) pour calculer work.split2 vous devez matérialiser à la fois les données d'entrée et work.split1.

  2. Les RDD fournissent une API déclarative. Lorsque vous utilisez filter ou map, il en va tout à fait jusqu'à Spark détermine le mode d'exécution de cette opération. Tant que les fonctions transmises aux transformations n'ont pas d'effets secondaires cela crée de multiples possibilités pour optimiser tout un pipeline.

En fin de compte, cette affaire n'est pas assez spéciale pour justifier sa propre transformation.

Cette carte avec motif de filtre est réellement utilisée dans un noyau Spark. Voir ma réponse à Comment Sparks RDD.randomSplit divise-t-il réellement le RDD et un partie pertinente de la méthode randomSplit.

Si le seul objectif est de scinder l’entrée, il est possible d’utiliser la clause partitionBy pour DataFrameWriter quel format de sortie texte:

def makePairs(row: T): (String, String) = ???

data
  .map(makePairs).toDF("key", "value")
  .write.partitionBy($"key").format("text").save(...)

* Il n'y a que 3 types de base de transformations dans Spark:

  • RDD [T] => RDD [T]
  • RDD [T] => RDD [U]
  • (RDD [T], RDD [U]) => RDD [W]

où T, U, W peuvent être des types atomiques ou produits /tuples (K, V). Toute autre opération doit être exprimée en utilisant une combinaison de ce qui précède. Vous pouvez vérifier le document original RDD pour plus de détails.

** http://chat.stackoverflow.com/rooms/91928/discussion-between-zero323-and-jason-lenderman

*** Voir aussi Scala Spark: Collection séparée en plusieurs RDD?

58
zero323

Comme les autres affiches mentionnées ci-dessus, il n’existe pas de transformation RDD native unique qui scinde les RDD, mais voici quelques opérations "multiplexées" qui peuvent efficacement émuler une grande variété de "scissions" sur les RDD sans lisant plusieurs fois:

http://silex.freevariable.com/latest/api/#com.redhat.et.silex.rdd.multiplex.MuxRDDFunctions

Quelques méthodes spécifiques au fractionnement aléatoire:

http://silex.freevariable.com/latest/api/#com.redhat.et.silex.sample.split.SplitSampleRDDFunctions

Les méthodes sont disponibles à partir du projet silex open source:

https://github.com/willb/silex

Un article de blog expliquant leur fonctionnement:

http://erikerlandson.github.io/blog/2016/02/08/efficient-multiplexing-for-spark-rdds/

def muxPartitions[U :ClassTag](n: Int, f: (Int, Iterator[T]) => Seq[U],
  persist: StorageLevel): Seq[RDD[U]] = {
  val mux = self.mapPartitionsWithIndex { case (id, itr) =>
    Iterator.single(f(id, itr))
  }.persist(persist)
  Vector.tabulate(n) { j => mux.mapPartitions { itr => Iterator.single(itr.next()(j)) } }
}

def flatMuxPartitions[U :ClassTag](n: Int, f: (Int, Iterator[T]) => Seq[TraversableOnce[U]],
  persist: StorageLevel): Seq[RDD[U]] = {
  val mux = self.mapPartitionsWithIndex { case (id, itr) =>
    Iterator.single(f(id, itr))
  }.persist(persist)
  Vector.tabulate(n) { j => mux.mapPartitions { itr => itr.next()(j).toIterator } }
}

Comme mentionné précédemment, ces méthodes impliquent un compromis de mémoire en termes de vitesse, car elles fonctionnent en calculant des résultats de partition entiers "avec impatience" au lieu de "paresseusement". Par conséquent, il est possible que ces méthodes rencontrent des problèmes de mémoire sur les grandes partitions, contrairement aux transformations paresseuses traditionnelles.

7
eje

Une solution consiste à utiliser un partitionneur personnalisé pour partitionner les données en fonction de votre condition de filtrage. Ceci peut être réalisé en étendant Partitioner et en implémentant quelque chose de similaire à RangePartitioner.

Une partition de carte peut ensuite être utilisée pour construire plusieurs RDD à partir du RDD partitionné sans lire toutes les données.

val filtered = partitioned.mapPartitions { iter => {

  new Iterator[Int](){
    override def hasNext: Boolean = {
      if(rangeOfPartitionsToKeep.contains(TaskContext.get().partitionId)) {
        false
      } else {
        iter.hasNext
      }
    }

    override def next():Int = iter.next()
  }

Sachez simplement que le nombre de partitions dans les RDD filtrés sera le même que le nombre dans le RDD partitionné; une fusion doit donc être utilisée pour réduire ce nombre et supprimer les partitions vides.

3
Jem Tucker

Si vous scindez un RDD à l'aide de = appel d'API randomSplit , vous récupérez un tableau de RDD.

Si vous voulez 5 RDD retournés, passez 5 valeurs de poids.

par exemple.

val sourceRDD = val sourceRDD = sc.parallelize(1 to 100, 4)
val seedValue = 5
val splitRDD = sourceRDD.randomSplit(Array(1.0,1.0,1.0,1.0,1.0), seedValue)

splitRDD(1).collect()
res7: Array[Int] = Array(1, 6, 11, 12, 20, 29, 40, 62, 64, 75, 77, 83, 94, 96, 100)
1
Ewan Leith