外观
离线写入怎么做幂等
适用场景
- 调度失败重跑、补数、上游迟到重刷
- 发现主键重复或指标翻倍
不适用:业务上合法的多次事件(多次支付尝试)——先定义粒度再谈去重。
真实场景(一条)
dwd_ad_spend_detail_di 首次跑到一半失败,第二次用 INSERT INTO 重跑,消耗翻倍,对账崩盘。改为按 dt INSERT OVERWRITE 后,同分区连续跑两遍金额一致。
怎么做(步骤清单)
1. 先定义「重复」与粒度
写清:一行代表什么、业务主键、时间/分区范围。没有唯一键,COUNT(*) 与 COUNT(DISTINCT) 的差没有业务意义。
2. 优先:分区级幂等写入
sql
INSERT OVERWRITE TABLE dwd_ad_spend_detail_di PARTITION (dt = '${biz_date}')
SELECT bill_id, channel_id, cost_amount, event_ts, is_valid, filter_reason, current_timestamp() AS etl_ts
FROM tmp_dwd_ad_spend_detail
WHERE biz_date = '${biz_date}';同 dt 重跑 = 整分区替换,不累加。
3. 无法 overwrite 时的替代
| 手段 | 何时用 |
|---|---|
先 DROP/ALTER DROP PARTITION 再写 | 引擎对 overwrite 支持差 |
| Merge / Upsert 按主键 | 湖仓主键表 |
| staging 表写完再交换 | 大分区避免读到半成品 |
staging 交换最小示意(大分区避免下游读到写一半的数据):
sql
-- 1) 写入 staging(可 overwrite 多次,不影响正式表)
INSERT OVERWRITE TABLE dwd_ad_spend_detail_di_stg PARTITION (dt = '${biz_date}')
SELECT ... FROM tmp_dwd_ad_spend_detail WHERE biz_date = '${biz_date}';
-- 2) 校验 staging 通过(主键唯一、金额非空等)后再交换
-- Hive 示意:删正式分区 → 将 staging 分区「改名/复制」为正式分区
-- (具体用 ALTER EXCHANGE / INSERT OVERWRITE 正式表 SELECT * FROM stg,按引擎选一种)
INSERT OVERWRITE TABLE dwd_ad_spend_detail_di PARTITION (dt = '${biz_date}')
SELECT * FROM dwd_ad_spend_detail_di_stg WHERE dt = '${biz_date}';原则:正式分区只在「整份就绪」后亮相;失败停在 staging,下游仍读旧正式分区。
4. 重复数据四来源(有序排查)
- 源头重复:ODS 同主键多行
- Join 放大:粒度没对齐
- 重跑重复写入:APPEND 第二次
- 目标粒度错误:把合法多事件当成重复
sql
SELECT bill_id, COUNT(*) AS cnt
FROM dwd_ad_spend_detail_di
WHERE dt = '${biz_date}'
GROUP BY bill_id
HAVING COUNT(*) > 1
LIMIT 50;对 ODS 跑同样 SQL;Join 前后分别 COUNT 看是否爆炸。
5. 补数契约
- 补数列表:
dt in (...) - 每分区 overwrite,并级联下游同
dt - 禁止只补 ADS 不补 DWD
6. 去重是止血不是根治
ROW_NUMBER() OVER (PARTITION BY bill_id ORDER BY etl_ts DESC) 可临时止血,必须留下根因与修复,否则下次重跑还来。
常见坑
- 失败重跑改用
INSERT INTO - 动态分区回刷范围过大,覆盖错日
- 用
DISTINCT掩盖 Join 爆炸 - 补数只改 ADS、不重跑 DWD,口径分叉
- 多作业写同一分区无互斥:例如补数作业与日常调度同时
INSERT OVERWRITE同一dt,后写覆盖先写,或一个 APPEND 一个 OVERWRITE,结果非确定。约定「单分区单写入者」:调度互斥锁 / 队列串行,或明确主写作业 + 禁止并行补数。
如何验收(可量化)
- [ ] 同分区连续跑两遍,主键行数与核心金额一致
- [ ] 文档写明写入模式:overwrite / upsert
- [ ] 补数工单含下游级联清单
- [ ] 重复告警能指到四来源之一
sql
SELECT SUM(cost_amount) AS cost_sum, COUNT(*) AS cnt, COUNT(DISTINCT bill_id) AS bill_cnt
FROM dwd_ad_spend_detail_di
WHERE dt = '${biz_date}' AND is_valid = true;
-- 重跑后再跑一遍,三列应一致相关面试题
题库小程序链接待定;先用站内锚点占位。
- [ ] 题:同分区连续跑两遍如何验收幂等?(考察点:主键行数与核心金额一致)→ 占位:
#todo-interview-idempotent-double-run - [ ] 题:文档必须写明哪种写入模式?(考察点:overwrite/upsert 写清)→ 占位:
#todo-interview-idempotent-write-mode - [ ] 题:补数工单缺什么容易漏刷?(考察点:下游级联清单)→ 占位:
#todo-interview-idempotent-cascade-list - [ ] 题:重复告警应能指到哪类来源?(考察点:四来源之一可定位)→ 占位:
#todo-interview-idempotent-four-sources
改写说明:综合 Wood 幂等/重复数据原创;无未公开公司数据。