J'ai un travail spark où je fais une jointure externe entre deux trames de données. La taille de la première trame de données est de 260 Go, le format de fichier est des fichiers texte qui sont divisés en 2200 fichiers et la taille du second trame de données est de 2 Go. Ensuite, l'écriture de la sortie de trame de données qui est d'environ 260 Go dans S3 prend très longtemps est plus de 2 heures après que j'ai annulé car j'ai été fortement modifié sur EMR.
Voici mes informations de cluster.
emr-5.9.0
Master: m3.2xlarge
Core: r4.16xlarge 10 machines (each machine has 64 vCore, 488 GiB memory,EBS Storage:100 GiB)
Ceci est ma configuration de cluster que je mets
capacity-scheduler yarn.scheduler.capacity.resource-calculator :org.Apache.hadoop.yarn.util.resource.DominantResourceCalculator
emrfs-site fs.s3.maxConnections: 200
spark maximizeResourceAllocation: true
spark-defaults spark.dynamicAllocation.enabled: true
J'ai essayé de régler manuellement le composant de mémoire comme ci-dessous et les performances étaient meilleures mais la même chose prenait encore très longtemps
--num-executors 60 - conf spark.yarn.executor.memoryOverhead = 9216 --executor-memory 72G --conf spark.yarn.driver.memoryOverhead = 3072 --driver-memory 26G --executor-cores 10 - driver-cores 3 --conf spark.default.parallelism = 1200
Je n'utilise pas la partition par défaut pour enregistrer les données dans S3.
Ajout de tous les détails sur les travaux et le plan de requête afin qu'il soit facile à comprendre.
La vraie raison est la partition. Et cela prend la plupart du temps. Parce que j'ai des fichiers 2K, donc si j'utilise une partition comme 200, les fichiers de sortie sont livrés en lakhs, puis chargés à nouveau en spark n'est pas une bonne histoire.
Dans l'image ci-dessous, je ne sais pas pourquoi le tri est à nouveau appelé après le projet
Dans l'image ci-dessous, le GC est trop élevé pour moi .. Dois-je gérer cela, veuillez suggérer comment?
Ci-dessous l'état de santé des nœuds .t ce point de données est enregistré dans S3, pas étonnant que je puisse voir que deux nœuds sont actifs et tous sont inactifs. -
Voici les détails du cluster lors du chargement .. À ce stade, je peux voir que le cluster est pleinement utilisé, mais pendant l'enregistrement des données dans S3, de nombreux nœuds sont libres.
Enfin, voici mon code où j'effectue Join puis enregistre dans S3 ...
import org.Apache.spark.sql.expressions._
val windowSpec = Window.partitionBy("uniqueFundamentalSet", "PeriodId", "SourceId", "StatementTypeCode", "StatementCurrencyId", "FinancialStatementLineItem_lineItemId").orderBy(unix_timestamp($"TimeStamp", "yyyy-MM-dd HH:mm:ss.SSS").cast("timestamp").desc)
val latestForEachKey = df2resultTimestamp.withColumn("rank", row_number.over(windowSpec)).filter($"rank" === 1).drop("rank", "TimeStamp")
val columnMap = latestForEachKey.columns.filter(c => c.endsWith("_1") & c != "FFAction|!|_1").map(c => c -> c.dropRight(2)) :+ ("FFAction|!|_1", "FFAction|!|")
val exprs = columnMap.map(t => coalesce(col(s"${t._1}"), col(s"${t._2}")).as(s"${t._2}"))
val exprsExtended = Array(col("uniqueFundamentalSet"), col("PeriodId"), col("SourceId"), col("StatementTypeCode"), col("StatementCurrencyId"), col("FinancialStatementLineItem_lineItemId")) ++ exprs
//Joining both dara frame here
val dfMainOutput = (dataMain.join(latestForEachKey, Seq("uniqueFundamentalSet", "PeriodId", "SourceId", "StatementTypeCode", "StatementCurrencyId", "FinancialStatementLineItem_lineItemId"), "outer") select (exprsExtended: _*)).filter(!$"FFAction|!|".contains("D|!|"))
//Joing ends here
val dfMainOutputFinal = dfMainOutput.na.fill("").select($"DataPartition", $"PartitionYear", $"PartitionStatement", concat_ws("|^|", dfMainOutput.schema.fieldNames.filter(_ != "DataPartition").filter(_ != "PartitionYear").filter(_ != "PartitionStatement").map(c => col(c)): _*).as("concatenated"))
val headerColumn = dataHeader.columns.toSeq
val headerFinal = headerColumn.mkString("", "|^|", "|!|").dropRight(3)
val dfMainOutputFinalWithoutNull = dfMainOutputFinal.withColumn("concatenated", regexp_replace(col("concatenated"), "|^|null", "")).withColumnRenamed("concatenated", headerFinal)
// dfMainOutputFinalWithoutNull.repartition($"DataPartition", $"PartitionYear", $"PartitionStatement")
.write
.partitionBy("DataPartition", "PartitionYear", "PartitionStatement")
.format("csv")
.option("timestampFormat", "yyyy/MM/dd HH:mm:ss ZZ")
.option("nullValue", "")
.option("delimiter", "\t")
.option("quote", "\u0000")
.option("header", "true")
.option("codec", "bzip2")
.save(outputFileURL)
Vous exécutez cinq instances EC3 c3.4large, qui ont 30 Go de RAM chacune. Donc, cela ne fait que 150 Go au total, ce qui est beaucoup plus petit que votre trame de données> 200 Go à joindre. D'où de nombreux débordements de disque Peut-être que vous pouvez lancer des instances EC2 de type r (mémoire optimisée par opposition au type c qui est optimisé pour le calcul) à la place, et voir s'il y a une amélioration des performances.
S3 est un magasin d'objets et non un système de fichiers, d'où les problèmes résultant de la cohérence éventuelle, des opérations de renommage non atomiques, c'est-à-dire que chaque fois que les exécuteurs écrivent le résultat du travail, chacun d'eux écrit dans un répertoire temporaire en dehors du répertoire principal (sur S3) où les fichiers devaient être écrits et une fois tous les exécuteurs exécutés, un changement de nom est effectué pour obtenir l'exclusivité atomique. C'est très bien dans un système de fichiers standard comme hdfs où les renommages sont instantanés mais sur un magasin d'objets comme S3, ce n'est pas propice car les renommages sur S3 se font à 6 Mo/s.
Pour surmonter le problème ci-dessus, assurez-vous de définir les deux paramètres de configuration suivants
1) spark.hadoop.mapreduce.fileoutputcommitter.algorithm.version = 2
Pour la valeur par défaut de ce paramètre, à savoir 1, commitTask déplace les données générées par une tâche du répertoire temporaire de la tâche vers le répertoire temporaire du travail et lorsque toutes les tâches sont terminées, commitJob déplace les données du répertoire temporaire du travail vers la destination finale. Étant donné que le pilote effectue le travail de commitJob, pour S3, cette opération peut prendre beaucoup de temps. Un utilisateur peut souvent penser que sa cellule est "suspendue". Cependant, lorsque la valeur de mapreduce.fileoutputcommitter.algorithm.version est 2, commitTask déplace les données générées par une tâche directement vers la destination finale et commitJob est fondamentalement un no-op.
2) spark.speculation = false
Si ce paramètre est défini sur true, si une ou plusieurs tâches s'exécutent lentement au cours d'une étape, elles seront relancées. Comme mentionné ci-dessus, l'opération d'écriture sur S3 à travers spark est très lent et donc nous pouvons voir beaucoup de tâches se relancer à mesure que la taille des données de sortie augmente.
Cela, ainsi que la cohérence éventuelle (lors du déplacement de fichiers du répertoire temporaire vers le répertoire de données principal), peut entraîner le blocage de FileOutputCommitter et, par conséquent, le travail peut échouer.
Alternativement
Vous pouvez d'abord écrire la sortie sur le HDFS local sur EMR, puis déplacer les données vers S3 à l'aide de la commande hadoop distcp. Cela améliore considérablement la vitesse de sortie globale. Cependant, vous aurez besoin de suffisamment de stockage EBS sur vos nœuds EMR pour garantir que toutes vos données de sortie s'intègrent.
De plus, vous pouvez écrire les données de sortie au format ORC, ce qui compressera considérablement la taille de sortie.
Référence: