databricks中的显式表分区如何影响写性能?

xienkqul  于 2021-06-27  发布在  Hive
关注(0)|答案(3)|浏览(462)

我们有以下场景:
我们有一个包含大约150亿条记录的现有表。它在创建时没有明确划分。
我们正在用分区创建这个表的副本,希望在某些类型的查询上有更快的读取时间。
我们的表在databricks云上,我们使用databricks delta。
我们通常按两列进行筛选,其中一列是实体的id(350k个不同的值),另一列是事件发生的日期(到目前为止有31个不同的值,但每天都在增加!)。
因此,在创建新表时,我们运行了如下查询:

CREATE TABLE the_new_table
USING DELTA
PARTITIONED BY (entity_id, date)
AS SELECT
  entity_id,
  another_id,
  from_unixtime(timestamp) AS timestamp,
  CAST(from_unixtime(timestamp) AS DATE) AS date
FROM the_old_table

此查询已运行48小时并正在计数。我们知道它正在取得进展,因为我们在相关的s3前缀中找到了大约250k个与第一个分区键相对应的前缀,并且在前缀中肯定存在一些大文件。
然而,我们很难精确地监控到底取得了多少进展,以及我们预计这需要多长时间。
当我们等待的时候,我们尝试了这样一个查询:

CREATE TABLE a_test_table (
  entity_id STRING,
  another_id STRING,
  timestamp TIMESTAMP,
  date DATE
)
USING DELTA
PARTITIONED BY (date);

INSERT INTO a_test_table
SELECT
  entity_id,
  another_id,
  from_unixtime(timestamp) AS timestamp,
  CAST(from_unixtime(timestamp) AS DATE) AS date
FROM the_old_table
  WHERE CAST(from_unixtime(timestamp) AS DATE) = '2018-12-01'

注意,这里新表模式的主要区别在于,我们只按日期进行分区,而不是按实体id进行分区。我们选择的日期几乎正好包含旧表数据的4%,我想指出这一点,因为它远远超过了1/31。当然,由于我们是通过一个值来选择的,而这个值恰好是我们划分的同一个对象,因此实际上我们只编写了一个分区,而可能是十几万个分区。
使用相同数量的工作节点创建这个测试表需要16分钟,因此我们预计(基于此)创建一个大25倍的表只需要7小时左右。
这个答案似乎部分承认使用过多的分区可能会导致问题,但是潜在的原因在过去几年中似乎已经发生了很大的变化,因此我们试图了解当前的问题可能是什么;databricks文档并没有特别说明问题。
根据s3发布的请求率准则,增加分区(密钥前缀)的数量似乎可以提高性能。分区有害似乎违反直觉。
总而言之:我们期望在成千上万个分区中的每个分区中写入成千上万条记录。似乎减少分区的数量可以显著减少写入表数据所需的时间。为什么这是真的?对于为特定大小的数据创建的分区的数量,有什么通用的指导原则吗?

3bygqnnd

3bygqnnd1#

我不是一个databricksMaven,但希望这些子弹能有所帮助
分区数
不管怎样,创建的分区和文件的数量都会影响作业的性能,特别是使用s3作为数据存储,但是,大小不同的集群应该可以轻松地处理这个数量的文件
动态分区
用两个键而不是一个键动态分区之间有很大的区别,让我尝试更详细地解决这个问题。
当您根据任务的数量和数据的大小对数据进行动态分区时,每个分区可能会创建大量的小文件,这可能会(而且可能会)影响需要使用此数据的下一个作业的性能,特别是当您的数据存储在orc、parquet或任何其他列格式中时。请注意,这将只需要一个map-only作业。
前面解释的问题以不同的方式解决,这是文件合并中最常见的问题。为此,数据被重新分区以创建更大的文件。因此,需要对数据进行洗牌。
您的查询
对于第一个查询,分区数将是350k*31(大约11mm!),考虑到处理工作所需的洗牌量和任务量,这是非常大的。
对于第二个查询(只需要16分钟),所需的任务数和所需的洗牌要少得多。
分区的数量(洗牌/排序/任务调度等)和作业执行的时间没有线性关系,这就是为什么在这种情况下,数学不能相加的原因。
推荐
我想你已经明白了,你应该把etl作业分成31个不同的查询,这样可以优化执行时间

h79rfbju

h79rfbju2#

你应该把你的数据按 date 因为听起来像是随着时间的推移不断添加数据。这是划分时间序列数据的公认方法。这意味着您将每天写入一个日期分区,并且您以前的日期分区不会再次更新(一件好事)。
如果您的用例从中受益,那么您当然可以使用辅助分区键(即。 PARTITIONED BY (date, entity_id) )
按日期分区将要求您也总是按日期读取这些数据,以获得最佳性能。如果这不是您的用例,那么您必须澄清您的问题。
有多少个分区?
没有人能回答您应该使用多少分区,因为每个数据集(和处理集群)都是不同的。您要避免的是“数据倾斜”,即一个工人必须处理大量数据,而其他工人则处于空闲状态。在你的情况下,如果 clientid 例如,数据集的20%。按日期划分必须假设每天的数据量大致相同,因此每个工作线程都保持同样的繁忙状态。
我不知道databricks是如何写入磁盘的,但是在hadoop上,我希望看到每个工作节点都写入自己的文件部分,因此您的写入性能在这个级别上是并行的。

xu3bshqb

xu3bshqb3#

如果占用分区列,我的建议是
确定所有列的基数,并选择时间有限的列,因此排除标识符和日期列
确定表的主要搜索,可能是日期或某个分类字段
生成具有有限基数的子列以加快搜索示例在日期的情况下可以将其分解为年、月、日等,或者在整数标识符的情况下,将其分解为这些ID的整数除法%[1,2,3…]
如前所述,使用基数较高的列进行分区会产生大量文件,从而导致性能较差,这是最糟糕的情况。
建议使用不超过1 gb的文件,在创建增量表时,建议使用“coalesce(1)”
如果需要执行更新或插入,请指定最大数量的分区列,以排除文件读取的连续情况,这对于减少时间非常有效。

相关问题