为什么Spark两次处理相同的数据?

时间:2018-06-28 13:04:36

标签: apache-spark

当最简单的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,并且具有相同的架构:intbigint类型的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的屏幕截图:

  • main screen
  • stage 2
  • stage 4

可以看出,阶段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:

  • stage 2
  • stage 4

2 个答案:

答案 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方法进行了挖掘,但是没有找到可以明确地传递范围的任何解决方法。