进阶 dagster.io 2026-10-10 01:48:56 · 6 阅读

第12章 数据与ML中的Backfill(回填)快速入门

第12章 数据与 ML 中的 Backfill(回填)快速入门

一份手把手指南,教你用 backfill 和分区(partition)简化数据管理,适合数据工程师和 ML 工程师。

对数据工程师来说,也许没有什么比 backfill 更让人又敬又怕了。搞砸一次 backfill,你会得到脏数据、愤怒的业务方,还有一笔高昂的云账单;做好一次 backfill,你就能收获几个月甚至几年的干净数据——直到下一次 backfill。Backfill 是数据工程师核心技能的一部分,它就像一座熔炉,把这份工作中所有的单项挑战熔炼成一个大挑战。

Backfill 经常出问题:可能不小心选错了目标数据;可能中途失败,却难以从断点恢复,只能从头再来;也可能让数据陷入不一致的状态——相关表之间的记录对不上。而且赌注很高:一次大型 backfill 从头重跑,可能意味着多花上万美元的云计算费用,还得忍受好几天的数据过期。

本文将全面介绍 backfill:它是什么、为什么需要它、难在哪里,以及如何应对这些难点。

Backfill 通常和分区(partition)密不可分——分区是一种数据管理方式,能让 backfill 变得简单、可控得多。本文也会深入讲解如何借助分区避开 backfill 的主要坑。

本文目录

什么是 backfill? 为什么需要 backfill? 对数据资产图进行 backfill Backfill 与分区 Backfill 的常见翻车场景 执行 backfill:一步步指南

什么是 backfill?

Backfill 指的是:把一个平时增量更新的数据资产,对其中的历史部分进行更新。

举个例子:你有一张表,每天往里写入当天发生的事件记录。对这张表做 backfill,就是把过去某些日期的数据补齐或覆盖。

我们用"数据资产"而不是只说"表",是因为并非所有管道处理的都是表格数据。你同样可以对一组用于训练视觉模型的图片做 backfill,或者对一组 ML 模型做 backfill。

为什么需要 backfill?

通常在以下几种情况下你需要做 backfill:

绿地场景(Greenfield)——你在数据管道中新增了数据资产

你刚开发出一个新的数据资产,比如写了一段代码,把一张原始数据表转换成一张更干净的数据表。正常运行时,这个资产会用最新数据增量更新,但你需要先用历史数据把它初始化。

棕地场景(Brownfield)——管道中的某个资产发生了变化

你修改了生成某个数据资产的代码,比如修复了过滤逻辑里的 bug;或者上游数据源发现数据损坏,发布了修正后的数据。这时你需要把用坏数据或有 bug 的代码生成的历史数据全部丢弃,用正确的数据重新覆盖。

从故障中恢复

你拉取数据的服务宕了一周,或者发现管道因为不认识新加的记录类型而失败了一周。问题修复之后,你需要一次 backfill 来填补数据缺口。

对数据资产图进行 backfill

很多时候,你要 backfill 的资产还有其他派生资产。这种情况下,backfill 完某个资产后,通常还需要 backfill 它的下游资产。比如你 backfill 了 raw_events 表,而 events 表是由 raw_events 表生成的,那你也得 backfill events 表。

编排器(Orchestrator)通常负责跟踪数据管道中的依赖关系,在管理这类 backfill 时特别有用:上游表 backfill 完成后,它可以自动触发下游表的 backfill。

Backfill 与分区

如果用分区的思路来看待数据,backfill 会变得容易得多。

分区是一种增量数据管理方式,它把每个数据资产视为一组分区的集合。例如,一张事件表可以对应一组按小时划分的分区,每个分区包含一个小时内发生的事件。每小时结束时,你填入该小时的分区;做 backfill 时,则覆盖过去某些小时的分区。

分区之所以有助于 backfill,有以下几个原因:

哪些数据需要 backfill?

分区让你能追踪哪些数据需要 backfill、哪些不需要。每个分区都是一个原子单位,记录了它是在什么时间用什么代码更新的。这样你就可以做出如下判断:

这个分区缺失,需要 backfill。 这个分区是用过时的逻辑构建的,需要 backfill。 这个分区是用最新逻辑构建的,不需要 backfill。

相比一大团由不同时间、不同代码、不同源数据反复更新堆出来的数据,分区化的数据资产是一组易于单独推理的对象,也就是一个个分区。

