Apache spark Spark性能问题-将分区作为单个文件写入S3

Apache spark Spark性能问题-将分区作为单个文件写入S3,apache-spark,pyspark,apache-spark-sql,aws-glue,aws-glue-spark,Apache Spark,Pyspark,Apache Spark Sql,Aws Glue,Aws Glue Spark,我正在运行一个spark作业,它的任务是扫描一个大文件并将其拆分为较小的文件。该文件是Json行格式的,我正试图按某个列id对其进行分区,并将每个分区作为单独的文件保存到S3。文件大小约为12GB,但id的不同值约为500000个。查询大约需要15个小时。我可以做些什么来提高性能?Spark对于这样的任务来说是一个糟糕的选择吗?请注意,我可以自由地确保源代码为每个id的固定行数 我也尝试过重新分区而不是合并 我使用AWS胶 请考虑以下可能的选项之一。看看它是否有帮助,这将是非常棒的: 首先,如@

我正在运行一个spark作业,它的任务是扫描一个大文件并将其拆分为较小的文件。该文件是Json行格式的,我正试图按某个列id对其进行分区,并将每个分区作为单独的文件保存到S3。文件大小约为12GB,但id的不同值约为500000个。查询大约需要15个小时。我可以做些什么来提高性能?Spark对于这样的任务来说是一个糟糕的选择吗?请注意,我可以自由地确保源代码为每个id的固定行数

我也尝试过重新分区而不是合并


<>我使用AWS胶

请考虑以下可能的选项之一。看看它是否有帮助,这将是非常棒的:

首先,如@Lamanus在评论中所说,如果合并,这意味着您将减少分区的数量,因此也将减少分区的数量 写入器任务,因此将所有数据洗牌到一个任务。这可能是改善的第一个因素

为了克服这个问题,即每个分区写一个文件并保持并行化级别,您可以更改以下逻辑:

