web-dev-qa-db-ja.com

EMRでジョブを調整する方法spark S3で巨大なデータをすばやく書き込む方法

私はsparkジョブで、2つのデータフレーム間で外部結合を行っています。最初のデータフレームのサイズは260 GBで、ファイル形式は2200ファイルに分割されたテキストファイルで、2番目のサイズです。データフレームは2GBです。その後、約260 GBのデータフレーム出力をS3に書き込むのに非常に長い時間がかかります。EMRで大幅に変更されたため、キャンセルしてから2時間以上かかります。

これが私のクラスター情報です。

emr-5.9.0
Master:    m3.2xlarge
Core:      r4.16xlarge   10 machines (each machine has 64 vCore, 488 GiB memory,EBS Storage:100 GiB)

これは私が設定している私のクラスター設定です

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

以下のように手動でメモリコンポーネントを設定してみましたが、パフォーマンスは向上しましたが、同じことが非常に長い時間かかっていました

--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

デフォルトのパーティションを使用してデータをS3に保存していません。

わかりやすいように、ジョブとクエリプランに関するすべての詳細を追加します。

本当の理由はパーティションです。そしてそれはほとんどの時間を費やしています。私は2Kファイルを持っているので、200のようなreパーティションを使用する場合、出力ファイルはlakhsで提供され、sparkで再度ロードすることは良い話ではありません。

以下の画像では、プロジェクトの後にソートが再度呼び出される理由がわかりませんenter image description here

以下では、イメージGCは高すぎます..これを処理する必要がありますか、方法を教えてくださいExecutor and GC details

以下はノードのヘルスステータスです。このポイントデータはS3に保存されています。なぜ2つのノードだけがアクティブで、すべてがアイドル状態であるのがわかるのでしょうか。- This is my nodes details .At this point Data is getting saved into S3

これは、ロード時のクラスターの詳細です。この時点で、クラスターは完全に利用されていますが、データをS3に保存している間、多くのノードが空いています。Fully Utilized clsuter

最後に、Joinを実行して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)
18
SUDARSHAN

RAMがそれぞれ30GBの5つのc3.4large EC2インスタンスを実行しています。そのため、合計で150GBにすぎず、結合する200GBを超えるデータフレームよりもはるかに小さくなります。したがって、大量のディスクが流出します。代わりに、rタイプのEC2インスタンス(計算最適化されたcタイプではなくメモリ最適化)を起動して、パフォーマンスが向上するかどうかを確認できます。

S3はファイルシステムではなくオブジェクトストアであるため、結果整合性、非アトミックな名前変更操作から発生する問題、つまり、エグゼキュータがジョブの結果を書き込むたびに、それぞれがメインディレクトリの外部の一時ディレクトリに書き込みます(S3の場合)ファイルを書き込む必要があり、すべてのエグゼキューターが完了すると、アトミックな排他性を取得するために名前が変更されます。これは、名前変更が瞬時に行われるhdfsなどの標準ファイルシステムでは問題ありませんが、S3のようなオブジェクトストアでは、S3での名前変更が6MB /秒で行われるため、これは助長されません。

上記の問題を解決するには、次の2つのconfパラメータを設定してください。

1)spark.hadoop.mapreduce.fileoutputcommitter.algorithm.version = 2

このパラメーターのデフォルト値、つまり1の場合、commitTaskは、タスクによって生成されたデータをタスクの一時ディレクトリからジョブの一時ディレクトリに移動し、すべてのタスクが完了すると、commitJobはデータをジョブの一時ディレクトリから最終的な宛先に移動します。ドライバーはcommitJobの作業を行っているため、S3の場合、この操作には長い時間がかかる可能性があります。ユーザーは自分の細胞が「ぶら下がっている」と考えることがよくあります。ただし、mapreduce.fileoutputcommitter.algorithm.versionの値が2の場合、commitTaskはタスクによって生成されたデータを直接最終的な宛先に移動し、commitJobは基本的に何もしません。

2)spark.speculation = false

このパラメーターがtrueに設定されている場合、ステージで1つ以上のタスクの実行速度が遅い場合、それらのタスクは再起動されます。上記で述べたように、sparkジョブを介したS3での書き込み操作は非常に遅いため、出力データのサイズが大きくなると、多くのタスクが再起動されることがわかります。

これと結果の一貫性(一時ディレクトリからメインデータディレクトリにファイルを移動している間)により、FileOutputCommitterがデッドロックになり、ジョブが失敗する可能性があります。

または

最初に出力をEMRのローカルHDFSに書き込み、次にhadoop distcpコマンドを使用してデータをS3に移動できます。これにより、全体的な出力速度が大幅に向上します。ただし、すべての出力データが収まるようにするには、EMRノードに十分なEBSストレージが必要です。

さらに、出力サイズを大幅に圧縮するORC形式で出力データを書き込むことができます。

参照:

https://medium.com/@subhojit20_27731/Apache-spark-and-Amazon-s3-gotchas-and-best-practices-a767242f3d98

2
Harsh Bafna