web-dev-qa-db-fra.com

Pourquoi tant de tâches dans mon métier d'étincelle? Obtenir 200 tâches par défaut

J'ai un travail d'étincelle qui prend un fichier avec 8 enregistrements de hdfs, effectue une agrégation simple et l'enregistre de nouveau sur hdfs. Je remarque qu'il y a des centaines de tâches lorsque je fais cela. 

Je ne sais pas non plus pourquoi il y a plusieurs emplois pour cela. Je pensais qu'un travail ressemblait plus à une action. Je peux spéculer sur les raisons - mais je croyais comprendre qu'à l'intérieur de ce code, il devrait s'agir d'un travail et qu'il devrait être divisé en plusieurs étapes. Pourquoi ne pas le diviser simplement en étapes, comment cela se fait-il en emplois?

En ce qui concerne plus de 200 tâches, étant donné que la quantité de données et le nombre de nœuds sont minuscules, il n’est pas logique de penser qu’il existe environ 25 tâches pour chaque ligne de données s’il n’ya qu’une agrégation et deux filtres. Pourquoi n’aurait-il pas juste une tâche par partition par opération atomique?

Voici le code de scala pertinent - 

import org.Apache.spark.sql._
import org.Apache.spark.sql.types._
import org.Apache.spark.SparkContext._
import org.Apache.spark.SparkConf

object TestProj {object TestProj {
  def main(args: Array[String]) {

    /* set the application name in the SparkConf object */
    val appConf = new SparkConf().setAppName("Test Proj")

    /* env settings that I don't need to set in REPL*/
    val sc = new SparkContext(appConf)
    val sqlContext = new SQLContext(sc)
    import sqlContext.implicits._

    val rdd1 = sc.textFile("hdfs://node002:8020/flat_files/miscellaneous/ex.txt")

     /*the below rdd will have schema defined in Record class*/
     val rddCase =  sc.textFile("hdfs://node002:8020/flat_files/miscellaneous/ex.txt")
      .map(x=>x.split(" "))    //file record into array of strings based spaces
      .map(x=>Record(
        x(0).toInt,
        x(1).asInstanceOf[String],
        x(2).asInstanceOf[String],
        x(3).toInt))


    /* the below dataframe groups on first letter of first name and counts it*/
    val aggDF = rddCase.toDF()
      .groupBy($"firstName".substr(1,1).alias("firstLetter"))
      .count
      .orderBy($"firstLetter")

    /* save to hdfs*/ 
 aggDF.write.format("parquet").mode("append").save("/raw/miscellaneous/ex_out_agg")

  }

    case class Record(id: Int
      , firstName: String
      , lastName: String
      , quantity:Int)

}

Ci-dessous, la capture d'écran après avoir cliqué sur l'application  enter image description here

Ci-dessous se trouvent les étapes montrées lors de la visualisation du "travail" spécifique de l'identifiant 0  enter image description here

Ci-dessous, la première partie de l'écran lorsque vous cliquez sur la scène avec plus de 200 tâches.

 enter image description here

C'est la deuxième partie de l'écran à l'intérieur de la scène  enter image description here

Ci-dessous se trouve après avoir cliqué sur l'onglet "exécuteurs"  enter image description here

Comme demandé, voici les étapes pour Job ID 1

 enter image description here

Voici les détails pour l'étape dans le travail ID 1 avec 200 tâches

 enter image description here

16
big_mike_boiii

Ceci est une question classique Spark. 

Les deux tâches utilisées pour la lecture (Stage Id 0 dans la deuxième figure) correspondent au paramètre defaultMinPartitions qui est défini sur 2. Vous pouvez obtenir ce paramètre en lisant la valeur dans le REPL sc.defaultMinPartitions. Il devrait également être visible dans l'interface utilisateur de Spark sous l'onglet "Environnement". 

Vous pouvez jeter un coup d'oeil au code de github pour voir que c'est exactement ce qui se passe. Si vous souhaitez utiliser plus de partitions à la lecture, ajoutez-le simplement en tant que paramètre, par exemple, sc.textFile("a.txt", 20).

Maintenant, la partie intéressante vient des 200 partitions qui viennent sur la deuxième étape (Étape Id 1 dans la deuxième figure). Eh bien, chaque fois qu’il y aura un shuffle, Spark devra décider du nombre de partitions que le RDD shuffle aura. Comme vous pouvez l’imaginer, la valeur par défaut est 200. 

Vous pouvez changer cela en utilisant:

sqlContext.setConf("spark.sql.shuffle.partitions", "4”)

Si vous exécutez votre code avec cette configuration, vous verrez que les 200 partitions ne seront plus présentes. Comment définir ce paramètre est une sorte d'art. Peut-être choisir 2x le nombre de cœurs que vous avez (ou autre). 

Je pense que Spark 2.0 est capable de déduire automatiquement le meilleur nombre de partitions pour les RDD shuffle. Dans l'attente de ça!

Enfin, le nombre de travaux que vous obtenez dépend du nombre d'actions RDD générées par le code optimisé du Dataframe. Si vous lisez les spécifications Spark, il est indiqué que chaque action RDD déclenchera un travail. Lorsque votre action implique une Dataframe ou SparkSQL, l’optimiseur Catalyst établira un plan d’exécution et générera du code basé sur RDD pour l’exécuter. Il est difficile de dire exactement pourquoi il utilise deux actions dans votre cas. Vous devrez peut-être consulter le plan de requête optimisé pour voir exactement ce que vous faites. 

25
marios

J'ai un problème similaire. Mais dans mon scénario, la collection que je parallélise contient moins d'éléments que le nombre de tâches planifiées par Spark (ce qui provoque parfois un comportement bizarre de spark). En utilisant le numéro de partition forcée, j'ai pu résoudre ce problème.

C'était quelque chose comme ça:

collection = range(10) # In the real scenario it was a complex collection
sc.parallelize(collection).map(lambda e: e + 1) # also a more complex operation in the real scenario

Ensuite, j'ai vu dans le journal Spark:

INFO YarnClusterScheduler: Adding task set 0.0 with 512 tasks
1
Enrique Altuna