数据写入如何设计幂等?
考察意图
数据链路几乎都是至少一次投递:任务会重试、会补数、消息会重复。考察候选人会不会把「同一批次跑一次和跑十次结果一致」设计进写入路径,而不是出事故后再手工删重。
回答框架
- 先定义:幂等是「相同输入重复执行,目标状态不变」,不是「少写一次」。
- 说清粒度:按消息/请求、按业务主键、按分区日、按批次号,粒度选错就会假幂等。
- 给出常用落地:分区覆盖、MERGE/UPSERT、去重表、下游按幂等键。
- 补失败收口:半成功怎么回滚或覆盖,迟到数据怎么更新而不叠加。
参考答案(口述版)
一句话:幂等写入保证「重试和补跑安全」——同一逻辑键执行一次和执行多次,落地表里的最终状态一样。
为什么必须做:调度失败重试、上游至少一次、手工回刷历史,都会把同一批数据再送一遍。如果写入是无脑 append,就会重复计数、重复订单、重复推送。
先定幂等键,再选写入模式:
1. 分区覆盖(离线数仓最常用):目标表按 dt(或 dt+小时)分区,作业对「自己负责的分区」做 INSERT OVERWRITE。同一天重跑只覆盖当天,不影响别的分区。适合可按时间窗完整重算的 T+1 任务。
2. 主键 UPSERT / MERGE:明细入湖、维表、状态表常用。按业务主键(订单号、user_id+sku)匹配,有则更新无则插入;不要用自增 id 当幂等键。
3. 去重落地:先按幂等键取最新(ROW_NUMBER() OVER (PARTITION BY biz_key ORDER BY updated_at DESC)),再覆盖目标。适合 CDC/日志里同一主键会来多版本。
4. 请求级幂等:对外接口或消息消费带 idempotency_key / event_id,先查幂等表再写业务表,写成功才标记完成。Kafka 至少一次 + sink 端按 event_id 去重,是工程里最常见的「近似恰好一次」。
半成功要有收口:覆盖写要把「写分区」当成原子单元;UPSERT 要可重入;跨多表用事务或「先写可重建的明细、再汇总」。迟到数据用主键更新,而不是再 insert 一行。
口述收尾:先说「至少一次是常态」,再说「幂等键 + 可覆盖的目标」,最后主动提分区覆盖和 MERGE 两种最常用手段。
关键词
面试官可能的追问
追问 1:至少一次、至多一次、恰好一次分别怎么落地?
至少一次:重试 + 幂等写入,数据工程默认选这个,丢失比重复更难修。至多一次:失败不重试,会丢数,只适合可容忍缺口的监控旁路。恰好一次:要事务/两阶段提交或源端幂等生产+sink 幂等(如 Kafka idempotent producer + transactional sink),成本和约束都更高。多数数仓日更用「至少一次 + 分区覆盖」就够用。
追问 2:分区覆盖和 MERGE 怎么选?迟到数据怎么办?
能按时间窗完整重算(日更 ODS/DWD)用分区覆盖,语义最简单、回刷最快。目标是当前状态、上游是变更流(订单状态、维表)用 MERGE。迟到数据:覆盖模式把迟到日所在分区重跑;MERGE 模式用业务时间和更新时间取最新版本,禁止对同一主键再插一行。
常见踩坑
- 把幂等理解成「不要重试」,结果失败就丢数
- 用自增主键或写入时间当去重键,重跑会当成新行
- 只覆盖一张明细表,下游汇总仍是累加,整条链路并不幂等
记忆锚点
幂等不是少写一次,是同一键重跑后状态不变。