Press "Enter" to skip to content

dbt 增量化 — 正确的方式

从全负载痛苦到增量增益(以及一些错误的经历)

Lukas Tennie在Unsplash上的照片

当我在GlamCorner的团队开始从传统的MySQL数据库转向以dbt为转换和建模层的Postgres数据库时,我们感到非常高兴。我们建立了dbt项目和配置文件,为我们的模型专门编写了宏,并构建了更多的数据集市来服务下游需求。我们以为我们完成了 – 直到我们遇到了第一个障碍:模型运行时间。在本文中,我将解释我如何通过采用dbt的增量方式克服了当时最棘手的性能挑战,犯了一些错误(谁没有呢?),并从中学到了宝贵的经验教训。

不断演化的怪兽

在GlamCorner,我们从事循环时尚游戏。我们的“后端”团队在仓库里使用RFID扫描仪,像专业人士一样扫描物品的进出。我们还使用诸如Zendesk和Google Analytics之类的高级平台,让我们的客户感到格外特别。最后,我们有自己的内部库存系统 – 这要归功于我们聪明的软件工程师 – 将我们的前端和后端系统连接在一起。这就像天作之合。但随着我们的业务增长和运营年限的增加,我们的数据库变得越来越大。而且,让我们说说传统的全表加载开始感觉有点痛苦。

痛苦

要么你理解“我希望数据在早上9点之前准备好”的痛苦,要么你不理解。

作者提供的图像

团队付出了努力来创建一个无缺陷的(E)xtract和(L)oad,我们齐聚并庆祝。然后有一天,(T)ransformation决定“嘿,在这里不是这样工作的”,并把总运行时间从10分钟增加到90分钟。我可能夸大了10到90分钟的部分,因为是的,每件事都有其原因,但是在早上8:55分当你还没有喝第一杯咖啡的时候,商务团队敲开你的门,只是为了问:“最新的数据在哪里?”那真是上班每天的噩梦。这就像把所有的辛勤工作都丢进垃圾桶,我自己无法接受那个现实。

让我们回到我说的事情:每件事都有其原因,以及为什么曾经花费我10分钟时间的童话现在变成了一个90分钟的红色恶魔。为了说明这一点,让我们以fct_booking数据表为例。该表包含每天从网站获取的所有预订信息。每个booking_id代表网站上预订的一个订单。

作者提供的图像

每天,大约有4个订单添加到预订表中,该表已经包含80个订单。当使用dbt运行此模型时,它会从上一天删除整个表,并用84条记录替换所有记录,包括旧订单和新订单(80个来自历史累积数据的订单+为最新一天添加的4个新订单)。更加糟糕的是,对于每添加4条新记录,查询时间会增加约0.5秒。

作者提供的图像

现在,想象一下,4个订单相当于每天4000个订单,而实际上80个订单代表了80万条记录。你能猜到将fct_bookings表转换需要多长时间吗?比如说,在3个月后我们会处于什么位置?

嗯,这个数学问题就留给你了。

黄金蛋

所以,在漫无目的地漫游dbt社区的帖子和半心半意地浏览dbt文档之后(我的意思是,谁没有这样做过?),我偶然发现了dbt增量的圣杯。这就像在大堆代码中找到一根金针,只不过这根针是金色的,而代码堆是由代码构成的。

通俗地说,dbt增量意味着您不必费力地从头开始处理所有数据。您只需处理新数据和修改过的数据,节省时间和资源。这就像一个真正有效且不会让您遭老板责备的快捷方式。

作者提供的图片

如果您想了解有关dbt增量的细节,请查看以下博客和文档:

dbt增量模型在大数据中的强大功能

在BigQuery上的实验

towardsdatascience.com

增量模型 | dbt开发者中心

阅读本教程以了解在dbt中构建时如何使用增量模型。

docs.getdbt.com

要在dbt模型中设置此模型,您需要在模型脚本的开头添加一个配置块,并记住以下两个组件:

  • Materialized: 默认情况下,dbt模型的物化视图在没有配置时等于”table”。要设置增量模式,请将物化视图设置为”incremental”。有关其他dbt物化视图的更多信息,请访问:

物化视图 | dbt开发者中心

阅读本教程以了解在dbt中构建时如何使用物化视图。

docs.getdbt.com

  • Unique_key: 尽管根据dbt文档,设置唯一键是可选的,但理性地考虑如何设置这个键非常重要。唯一键将是让dbt知道记录是否应添加或更改的主要驱动程序。一些需要记住的问题是:
  • 唯一键是否真的唯一?
  • 它是否由两个或多个列的组合构成?

如果未设置唯一键,可能会导致数据丢失和值模糊,所以要小心!