分区还能帮你确定下游资产需要 backfill 哪些部分。比如你发现 events 表中某个日期范围内的数据损坏了,对这段日期做了 backfill,那么所有依赖 events 表的其他表,大概率也需要对同样的日期范围做 backfill。

分区、并行与容错

当生成数据的代码是单线程的,分区能让你把 backfill 拆成小块并行执行。

Backfill 往往需要大量计算。Snowflake、Spark 这类大规模并行计算引擎可以把计算分发到多台机器上,并在部分失败时自动恢复。

但如果你的资产是用一段从 REST 接口取数据的 Python 循环,或者一组 Pandas DataFrame 变换来生成的,那么在很大的历史时间范围上跑一遍可能要几天甚至几周,或者直接内存不够跑不完。中途一旦失败,很可能就得从头再来。

对于这种单线程代码,更常见的做法是为每个分区跑一个任务,而不是一次性执行整个 backfill。这样 backfill 可以更快完成,因为各分区能并行填补;容错性也更好——如果处理某个分区的任务失败了,重启它即可,不会影响其他分区的进度。

不过情况并非总是如此。如果你的数据是在 Snowflake 里跑一条 SQL 查询得到的,通常让 Snowflake 自己处理并行化更省事,拆成多条查询反而增加额外开销。要不要按分区并行做 backfill,很大程度上取决于你用的计算框架——Pandas、Spark 还是 Snowflake。正因如此,能灵活支持两种方式非常有价值。

想了解更多关于数据分区的内容,可以阅读 Sandy 的文章《数据管道中的分区》(Partitions in Data Pipelines)。

Backfill 的常见翻车场景

Backfill 可能在以下几个方面出问题:

选错目标范围——如果漏掉了本该 backfill 的数据,你可能错误地以为数据已经是最新的;如果 backfill 了不需要 backfill 的数据,就白白浪费时间金钱;如果 backfill 了某个资产却没 backfill 它派生出来的资产,数据可能陷入不一致、令人困惑的状态。

资源过载——Backfill 可能需要大量内存和算力,可能压垮你的系统,或者挤占重要工作负载的资源。

成本失控——一次大型 backfill 的花费可能远超预期。

中途迷失——如果 backfill 的部分任务失败,你可能陷入"知道出了问题但不知道是什么问题"的状态,只能从头重跑。

要避免这些问题,必须仔细规划和执行 backfill。

执行 backfill:一步步指南

好了,你决定要跑一次 backfill。该怎么做?把它拆成几个步骤来思考会很有帮助:

第 0 步:用便于 backfill 的方式管理数据

这通常意味着用分区来组织数据、追踪数据变化。还要记录生成数据所用的代码和上游数据,这样你才知道哪些部分已过时。最后,要追踪数据资产之间的依赖关系,以便判断某次变更会影响哪些资产。

第 1 步:规划 backfill

你需要 backfill 哪些数据资产(表和文件)?需要覆盖这些资产的哪些分区?第 0 步做得越好,这一步就越轻松。

你的 backfill 策略是什么?是跑一条大 Snowflake 查询?还是每个分区一个任务?或者对不同资产用不同策略?

如何避免压垮系统?你可能需要申请额外资源,或者配置队列来限制并发量。

如果不使用编排器,你可能需要写一个脚本来执行 backfill。

第 3 步:启动 backfill

这一步可能是执行一个脚本,也可能是清除一组 Airflow 任务。如果你用的是 Dagster,可以在 UI 里选中要 backfill 的资产和分区,或者向 GraphQL API 提交请求。

如果你没有用编排器管理 backfill,这一步可能需要分散进行——backfill 的部分完成后,你需要手动触发下一步。

第 3a 步:从小规模开始——如果 backfill 规模较大,先跑一小部分(比如几个分区)验证是否符合预期。

第 4 步:监控 backfill

Backfill 可能需要数小时甚至数天才能完成,期间可能出现很多问题。你需要关注失败情况并重新启动失败的任务;如果发现逻辑有误,可能得从头重跑或重做其中一部分。

第 5 步:验证结果

工作完成不等于结果符合预期。你需要验证 backfill 后的数据是否如你所料,可以手动跑查询,也可以执行数据质量检查。

有反馈或问题?欢迎到 Slack 或 GitHub 上发起讨论。

想和我们一起工作?查看我们的在招职位。

想要更多这类内容?请在 LinkedIn 上关注我们。

评论 (0)