web-dev-qa-db-fra.com

Apache Spark - foreach Vs foreachPartitions Quand utiliser quoi?

Je voudrais savoir si le foreachPartitions entraînera de meilleures performances, en raison d'un niveau de parallélisme plus élevé, par rapport à la méthode foreach compte tenu du cas dans lequel je traverse un RDD afin d'effectuer quelques sommes dans une variable d'accumulateur.

34

foreach et foreachPartitions sont des actions.

foreach (fonction): Unité

Une fonction générique pour appeler des opérations avec des effets secondaires. Pour chaque élément du RDD, il appelle la fonction passée. Ceci est généralement utilisé pour manipuler des accumulateurs ou écrire dans des magasins externes.

Remarque: la modification de variables autres que les accumulateurs en dehors de la foreach() peut entraîner un comportement non défini. Voir Comprendre les fermetures pour plus de détails.

exemple :

scala> val accum = sc.longAccumulator("My Accumulator")
accum: org.Apache.spark.util.LongAccumulator = LongAccumulator(id: 0, name: Some(My Accumulator), value: 0)

scala> sc.parallelize(Array(1, 2, 3, 4)).foreach(x => accum.add(x))
...
10/09/29 18:41:08 INFO SparkContext: Tasks finished in 0.317106 s

scala> accum.value
res2: Long = 10

foreachPartition (fonction): Unité

Similaire à foreach(), mais au lieu d'appeler une fonction pour chaque élément, il l'appelle pour chaque partition. La fonction doit pouvoir accepter un itérateur. C'est plus efficace que foreach() car cela réduit le nombre d'appels de fonction (tout comme mapPartitions ()).

Utilisation d'exemples foreachPartition:


  • Exemple 1: pour chaque partition, une connexion à la base de données (à l'intérieur de chaque bloc de partition) que vous souhaitez utiliser, voici un exemple d'utilisation de la façon dont cela peut être fait à l'aide de scala.
/** 
 * Insérer dans la base de données à l'aide de chaque partition. 
 * 
 * @Param sqlDatabaseConnectionString 
 * @Param sqlTableName 
 */
 def insertToTable (sqlDatabaseConnectionString: String, sqlTableName: String): Unit = {
 
 // numPartitions = nombre de connexions DB simultanées que vous pouvez prévoir de donner 
 
 datframe.repartition (numofpartitionsyouwant) 
 
 val tableHeader: String = dataFrame.columns.mkString (",") 
 dataFrame.foreachPartition {partition => 
 // Remarque: pour chaque partition, une connexion (le meilleur moyen consiste à utiliser des pools de connexions) 
 Val sqlExecutorConnection: Connection = DriverManager.getConnection (sqlDatabaseConnectionString) 
 // Taille de lot de 1000 est utilisé car certaines bases de données ne peuvent pas utiliser une taille de lot supérieure à 1000 pour ex: Azure sql 
 partition.grouped (1000) .foreach {
 group => 
 val insertString: sca la.collection.mutable.StringBuilder = new scala.collection.mutable.StringBuilder () 
 group.foreach {
 record => insertString.append ("('" + record.mkString (", ") +" '), ") 
} 
 
 sqlExecutorConnection.createStatement () 
 .executeUpdate (f" INSERT INTO [$ sqlTableName] ($ tableHeader) VALEURS "
 + InsertString.stripSuffix (", ")) 
} 
 
 
 SqlExecutorConnection.close () // ferme la connexion afin que les connexions ne s'épuisent pas. 
} 
} 
  • Exemple2:

Utilisation de foreachPartition avec sparkstreaming (dstreams) et kafka producteur

dstream.foreachRDD { rdd =>
  rdd.foreachPartition { partitionOfRecords =>
// only once per partition You can safely share a thread-safe Kafka //producer instance.
    val producer = createKafkaProducer()
    partitionOfRecords.foreach { message =>
      producer.send(message)
    }
    producer.close()
  }
}

Remarque: Si vous voulez éviter cette façon de créer un producteur une fois par partition, mieux vaut diffuser le producteur en utilisant sparkContext.broadcast Puisque Kafka producteur est asynchrone et met en mémoire tampon les données avant l'envoi.


Accumulator samples snippet to play around with it ... through which you can test the performance

 test ("Foreach - Spark") {
 import spark.implicits ._ 
 var accum = sc.longAccumulator 
 sc.parallelize (Seq (1,2 , 3)). Foreach (x => accum.add (x)) 
 Assert (accum.value == 6L) 
} 
 
 Test (" Foreach partition - Spark ") {
 Import spark.implicits ._ 
 Var accum = sc.longAccumulator 
 Sc.parallelize (Seq (1,2,3)). ForeachPartition ( x => x.foreach (accum.add (_))) 
 assert (accum.value == 6L) 
} 

Conclusion :

foreachPartition opérations sur les partitions donc évidemment ce serait mieux Edge que foreach

Règle générale:

foreachPartition doit être utilisé lorsque vous accédez à des ressources coûteuses telles que les connexions à la base de données ou kafka producteur, etc. qui initialiserait une par partition plutôt qu'une par élément (foreach). En ce qui concerne les accumulateurs, vous pouvez mesurer les performances par les méthodes de test ci-dessus, qui devraient également fonctionner plus rapidement dans le cas des accumulateurs.

Voir aussi map vs mappartitions qui a un concept similaire mais ce sont des transformations.

22
Ram Ghadiyaram

foreach exécute automatiquement la boucle sur de nombreux nœuds.

Cependant, vous souhaitez parfois effectuer certaines opérations sur chaque nœud. Par exemple, établissez une connexion à la base de données. Vous ne pouvez pas simplement établir une connexion et la passer dans la fonction foreach: la connexion n'est établie que sur un seul nœud.

Ainsi, avec foreachPartition, vous pouvez établir une connexion à la base de données sur chaque nœud avant d'exécuter la boucle.

21
Bin Wang

Il n'y a vraiment pas beaucoup de différence entre foreach et foreachPartitions. Sous les couvertures, tout ce que foreach fait est d'appeler le foreach de l'itérateur en utilisant la fonction fournie. foreachPartition vous donne juste la possibilité de faire quelque chose en dehors du bouclage de l'itérateur, généralement quelque chose de cher comme faire tourner une connexion à une base de données ou quelque chose dans ce sens. Donc, si vous n'avez rien qui puisse être fait une fois pour l'itérateur de chaque nœud et réutilisé tout au long, je suggère d'utiliser foreach pour une clarté améliorée et une complexité réduite.

16
Justin Pihony

foreachPartition ne signifie pas qu'il s'agit d'une activité par nœud, mais plutôt qu'il est exécuté pour chaque partition et il est possible que vous ayez un grand nombre de partitions par rapport au nombre de nœuds dans ce cas, vos performances peuvent être dégradées. Si vous avez l'intention de faire une activité au niveau du nœud, la solution expliquée ici peut être utile bien qu'elle ne soit pas testée par moi

4
deenbandhu

foreachPartition n'est utile que lorsque vous parcourez des données que vous agrégez par partition.

Un bon exemple est le traitement des flux de clics par utilisateur. Vous souhaitez vider votre cache de calcul chaque fois que vous avez terminé le flux d'événements d'un utilisateur, mais le conserver entre les enregistrements du même utilisateur afin de calculer certaines informations sur le comportement de l'utilisateur.

3
Oren