以下是设置单个唯一键的配置块示例:

如果唯一键是几列的组合,则可以调整配置为:

注意: 如果您使用BigQuery或Snowflake存储数据,您可能还可以调整更多的额外配置,如设置sync_mode。但由于我们公司的数据库是基于Redshift的,具体是Postgres,我们没有那些花里胡哨的齿轮。

一旦处理完这个,我们只需要在我们的dbt增量模型脚本中添加一个条件块:is_incremental()宏。

is_incremental()宏在满足以下条件时返回True:

  • 目标表已经存在于数据库中。
  • dbt不是在full-refresh模式下运行。
  • 正在运行的模型配置为materialized=’incremental’

请注意,您的模型中的SQL语句需要在is_incremental()评估为TrueFalse时都有效。

回到fct_booking的示例,这是原始查询:

在应用上述增量设置后,我们得到一个包括唯一键、模型标签和is_incremental()宏的条件块的模型,如下所示:

从代码中可以看到,唯一键已设置为booking_id,因为一个booking_id对应一个订单。

为了使其更加华丽,我还添加了一个名为incremental_model的模型标签,用于与增量材料化集成的其他模型。主要原因是,当使用dbt模型增量时,通常会出现大规模的错误。因此,为了刷新它们而不影响其他模型,并且不必记住每个启用增量模式的单个模型,我可以运行上述代码,而不是单独指定每个模型名称。

dbt run — select tag:incremental_model --full-fresh

还要注意,如果增量模型设置不正确并且在生产表中更新了不正确的数据,则需要使用--full-refresh命令再次运行模型。然而,您应该记住,以全量加载刷新而不是增量模式运行它将会更慢,所以要在合适的时间进行操作(提示:不要在早上9点进行)。

反击

直到这一点,生活又变得美好起来!我完美地设置了表格,并且性能查询大幅改善。终于,我可以安心睡觉了。我的手可以触摸到草地,dbt增量授权错过了小Leah——一个梦想成真。然而,不久之后,一位来自财务团队的人冲到我的办公桌前,手里拿着一份报告,激动地声称:“你给我错误的数据!”

事实证明,增量模型意外地跳过了一天中的许多订单,然后进入了下一天。“这到底怎么会发生?我按照专家教程操作——这不可能出错!”我在心里低声说道。除非在上游发生了一些我可能忽略的事情。经过一番调查,问题浮出水面。

每天,都会进行数据提取和加载过程,以同步到那一刻的所有数据。这个同步通常在午夜进行,但其时间可能会受到启动时间和软件包缓存等因素的影响。重要的是要注意,提取过程可能稍晚于午夜开始。

考虑这样一种情况,提取过程从凌晨12:02开始,有人决定在凌晨12:01左右进行预订。在这种情况下,数据还会包括当天的一小部分订单,这在技术术语中称为“迟到数据”。

然而,当前WHERE过滤器的逻辑存在一个问题。由于它只附加来自created_at最新日期值的新记录,所以其效率受到影响。这意味着它不会捕获整天的所有数据。

为了解决这个问题,我们需要稍微调整这个逻辑:

新的过滤器涉及同步过去7天的所有数据。任何新数据都将添加到现有数据集中,而任何具有更新字段值的旧数据将被替换。

权衡

如果您一直在按照步骤进行,您可能会想:“使用is_incremental过滤器应该回溯多少天?为什么我选择了7天?如果我需要最近30天的数据怎么办?”嗯,答案并不简单——它取决于您的具体情况。

在我的情况下,我确保每天至少有一个订单。由于在过去的7天内可能发生数据的内部更改,因此我将过滤器设置为在该时间范围内附加新数据并更新现有数据。然而,如果您对查询性能感到自信,并且希望进一步回溯,比如最近365天的数据,您可以自由选择!只需注意,有一些权衡需要考虑。

使用增量模型的主要原因是在模型运行性能方面降低成本。然而,扫描过去7天的较大数据集可能会降低性能,这取决于数据的大小和您公司的具体用例。根据您的需求,找到合适的平衡是非常重要的。

对于更一般的方法,我建议使用7天作为标准规则。您可以根据dbt增量模型的全量刷新需求,设置每周或每年的数据更新计划。这种方法可以考虑到意外问题,因为无论您的设置有多好,偶尔的停机时间仍然可能发生。

在我的使用案例中,我通常会在周末进行全量刷新的增量运行,因为这时候的运营任务较少。然而,这个计划可以根据您团队的要求进行定制。

记住,关键是在数据的新鲜度和查询性能之间找到合适的权衡,确保数据保持准确和最新,同时优化模型的效率。

Leave a Reply

Your email address will not be published. Required fields are marked *