第 13 部分. 增量、快照与历史
数据历史是隐藏语义漂移的主要来源之一。表格在「今天」可能是正确的,但如果昨天的状态无法恢复,则不适合审计。在银行业务背景下,这一点尤其重要:报告、授权、逾期和风险标志都必须是可复现的。
三种历史模式
- Full refresh(全量刷新) — 从源端重新构建所有内容。适合教学示例,但不适合大型生产表。
- Incremental model(增量模型) — 添加或更新已更改的行。需要策略、唯一键和重放策略。
- Snapshot(快照) — 随时间存储属性的变化。对于状态、分群、授权状态和缓慢变化的维度很有用。
在教学示例中,我们不会用增量逻辑把所有模型都复杂化,但应该理解在生产环境中它会出现在哪里。
规范中应写什么
对于具有历史的模型,规范应回答:
- 哪个键定义一行;
- 哪个字段定义事件或变更的时间;
- 是否可以重新计算过去;
- 对于延迟到达的数据应如何处理;
- 哪个命令证明重放是安全的。
不好的表述:
做增量加载。
好的:
模型使用 `transaction_id` 作为唯一键,使用 `transaction_date` 作为事件日期。7 天内到达的延迟行可以更新聚合。更旧的更正需要 PatchSpec 和审阅者的确认。
验证事实
最低限度的事实:
- 在不更改 raw 的情况下重复运行
dbt build不会更改结果; - 唯一键不重复;
- 事件日期不为
null; - 重放窗口已描述;
- 审阅者手动确认历史的语义未发生变化。
Qwen 查询
阅读 specs 和 marts。
找出需要历史、增量策略或快照的模型。
对每个模型返回键、事件日期、重放策略和未决问题。
不要更改 SQL。
最小输出
完成本章后创建笔记:
# 历史策略
## 客户属性
## 卡片交易
## 贷款
## Open API 授权
## 重放与延迟数据
如果模型不具有历史性,请明确写出原因。
读者解读
数据历史看起来像一个技术话题,直到出现「我们当时知道什么?」这样的问题。对于报告和风险来说,这是至关重要的。今天的重算可能会给出正确的当前表格,但给出错误的历史答案。如果昨天的授权是激活的,而今天被撤销,简单的状态重写将抹去一个重要事实。
增量模型、快照和 full refresh 不只是记录表格的不同方式。它们是关于时间的不同承诺。Full refresh 表示:从源端重新构建当前状态。Incremental model 表示:根据键和重放窗口添加或更新已更改的行。Snapshot 表示:随时间保存属性的变化。它们之间的选择必须在实现之前记录下来,否则智能体会选择最方便的方案。
延迟到达的数据尤其危险。如果上周的操作今天才到达,是否可以更改已构建的聚合?如果可以,更改哪个时间段?如果不可以,在哪里记录异常?这些问题不能凭灵感在 is_incremental() 内解决。它们属于契约,因为它们会改变结果的可复现性。
教学示例不必实现所有历史模式。但读者应该学会看出在生产环境中哪里需要历史。对于客户,这是属性和分群。对于支付 — 操作时间和延迟到达。对于授权 — 激活与撤销。对于贷款 — 阶段(stage)和逾期。即使第一版保持为简单的 full refresh,良好的规范至少应指出这些边界。
实践
选择一个源,决定它需要 full refresh、incremental model 还是 snapshot。记录键、事件日期、重放策略以及如何处理延迟数据。
典型错误
以为 materialized='incremental' 解决了历史问题。这只是写入模式。历史的语义由键、时间戳、重放策略和审阅者的事实决定。
检查问题
- incremental model 与 snapshot 有什么区别?
- 哪些事实证明重放是安全的?
- 为什么延迟到达的数据需要在 SQL 之前描述?