对象TestSoAnswer扩展应用程序{ private val testSparkSession=SparkSession.builder .appNameDemo groupBy和partitionBy.masterlocal[*] .getOrCreate 导入testSparkSession.implicits_ //具有5个分区的输入数据集 val数据集=testSparkSession.sparkContext.parallelizeSeq TestDataa,0,TestDataa,1,TestDatab,0,TestDatab,1, TestDatac,1,TestDatac,2 ,5.toDFletter,数字 dataset.as[TestData].groupByKeyrow=>row.letter .flatmap组{ 大小写389;,值=>值 }.write.partitionByletter.modeappend.json/tmp/test-parallel-write } 案例类TestDataletter:String,数字:Int 它是如何工作的? 首先,代码执行洗牌以收集与特定键相关的所有行,与分区到同一个键相同 分区。因此,它将一次对属于该键的所有行执行写操作。不久前我写过。大致上,它会在内部对给定分区上的记录进行排序,然后再写入它们 一个接一个地输入文件

这样我们就得到了这样一个计划,其中只有1个洗牌,因此存在处理消耗操作:

== Physical Plan ==
*(2) SerializeFromObject [staticinvoke(class org.apache.spark.unsafe.types.UTF8String, StringType, fromString, knownnotnull(assertnotnull(input[0, TestData, true])).letter, true, false) AS letter#22, knownnotnull(assertnotnull(input[0, TestData, true])).number AS number#23]
+- MapGroups TestSoAnswer$$$Lambda$1236/295519299@55c50f52, value#18.toString, newInstance(class TestData), [value#18], [letter#3, number#4], obj#21: TestData
   +- *(1) Sort [value#18 ASC NULLS FIRST], false, 0
      +- Exchange hashpartitioning(value#18, 200), true, [id=#15]
         +- AppendColumnsWithObject TestSoAnswer$$$Lambda$1234/1747367695@6df11e91, [staticinvoke(class org.apache.spark.unsafe.types.UTF8String, StringType, fromString, knownnotnull(assertnotnull(input[0, TestData, true])).letter, true, false) AS letter#3, knownnotnull(assertnotnull(input[0, TestData, true])).number AS number#4], [staticinvoke(class org.apache.spark.unsafe.types.UTF8String, StringType, fromString, input[0, java.lang.String, true], true, false) AS value#18]
            +- Scan[obj#2]
两次执行的TestSoAnswer的输出如下所示:

test-parallel-write % ls
_SUCCESS letter=a letter=b letter=c
test-parallel-write % ls letter=a
part-00170-68245d8b-b155-40ca-9b5c-d9fb746ac76c.c000.json part-00170-cd90d64f-43c6-4582-aae6-fe443b6617f4.c000.json

test-parallel-write % ls letter=b
part-00161-68245d8b-b155-40ca-9b5c-d9fb746ac76c.c000.json part-00161-cd90d64f-43c6-4582-aae6-fe443b6617f4.c000.json

test-parallel-write % ls letter=c
part-00122-68245d8b-b155-40ca-9b5c-d9fb746ac76c.c000.json part-00122-cd90d64f-43c6-4582-aae6-fe443b6617f4.c000.json
您还可以控制

编辑:没有看到@mazaneicha的评论,但实际上,您可以尝试使用repartitionpartitioning列!它甚至比分组表达式更清楚

最好的


< Bartosz >

< P>请考虑以下可能的选择之一。看看它是否有帮助,这将是非常棒的:

首先,如@Lamanus在评论中所说,如果合并,这意味着您将减少分区的数量,因此也将减少分区的数量 写入器任务,因此将所有数据洗牌到一个任务。这可能是改善的第一个因素

为了克服这个问题,即每个分区写一个文件并保持并行化级别,您可以更改以下逻辑:

对象TestSoAnswer扩展应用程序{ private val testSparkSession=SparkSession.builder .appNameDemo groupBy和partitionBy.masterlocal[*] .getOrCreate 导入testSparkSession.implicits_ //具有5个分区的输入数据集 val数据集=testSparkSession.sparkContext.parallelizeSeq TestDataa,0,TestDataa,1,TestDatab,0,TestDatab,1, TestDatac,1,TestDatac,2 ,5.toDFletter,数字 dataset.as[TestData].groupByKeyrow=>row.letter .flatmap组{ 大小写389;,值=>值 }.write.partitionByletter.modeappend.json/tmp/test-parallel-write } 案例类TestDataletter:String,数字:Int 它是如何工作的? 首先,代码执行洗牌以收集与特定键相关的所有行,与分区到同一个键相同 分区。因此,它将一次对属于该键的所有行执行写操作。不久前我写过。大致上,它会在内部对给定分区上的记录进行排序,然后再写入它们 一个接一个地输入文件

这样我们就得到了这样一个计划,其中只有1个洗牌,因此存在处理消耗操作:

== Physical Plan ==
*(2) SerializeFromObject [staticinvoke(class org.apache.spark.unsafe.types.UTF8String, StringType, fromString, knownnotnull(assertnotnull(input[0, TestData, true])).letter, true, false) AS letter#22, knownnotnull(assertnotnull(input[0, TestData, true])).number AS number#23]
+- MapGroups TestSoAnswer$$$Lambda$1236/295519299@55c50f52, value#18.toString, newInstance(class TestData), [value#18], [letter#3, number#4], obj#21: TestData
   +- *(1) Sort [value#18 ASC NULLS FIRST], false, 0
      +- Exchange hashpartitioning(value#18, 200), true, [id=#15]
         +- AppendColumnsWithObject TestSoAnswer$$$Lambda$1234/1747367695@6df11e91, [staticinvoke(class org.apache.spark.unsafe.types.UTF8String, StringType, fromString, knownnotnull(assertnotnull(input[0, TestData, true])).letter, true, false) AS letter#3, knownnotnull(assertnotnull(input[0, TestData, true])).number AS number#4], [staticinvoke(class org.apache.spark.unsafe.types.UTF8String, StringType, fromString, input[0, java.lang.String, true], true, false) AS value#18]
            +- Scan[obj#2]
两次执行的TestSoAnswer的输出如下所示:

test-parallel-write % ls
_SUCCESS letter=a letter=b letter=c
test-parallel-write % ls letter=a
part-00170-68245d8b-b155-40ca-9b5c-d9fb746ac76c.c000.json part-00170-cd90d64f-43c6-4582-aae6-fe443b6617f4.c000.json

test-parallel-write % ls letter=b
part-00161-68245d8b-b155-40ca-9b5c-d9fb746ac76c.c000.json part-00161-cd90d64f-43c6-4582-aae6-fe443b6617f4.c000.json

test-parallel-write % ls letter=c
part-00122-68245d8b-b155-40ca-9b5c-d9fb746ac76c.c000.json part-00122-cd90d64f-43c6-4582-aae6-fe443b6617f4.c000.json
您还可以控制

编辑:没有看到@mazaneicha的评论,但实际上,您可以尝试使用repartitionpartitioning列!它甚至比分组表达式更清楚

最好的


Bartosz。

如果你不打算使用Spark来做任何事情,而只是将文件本身拆分成更小的版本,那么我会说Spark是一个糟糕的选择。您最好在AWS中按照中给出的方法执行此操作

假设您有一个可用的EC2实例,您可以运行如下操作:

aws s3 cp s3://input_folder/12GB.json - | split -l 1000 - output.
aws s3 cp output.* s3://output_folder/
如果您希望在Spark中对数据进行进一步处理,则需要将数据重新分区为128MB到128MB之间的块。和 默认的snappy压缩,通常会得到原始文件大小的20%。因此,在您的情况下:在12/5~3和12/5/8~20个分区之间,所以:

data = spark.read.format("json").load(input_folder, schema=INS_SCHEMA) 

dataPart = data.repartition(12)
对于Spark来说,这实际上不是一个特别大的数据集,处理起来也不应该那么麻烦


保存为拼花地板为您提供了一个很好的恢复点,重新读取数据将非常快。总文件大小约为2.5 GB。

如果您不打算使用Spark,而只是将文件拆分为更小的版本,那么我会说Spark是一个糟糕的选择。您最好在AWS中按照中给出的方法执行此操作

假设您有一个可用的EC2实例,您可以运行如下操作:

aws s3 cp s3://input_folder/12GB.json - | split -l 1000 - output.
aws s3 cp output.* s3://output_folder/
如果您希望在Spark中对数据进行进一步处理,则需要将数据重新分区为128MB到128MB之间的块。使用默认的snappy压缩,您通常会得到原始文件大小的20%。因此,在您的情况下:在12/5~3和12/5/8~20个分区之间,所以:

data = spark.read.format("json").load(input_folder, schema=INS_SCHEMA) 

dataPart = data.repartition(12)
对于Spark来说,这实际上不是一个特别大的数据集,处理起来也不应该那么麻烦



保存为拼花地板为您提供了一个很好的恢复点,重新读取数据将非常快。总文件大小约为2.5 GB。

为什么需要合并1?有什么原因吗啊。。。模式附加,我明白了。Spark使用分布式文件系统,因此单文件处理非常糟糕,会使作业速度变慢。那么你是说如果我有多个文件作为源文件,速度会更快吗?我认为Spark将使用分布式处理,即使源文件是单个文件。我的意思是当您写入而不是读取时。那么,当您尝试重新分区Fnsku__1而不是合并1时发生了什么?两者之间的区别在于,重新划分创建了一个阶段边界,而合并可以向前优化…但是,如果要进行剧烈合并…为什么需要合并1?有什么原因吗啊。。。模式附加,我明白了。Spark使用分布式文件系统,因此单文件处理非常糟糕,会使作业速度变慢。那么你是说如果我有多个文件作为源文件,速度会更快吗?我认为Spark将使用分布式处理,即使源文件是单个文件。我的意思是当您写入而不是读取时。那么,当您尝试重新分区Fnsku__1而不是合并1时发生了什么?两者之间的区别在于,重新分区创建了一个阶段边界,而合并可以向前优化…但是,如果要进行剧烈合并…只有当每个分区列的行数相等时,这才有效。data.repartition12将随机重新排列RDD中的数据,以适应12个分区。未给定分区列,未使用任何分区列。Ref:仅当每个分区列的行数相等时,此操作才有效。data.repartition12将随机重新排列RDD中的数据,以适合12个分区。未给定分区列,未使用任何分区列。参考:谢谢。我的一位队友将尝试这种方法,并在评论部分做出回应,因此使用这种方法,运行时间将从50小时缩短到20小时!这是一个很大的进步,但我需要把它减少到5小时以下。我正在处理一个50GB的文件,分区列有170万个不同的值。在这一点上你还有什么建议吗?文件的格式是什么?压缩还是不压缩?如果您想尝试一下,也许您可以将大作业拆分为小作业,通过拆分,我的意思是每个作业在不同的分区列范围上进行筛选。你并行运行这些作业,除非你在阅读上有争议,否则它应该会加快时间。我看不到日志,但假设对于1.7mln分区,用于编写的I/O部分需要时间,并且只有一个进程,没有办法加快速度。谢谢。我的一位队友将尝试这种方法,并在评论部分做出回应,因此使用这种方法,运行时间将从50小时缩短到20小时!这是一个很大的进步,但我需要把它减少到5小时以下。我正在处理一个50GB的文件,分区列有170万个不同的值。在这一点上你还有什么建议吗?文件的格式是什么?压缩还是不压缩?如果您想尝试一下,也许您可以将大作业拆分为小作业,通过拆分,我的意思是每个作业在不同的分区列范围上进行筛选。你并行运行这些作业,除非你在阅读上有争议,否则它应该会加快时间。我看不到日志,但假设对于1.7mln分区,用于编写的I/O部分需要时间,并且只有一个进程,没有办法加速它。