什么是在Spark SQL中连接大表的优化方法

时间:2016-06-15 18:01:05

标签: apache-spark apache-spark-sql

我需要使用Spark SQL或Dataframe API连接表。需要知道实现它的最佳方式。

情景是:

  1. 所有数据都以ORC格式存在于Hive中(基本数据帧和参考文件)。
  2. 我需要加入一个从Hive读取的基本文件(Dataframe)和11-13其他参考文件来创建一个大的内存结构(400列)(大小约1 TB)
  3. 实现这一目标的最佳方法是什么?如果有人遇到类似问题,请分享您的经验。

3 个答案:

答案 0 :(得分:8)

关于如何优化连接的默认建议是:

  1. 如果可以,请使用广播联接(请参阅this notebook)。从您的问题来看,您的表格似乎很大,并且广播联接不是一种选择。

  2. 考虑使用一个非常大的集群(你可能会认为它更便宜)。现在250美元(6/2016)在EC2现货实例市场上购买了大约24小时800核,6Tb RAM和许多SSD。在考虑大数据解决方案的总成本时,我发现人类往往会大大低估他们的时间。

  3. 使用相同的分区程序。有关共同分组联接的信息,请参阅this question

  4. 如果数据量很大和/或您的群集无法增长,甚至上面的(3)导致OOM,请使用两遍方法。首先,重新分区数据并使用分区表(dataframe.write.partitionBy())保持不变。然后,在循环中串行连接子分区,“追加”到同一个最终结果表。

  5. 旁注:我上面说“追加”,因为在制作中我从不使用SaveMode.Append。它不是幂等的,而且是危险的。我将SaveMode.Overwrite深入到分区表树结构的子树中。在2.0.0和1.6.2之前,您必须删除_SUCCESS或元数据文件,否则动态分区发现将会阻塞。

    希望这有帮助。

答案 1 :(得分:2)

对源使用散列分区或范围分区进行分区,或者如果您对连接字段有更好的了解,则可以编写自定义分区。分区将有助于避免在连接期间重新分区,因为来自表的相同分区的火花数据将存在于同一位置。 ORC肯定会帮助这个事业。 如果这仍然导致溢出,请尝试使用比磁盘更快的快速

答案 2 :(得分:2)

Spark 使用 SortMerge joins 连接大表。它包括对两个表上的每一行进行散列,并将具有相同散列的行混洗到同一个分区中。键在两侧排序并应用 sortMerge 算法。据我所知,这是最好的方法。

要显着加快 sortMerges 的速度,请将大型数据集编写为带有预分桶和预排序选项(相同数量的分区)的 Hive 表,而不是平面拼花数据集。

namespace std {
template <>
struct hash<T> {
  size_t operator()(const T& x) const {
    return ...;
  }
};
} // end of std

与收益相比,编写预分桶/预排序表的开销成本适中。

默认情况下,底层数据集仍将是镶木地板,但 Hive 元存储(可以是 AWS 上的 Glue 元存储)将包含有关表结构的宝贵信息。因为所有可能的“可连接”行都位于同一位置,Spark 不会对预先存储的表进行混洗(节省很大!),也不会对预先排序的表分区内的行进行排序。

tableA
  .repartition(2200, $"A", $"B")
  .write
  .bucketBy(2200, "A", "B")
  .sortBy("A", "B")   
  .mode("overwrite")
  .format("parquet")
  .saveAsTable("my_db.table_a")


tableb
  .repartition(2200, $"A", $"B")
  .write
  .bucketBy(2200, "A", "B")
  .sortBy("A", "B")    
  .mode("overwrite")
  .format("parquet")
  .saveAsTable("my_db.table_b")

查看使用和不使用预分桶的执行计划。

这不仅可以在加入过程中为您节省大量时间,而且可以在没有 OOM 的情况下在相对较小的集群上运行非常大的加入。在亚马逊,我们大部分时间都在生产中使用它(仍有少数情况下不需要)。

要了解有关预分桶/预分拣的更多信息: