web-dev-qa-db-fra.com

Spark la tâche finale prend 100 fois plus de temps que la première 199, comment améliorer

Je constate des problèmes de performances lors de l'exécution de requêtes à l'aide de cadres de données. J'ai vu dans mes recherches que des tâches de longue durée peuvent finalement être le signe que les données ne sont pas perturbées de manière optimale, mais que je n'ai pas trouvé de processus détaillé pour résoudre ce problème.

Je commence par charger deux tables en tant que trames de données, puis je joins ces tables sur un champ. J'ai essayé d'ajouter distribuer par (repartition) et trier par afin d'améliorer les performances, mais je vois toujours cette tâche finale longue et unique. Voici une version simple de mon code, notez que les requêtes un et deux ne sont pas aussi simples et utilisez des FDU pour calculer certaines valeurs.

J'ai essayé plusieurs paramètres différents pour spark.sql.shuffle. J'ai essayé 100, mais il a échoué (je n'ai pas vraiment débogué cela pour être honnête). J'ai essayé 300, 4000 et 8000. Les performances diminuaient à chaque augmentation. Je sélectionne un seul jour de données, où chaque fichier est d'une heure.

val df1 = sqlContext.sql("Select * from Table1")
val df2 = sqlContext.sql("Select * from Table2")

val distributeDf1 = df1
    .repartition(df1("userId"))
    .sortWithinPartitions(df1("userId"))

val distributeDf2 = df2
    .repartition(df2("userId"))
    .sortWithinPartitions(df2("userId"))

distributeDf1.registerTempTable("df1")
distributeDf2.registerTempTable("df2")

val df3 = sqlContext
  .sql("""
    Select 
      df1.* 
    from 
      df1 
    left outer join df2 on 
      df1.userId = df2.userId""")

Comme il semble que le partitionnement par userId ne soit pas idéal, je pourrais plutôt partitionner par l'horodatage. Si je fais cela, dois-je simplement faire la date + heure? Si j'ai moins de 200 combos uniques pour cela, aurai-je des exécuteurs vides?

27

Vous avez clairement un problème avec un énorme biais de données droit . Jetons un coup d'œil aux statistiques que vous avez fournies :

df1 = [mean=4.989209978967438, stddev=2255.654165352454, count=2400088] 
df2 = [mean=1.0, stddev=0.0, count=18408194]

Avec une moyenne d'environ 5 et un écart type supérieur à 2000, vous obtenez une longue queue .

Étant donné que certaines clés sont beaucoup plus fréquentes que d'autres après la répartition, certains exécuteurs auront beaucoup plus de travail à faire que les autres.

De plus, votre description suggère que le problème peut être dû à une ou plusieurs clés qui hachent la même partition.

Alors, identifions d'abord les valeurs aberrantes (pseudocode):

val mean = 4.989209978967438 
val sd = 2255.654165352454

val df1 = sqlContext.sql("Select * from Table1")
val counts = df.groupBy("userId").count.cache

val frequent = counts
  .where($"count" > mean + 2 * sd)  // Adjust threshold based on actual dist.
  .alias("frequent")
  .join(df1, Seq("userId"))

et le reste:

val infrequent = counts
  .where($"count" <= mean + 2 * sd)
  .alias("infrequent")
  .join(df1, Seq("userId"))

Est-ce vraiment quelque chose à prévoir? Sinon, essayez d'identifier la source du problème en amont.

Si cela est prévu, vous pouvez essayer :

  • diffusion d'une table plus petite:

    val df2 = sqlContext.sql("Select * from Table2")
    df2.join(broadcast(df1), Seq("userId"), "rightouter")
    
  • le fractionnement, l'unification (union) et la diffusion ne sont fréquents:

    df2.join(broadcast(frequent), Seq("userId"), "rightouter")
      .union(df2.join(infrequent, Seq("userId"), "rightouter"))
    
  • salage userId avec quelques données aléatoires

mais vous ne devriez pas :

  • repartitionner toutes les données et trier localement (bien que le tri local seul ne devrait pas être un problème)
  • effectuer des jointures de hachage standard sur les données complètes.
19
zero323