当最简单的spark应用程序似乎两次完成同一项工作时,我陷入了一种奇怪的情况。
应用程序本身执行查询:
SELECT date, field1, field2, ..., field10
FROM table1
WHERE field1 = <some number>
AND date BETWEEN date('2018-05-01') AND date('2018-05-30')
ORDER BY 1
并将结果存储到HDFS中。
表table1
是一堆实木复合地板文件,存储在HDFS上,并按以下方式分区
/root/date=2018-05-01/hour=0/data-1.snappy.parquet
/root/date=2018-05-01/hour=0/data-2.snappy.parquet
...
/root/date=2018-05-01/hour=1/data-1.snappy.parquet
...
/root/date=2018-05-02/hour=0/data-1.snappy.parquet
...
etc.
所有实木复合地板文件的大小从700M到2G,并且具有相同的架构:int
或bigint
类型的10个非空字段。
应用程序的结果很小-仅几千行。
我的spark应用程序在YARN上以集群模式运行。基本火花参数为
spark.driver.memory=2g
spark.executor.memory=4g
spark.executor.cores=4
spark.dynamicAllocation.enabled=true
spark.shuffle.service.enabled=true
spark.submit.deployMode=cluster
在执行过程中,抢占了两个容器,没有错误,也没有发生故障。整个应用程序一次完成。
Spark UI的屏幕截图:
可以看出,阶段2和4都处理了相同数量的输入行,但是阶段4也进行了一些改组(这些都是结果行)。失败的任务是那些抢占了容器的任务。
所以我的应用程序似乎两次处理了相同的文件。
我不知道这怎么可能和发生了什么。请帮我了解为什么Spark会做这种奇怪的事情。
实际身体计划:
== Physical Plan ==
Execute InsertIntoHadoopFsRelationCommand InsertIntoHadoopFsRelationCommand hdfs://hadoop/root/tmp/1530123240802-PrQXaOjPoDqCBhfadgrXBiTtfvFrQRlB, false, CSV, Map(path -> /root/tmp/1530123240802-PrQXaOjPoDqCBhfadgrXBiTtfvFrQRlB), Overwrite, [date#10, field1#1L, field0#0L, field3#3L, field2#2L, field5#5, field4#4, field6#6L, field7#7]
+- Coalesce 16
+- *(2) Sort [date#10 ASC NULLS FIRST], true, 0
+- Exchange rangepartitioning(date#10 ASC NULLS FIRST, 200)
+- *(1) Project [date#10, field1#1L, field0#0L, field3#3L, field2#2L, field5#5, field4#4, field6#6L, field7#7]
+- *(1) Filter (isnotnull(field1#1L) && (field1#1L = 1234567890))
+- *(1) FileScan parquet default.table1[field0#0L,field1#1L,field2#2L,field3#3L,field4#4,field5#5,field6#6L,field7#7,date#10,hour#11] Batched: true, Format: Parquet, Location: InMemoryFileIndex[hdfs://hadoop/table1], PartitionCount: 714, PartitionFilters: [(date#10 >= 17652), (date#10 <= 17682)], PushedFilters: [IsNotNull(field1), EqualTo(field1,1234567890)], ReadSchema: struct<field0:bigint,field1:bigint,field2:bigint,field3:bigint,field4:int,field5:int,field6:bigint,field7:...
以下是第2阶段和第4阶段的DAG:
答案 0 :(得分:0)
我仍然不确定为什么spark会以这种方式运行并且我仍在挖掘,但是我设法做到了 。
注意:我的SQL以ORDER
结尾。由于预计该作业将返回很少的行,因此我认为最终排序应该是一件容易的事。
因此,当我删除ORDER
子句时,我的查询将按预期运行,并且仅读取实木复合地板一次。无论数据集有多大,执行期间任务被抢占了多少次,这种怪异的行为都是可重现的:添加order
子句会导致火花对整个数据集进行两次扫描(至少看起来像)。
我忘了提及:我使用的是Hortonworks发行版(HDP-2.6.5)中的spark 2.3.0。
答案 1 :(得分:0)
我遇到了这个完全相同的问题,事实证明,这种行为是完全正常的。
我在一个Spark作业中观察到了这种行为,该作业只是从HDFS读取数据,进行了一些轻量级的处理,并使用orderBy
方法对列进行排序,然后再写回HDFS。在Spark UI中,与您一样,我看到了两个作业将扫描整个6 TB表。第一项工作占用的内存很少,没有写入随机记录,也没有向HDFS写入记录。
事实证明,根本原因在于,在实际对数据进行排序之前,Spark会执行采样操作,以帮助其定义用于对其数据进行分区的RangePartitioner
,用于其排序算法:它需要知道定义排序键以定义良好RangePartitioner
的列中数据的大致范围。
此博客中提到了此操作:
https://blog.cloudera.com/blog/2015/01/improving-sort-performance-in-apache-spark-its-a-double/
此StackOverflow帖子:
How does Spark achieve sort order?
以及霍顿·卡劳(Holden Karau)和瑞秋·沃兰(Rachel Warran)着作的著作《 高性能火花》(第pg)。 143。
就我而言,我知道密钥的范围,因此我想到原则上应该能够定义RangePartitioner
一个先验。但是,我在Spark源代码中为其sort
方法进行了挖掘,但是没有找到可以明确地传递范围的任何解决方法。