Amazon S3
を使用して、parquet
ファイルをSpark 1.6.1
に書き込もうとしています。私が生成している小さなparquet
は、一度書き込まれた~2GB
なので、それほど多くのデータではありません。 Spark
outを使用できるプラットフォームとして証明しようとしています。
基本的には、dataframes
でstar schema
を設定し、それらのテーブルを寄木細工に書きます。データはベンダーから提供されたcsvファイルから取得し、Spark=としてETL
プラットフォームとして使用しています。現在、ec2(r3.2xlarge)
に3ノードのクラスターがありますしたがって、エグゼキュータと合計16コアのメモリの120GB
。
入力ファイルの合計は約22GBで、今のところ約2GBのデータを抽出しています。最終的に、完全なデータセットのロードを開始すると、これは数テラバイトになります。
これが私のスパーク/スカラpseudocode
です:
def loadStage(): Unit = {
sc.hadoopConfiguration.set("fs.s3a.buffer.dir", "/tmp/tempData")
sc.hadoopConfiguration.set("spark.sql.parquet.output.committer.class","org.Apache.spark.sql.parquet.DirectParquetOutputCommitter")
sc.hadoopConfiguration.set("spark.sql.Hive.convertMetastoreParquet","false")
var sqlCtx = new SQLContext(sc)
val DataFile = sc.textFile("s3a://my-bucket/archive/*/file*.gz")
//Setup header table/df
val header_rec = DataFile.map(_.split("\\|")).filter(x=> x(0) == "1")
val headerSchemaDef = "market_no,rel_date,field1, field2, field3....."
val headerSchema = StructType(headerSchemaDef.split(",").map(fieldName => StructField(fieldName, StringType,false)))
val headerRecords = header_rec.map(p => Row(p(3), p(8), p(1), p(2), p(4), p(5), p(6) ))
val header = sqlCtx.createDataFrame(headerRecords, headerSchema)
header.registerTempTable("header")
sqlCtx.cacheTable("header")
//Setup fact table/df
val fact_recs = DataFile.map(_.split("\\|")).filter(x=> x(0) == "2")
val factSchemaDef = "market_no,rel_date,field1, field2, field3....."
val factSchema = StructType(factSchemaDef.split(",").map(fieldName => StructField(fieldName, StringType,false)))
val records = fact_recs.map(p => Row(p(11), p(12), p(1), p(2), p(3), p(4), p(5), p(6), p(7), p(8), p(9), p(10)))
val df = sqlCtx.createDataFrame(records, factSchema)
df.registerTempTable("fact")
val results = sqlCtx.sql("select fact.* from header inner join fact on fact.market_no = header.market_no and fact.rel_date = header.rel_date")
println(results.count())
results.coalesce(1).write.mode(SaveMode.Overwrite).parquet("s3a://my-bucket/a/joined_data.parquet")
}
465884512行のカウントには約2分かかります。寄せ木張りへの書き込みには8分かかります
coalesce
が書き込みを行うドライバーに対してシャッフルを行うことを理解しています...しかし、それが取っている時間の長さは、私が何か重大な間違いをしていると思うようにしています。 coalesce
がない場合、これにはまだ15分かかりますが、IMOはまだ長すぎて、大量のparquet
ファイルを生成します。私が持っているデータの1日あたり1つの大きなファイルが欲しいです。同様に、フィールド値によってパーティション分割を行うコードがあります。また、これをcsv
に出力しようとしましたが、1時間ほどかかります。
また、ジョブを送信するときに、実行時の小道具を実際に設定していません。 1つのジョブのコンソール統計は次のとおりです。
Sparkのデフォルトは、特にS3への書き込み時に、I/O操作中に大量の(おそらく)不要なオーバーヘッドを引き起こします。 この記事 これについてさらに詳しく説明しますが、変更を検討したい設定が2つあります。
DirectParquetOutputCommitterを使用します。デフォルトでは、Sparkはすべてのデータを一時フォルダーに保存し、それらのファイルを後で移動します。DirectParquetOutputCommitterを使用すると、S3出力パスに直接書き込むことで時間を節約できます
- S3aとHadoop 2.7.2+を使用するようにコードを切り替えます。すべてのラウンドでより良く、Hadoop 2.8でより良くなり、s3guardの基礎となります
- Hadoop FileOutputCommitterを使用して、mapreduce.fileoutputcommitter.algorithm.versionを2に設定します
-スキーマのマージは Spark 1.5 の時点でデフォルトでオフになっています スキーマのマージをオフにします。スキーママージがオンの場合、ドライバーノードはすべてのファイルをスキャンして、スキーマの一貫性を確保します。これは分散操作ではないため、特にコストがかかります。を実行して、これがオフになっていることを確認します
val file = sqx.read.option("mergeSchema", "false").parquet(path)
直接出力コミッターはspark codebaseから削除されています。削除したコードを独自のJARに記述/復活させる必要があります。問題が「無効なデータ」である場合、他の障害も問題を引き起こす可能性があること。
より明るい注意として、Hadoop 2.8は、S3から最適化されたバイナリ形式(ORC、Parquet)を読み取るためのS3Aの高速化をいくつか追加します。詳細については、 HADOOP-11694 を参照してください。また、作業の最後に堅牢なO(1)コミットを行うことができるはずの一貫したメタデータストアにAmazon Dynamoを使用している人もいます。
Spark S3への書き込みを高速化するための即時アプローチの1つは、 EMRFS S3-optimized Committer を使用することです。
ただし、s3aを使用する場合、このコミッター 使用不可 :
EMRFS S3に最適化されたコミッターが使用されない場合
コミッターは、次の状況では使用されません。
When writing to HDFS -> When using the S3A file system When using an output format other than Parquet, such as ORC or text When using MapReduce or Spark's RDD API
AWS EMR 5.26でこの違いをテストしましたが、s3://の使用はs3a://よりも15%〜30%高速でした(ただし、依然として低速です)。
私もこの問題を抱えていました。残りの発言から追加されたものは、AWSからの完全な説明です: https://aws.Amazon.com/blogs/big-data/improve-Apache-spark-write-performance-on-Apache-parquet -formats-with-the-emrfs-s3-optimized-committer /
実験中にFileOutCommiter v2(v1から)に変更しただけで、書き込みが3〜4倍に改善されました。
self.sc._jsc.hadoopConfiguration().set("mapreduce.fileoutputcommitter.algorithm.version", "2")