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 ...
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:
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
.
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:
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?
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.
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.
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)