Skip to content

离线写入怎么做幂等 ​

难度与前置

难度:中级
前置:分层、分区。
读完能做什么:同分区重跑结果稳定;能把「重复」拆到源、Join、写入、粒度四类。

适用场景 ​

  • 调度失败重跑、补数、上游迟到重刷
  • 发现主键重复或指标翻倍

不适用:业务上合法的多次事件(多次支付尝试)——先定义粒度再谈去重。

真实场景(一条) ​

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. 重复数据四来源(有序排查) ​

  1. 源头重复:ODS 同主键多行
  2. Join 放大:粒度没对齐
  3. 重跑重复写入:APPEND 第二次
  4. 目标粒度错误:把合法多事件当成重复
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) 可临时止血,必须留下根因与修复,否则下次重跑还来。

常见坑 ​

  1. 失败重跑改用 INSERT INTO
  2. 动态分区回刷范围过大,覆盖错日
  3. 用 DISTINCT 掩盖 Join 爆炸
  4. 补数只改 ADS、不重跑 DWD,口径分叉
  5. 多作业写同一分区无互斥:例如补数作业与日常调度同时 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 幂等/重复数据原创;无未公开公司数据。