重跑一次数据翻倍了——幂等性设计
本文目录
先说结论
幂等性=多次执行结果一致。ETL实现幂等性有三种方法:① INSERT OVERWRITE覆盖重写(最简单最推荐);② DELETE+INSERT先删后插;③ MERGE INTO增量更新(最精细但最复杂)。所有生产ETL都必须是幂等的——因为失败重试、手动重跑、Backfill回溯都会导致任务被多次执行。
先把这段讲顺;面试官追问时,再展开下面的原理和细节。
详细讲解
幂等性(Idempotency)是ETL设计中最重要的原则之一——同一个任务执行多次和执行一次的结果应该完全一样。在某元宇宙社交公司做实时数仓时,有一次DWD层的ETL任务失败后手动重跑,结果当天的订单数据翻倍了——因为用的是INSERT INTO(追加写入),重跑又插入了一遍。如果用INSERT OVERWRITE(覆盖写入)就不会有这个问题。幂等性不是可选的,是生产环境的硬性要求。
非幂等的危害
-- 非幂等写法(INSERT INTO追加)
INSERT INTO dwd_orders
SELECT * FROM ods_orders WHERE dt = '2024-01-15';
-- 第1次执行:插入了10000条记录 => 表里10000条
-- 任务失败重跑(或手动重跑)
-- 第2次执行:又插入了10000条 => 表里20000条!
-- 数据翻倍了!
-- 如果重跑了3次 => 30000条
-- 下游的聚合报表(SUM/COUNT)全部翻了3倍
-- 运营看到'今天订单量暴涨200%'以为是大促效果
-- 实际上是ETL重跑导致的数据重复
方法一:INSERT OVERWRITE——最简单最推荐
-- 幂等写法(INSERT OVERWRITE覆盖)
INSERT OVERWRITE TABLE dwd_orders PARTITION (dt = '2024-01-15')
SELECT * FROM ods_orders WHERE dt = '2024-01-15';
-- 第1次执行:写入10000条
-- 第2次执行:先删除dt=2024-01-15分区的所有数据
-- 再写入10000条
-- 结果:还是10000条,和执行1次一模一样!
-- INSERT OVERWRITE的本质:
-- 1. 删除目标分区的所有数据
-- 2. 写入新数据
-- 等价于 先DELETE再INSERT,但是原子的
-- 适用场景:按分区覆盖的ETL(90%的数仓ETL都是这个模式)
-- 不适用:没有分区的表、需要保留历史版本的表
方法二:DELETE+INSERT——显式先删后插
-- 先删除目标数据,再插入
-- 适合没有分区的表或需要精细控制的场景
DELETE FROM dwd_user_profile WHERE update_date = '2024-01-15';
INSERT INTO dwd_user_profile
SELECT * FROM ods_user_profile WHERE dt = '2024-01-15';
-- 注意:DELETE和INSERT不是原子的!
-- 如果DELETE成功但INSERT失败,数据就丢了
-- 解决方案:
-- 1. 用事务包裹(支持事务的数据库)
-- 2. 先INSERT到临时表,验证后再RENAME(更安全)
-- 更安全的写法:
-- DROP TABLE IF EXISTS dwd_user_profile_tmp;
-- CREATE TABLE dwd_user_profile_tmp AS
-- SELECT * FROM ods_user_profile WHERE dt = '2024-01-15';
-- -- 验证tmp表数据正确后
-- ALTER TABLE dwd_user_profile_tmp RENAME TO dwd_user_profile;
方法三:MERGE INTO——增量更新(最精细)
-- MERGE INTO(Delta Lake/Databricks/Doris等支持)
-- 存在则更新,不存在则插入
MERGE INTO dwd_user_profile AS target
USING ods_user_profile AS source
ON target.user_id = source.user_id
WHEN MATCHED THEN
UPDATE SET target.name = source.name,
target.email = source.email,
target.update_time = source.update_time
WHEN NOT MATCHED THEN
INSERT (user_id, name, email, update_time)
VALUES (source.user_id, source.name, source.email, source.update_time);
-- MERGE INTO天然是幂等的:
-- 重跑时已存在的记录执行UPDATE(覆盖为相同值)
-- 不存在的记录执行INSERT(只插入一次)
-- 无论执行多少次,结果都一样
-- 适用场景:维度表更新(SCD Type 1)、用户画像增量更新
-- 我在出海硬件品牌迁移到Databricks后大量使用MERGE INTO
-- 代码量比原来的UNION全量覆盖减少70%
三种方法对比
| 方法 | 原理 | 幂等性 | 性能 | 适用场景 |
|---|---|---|---|---|
| INSERT OVERWRITE | 覆盖写入整个分区 | 天然幂等 | 最好 | 按分区的事实表ETL |
| DELETE+INSERT | 先删后插 | 幂等(需事务保证) | 中 | 无分区表或精细控制 |
| MERGE INTO | 匹配则更新否则插入 | 天然幂等 | 较好 | 维度表增量更新 |
实战踩坑:重跑导致订单数据翻倍
在某元宇宙社交公司,DWD层的虚拟礼物订单表用的是INSERT INTO写入(当时没注意幂等性)。有一天凌晨ETL跑到一半网络超时失败了,Airflow自动重试成功。但第一次执行已经写入了一半的数据(约5000条),重试又写了一遍完整的10000条——其中5000条重复了。
结果:当天的礼物流水金额报表多了50%。运营以为是大促效果,差点多发了一波奖金。
修复:① 手动删除重复数据(用ROW_NUMBER去重);② 把所有INSERT INTO改成INSERT OVERWRITE PARTITION;③ 建立数据质量校验——每天检查关键表的行数是否在合理范围内(波动超过20%告警)。
教训:生产环境的ETL必须是幂等的。INSERT OVERWRITE是最简单的幂等方案——所有按分区的ETL都应该用它。如果你的ETL用了INSERT INTO,现在就去改成INSERT OVERWRITE。
幂等性的边界情况
时间戳字段:如果ETL中有current_timestamp()这样的函数,每次执行时间不同,严格来说不是完全幂等。但通常这不影响业务正确性——因为时间戳只记录'最后一次处理时间'。如果需要严格幂等,用execution_date参数化时间。
自增ID:如果目标表有自增ID,重跑后同一条数据的ID会不同。如果下游依赖了这个ID就有问题。建议:不要用自增ID作为业务标识,用业务主键(user_id+order_id+dt等组合键)。
面试高频追问
追问一:INSERT OVERWRITE的分区是动态的怎么办?
Hive支持动态分区OVERWRITE:SET hive.exec.dynamic.partition.mode=nonstrict; INSERT OVERWRITE TABLE t PARTITION(dt) SELECT ..., dt FROM source; 动态分区会只覆盖本次涉及的分区,不影响其他分区的数据。Spark SQL也支持类似机制。
追问二:Flink实时任务怎么保证幂等?
Flink写入外部存储的幂等方案:① 写MySQL/Doris用UPSERT(INSERT ON DUPLICATE KEY UPDATE);② 写Kafka用Exactly-Once语义(事务Producer);③ 写S3/HDFS用Checkpoint+两阶段提交。Flink的Checkpoint机制保证了从故障恢复后重发的数据也是幂等的。
追问三:怎么验证ETL是不是幂等的?
最简单的方法:同一个ETL对同一份输入数据跑两次,比较两次的输出是否完全一致(行数相同、聚合值相同)。可以写一个自动化脚本:① 跑一次ETL记录输出行数和SUM;② 再跑一次记录输出;③ 比较两次结果。不一致就说明不幂等——去查INSERT INTO还是INSERT OVERWRITE。
总结
幂等性面试讲清楚三种方法:① INSERT OVERWRITE(覆盖分区,最简单最推荐,90%的ETL用这个);② DELETE+INSERT(先删后插,需要事务保证原子性);③ MERGE INTO(匹配更新否则插入,适合维度表增量更新)。幂等性是生产ETL的硬性要求——因为失败重试、手动重跑、Backfill都会导致多次执行。能讲出INSERT INTO导致数据翻倍的踩坑和改INSERT OVERWRITE的修复方案,面试官会觉得你有数据质量意识。
下一篇聊DAG设计最佳实践:Airflow的DAG写了500行没人看得懂——怎么写出可维护的DAG?
你们ETL做了幂等性设计吗?用什么方式?评论区聊聊~
关注「Wood聊大数据」| 9年大数据老兵,聊技术、聊职场、聊成长
调度任务管理 面试通关手册
第6-10篇:DAG设计与运维实战
Wood聊大数据
9年大数据老兵,聊技术、聊职场、聊成长