用户点了“删除账号”,接口返回 200,文档写“数据已删除”。实际上:主库那行没了,备份里还在,搜索索引还能搜到,宽表还在,日志里留着手机号。半年后有人问“真的删了吗”,没人答得上来。
保留与归档的分岔点不是“要不要留”,而是删除请求要覆盖哪些位置:答不上“一共几处存在这份数据”,删除就只删了看得见的那一处。
1. "删除"从来不是一次操作
| 位置 | 常见遗漏原因 | 删除难度 |
|---|---|---|
| 主库表 | 关联表、历史表未删 | 低 |
| 备份与快照 | 不可改,只能等过期 | 高 |
| 搜索/缓存索引 | 异步同步,有延迟 | 中 |
| 仓库与宽表 | CDC 复制,源删不移 | 高 |
| 日志与遥测 | 文本带 PII,无人扫 | 高 |
| 派生数据(报表、样本) | 脱敏但可反推 | 高 |
先做清单比先写脚本重要:漏一项,后面都有缺口。
判据:画不出这份数据的流转图(写到哪、复制到哪、谁加工),就不要宣称支持删除。
2. 保留期由三个来源叠加,取最严的那个
| 来源 | 典型要求 | 与谁冲突 |
|---|---|---|
| 业务需要 | 能查近 N 个月订单 | 成本:越长越贵 |
| 合规规定 | 某些数据必须留存 N 年 | 删除请求:不能删 |
| 诉讼与审计保全 | 争议期禁止删除 | 合规的最短期限 |
最终保留期取三者最长。常见错误是只按业务定,纠纷时发现数据已删;另一种是把合规要求套到所有数据上,成本翻倍。
冲突要显式登记:哪类数据因哪条要求留到何时、谁批准例外。口头例外会变成永久保留。
3. 软删与硬删:两种语义,别混用
软删:置 deleted_at,读写都过滤,物理行保留
用途:误删恢复、审计留痕、外键完整性
风险:所有查询必须过滤,漏一处就是数据泄漏
硬删:DELETE 或按分区 DROP
用途:合规删除、大表清理
风险:不可恢复,必须确认备份与派生数据也覆盖
最危险的是“软删当成硬删”:对外说已删除,实际只打了标记,任一处漏了过滤就还能读到。合规意义上软删≠删除。
判据:对外承诺“已删除”的路径必须走硬删 + 派生清理;软删只用于内部容错,且应在一周内转成硬删。
4. 三种归档形态与它们的代价
| 形态 | 保留什么 | 可查询性 | 成本 |
|---|---|---|---|
| 原表归档 | 完整行,同库异表 | 高,SQL 直查 | 存储最贵 |
| 列存/仓库归档 | 完整行,压缩列式 | 中,适合聚合 | 低,查询另建 |
| 文件归档 | 压缩包或 Parquet | 低,需取回解析 | 最便宜,取回最慢 |
取决于归档后还有没有人查、多久查一次:一年查一次,文件归档足够;每周对账要查,等于每次写一个取回任务。
最常见的失误是没写取回路径:归档时觉得“反正不会查”,出事时要解压几十 GB 才查一行;取回脚本要随归档交付。
5. 可查询性:归档之后还能查什么
| 需求 | 可行做法 | 不可行的做法 |
|---|---|---|
| 查单条明细 | 主键索引 + 取回 | 每次全量解压 |
| 范围聚合 | 仓库预聚合表 | 直接扫归档 |
| 客服查历史 | 冷查询入口 + 权限 | 混在在线接口 |
| 审计导出 | 批量导出任务 | 逐条点查 |
关键是点查与聚合:点查要索引与快速定位,聚合要列存。一套归档满足两者,成本接近保留全量。
判据:归档前先统计“归档后还会执行的查询类型”,超过两类说明形态选错了。
6. 备份里的删除:只能等过期
备份不可变是设计目标,所以删除在备份侧的生效方式是等过期,而非“删掉备份里那条”。
可行:缩短备份保留期 + 加密密钥按期轮换销毁
记录"某时间点之后写入的备份不含该用户数据"
不可行:修改已有备份、逐条删除备份中的记录
合规上通常接受“备份在 N 天后自然过期”,前提是N 写进对外承诺。写“立即删除”、实际 35 天才过期,是最容易被指出的差距。
密钥销毁是等效手段:备份加密,销毁密钥即不可读;前提是密钥管理真的按期执行。
7. 日志与遥测:最容易漏的一处
| 位置 | 为什么带 PII | 处理 |
|---|---|---|
| 应用日志 | 打印完整请求体 | 结构化脱敏,禁整包打印 |
| 网关日志 | URL 带手机号、token | 参数白名单,其余丢弃 |
| 错误追踪 | 栈带上下文变量 | 采集端过滤字段 |
| 指标标签 | user_id 当 label | 禁止高基数标签 |
难处在已写下去无法追溯清理,所以治理只能在采集端:敏感字段写入前脱敏,保留期短于业务数据。
判据:日志保留期不应长于业务数据。日志 90 天、业务 3 年,意味着删除在日志侧要等 90 天,这数字必须能解释。
8. 派生数据:删了源,副本还在
必须一并处理的派生位置:
1 缓存(Redis / 本地缓存) → 按 key 删 + 版本号失效
2 搜索索引 → 按文档 id 删,再触发重建
3 数仓宽表与汇总表 → 按分区重算,或整分区重建
4 报表快照与导出文件 → 记录生成批次,按批次清理
5 模型训练样本与特征库 → 记录样本来源,支持按用户撤回
6 消息队列的滞留消息 → 消费即删,或按 TTL 过期
第 5 项最难:样本混入数据集后,“撤回”需样本级溯源;做不到只能承诺“后续不再使用”,并写进说明。
判据:派生数据能否删除取决于是否记录来源 id;没有标记,删除只能整批重算。
9. 执行链路:一次删除要经过几步
1 受理:校验请求合法性,落一条删除工单(含请求时间与范围)
2 标记:主库软删,立即停止对外可见
3 传播:发出删除事件,各消费者幂等处理
4 硬删:按分区或按主键清理主库与历史表
5 派生清理:缓存、索引、宽表、导出文件
6 备份:记录"该时间点之后的备份不含此数据"
7 回执:工单置完成,保留执行日志(不含被删内容)
第 3 步事件必须幂等可重放:漏消费时要能重放,不能人工补。
判据:整条链路要能在无人干预下重跑;靠人工补的流程,第一次漏就永远漏。
10. 验证:怎么确认真的删了
-- 抽查脚本的骨架:按位置逐个确认,结果为 0 才算完成
SELECT COUNT(*) FROM orders WHERE user_id = :uid; -- 期望 0
SELECT COUNT(*) FROM orders_hist WHERE user_id = :uid; -- 期望 0
-- 搜索索引与缓存用各自的客户端按 id 校验存在性
-- 数仓按分区重算后校验汇总值
验证要留样本与时间戳,形成可复核证据链。只查主库等于没验证。
判据:每个位置都要有一条可执行断言,而非“人工确认过”,写进工单完成条件。
11. 配置与审计:把保留期写成代码
retention:
order: { keep: 3y, basis: "business", archive: columnar, query: point }
payment: { keep: 10y, basis: "compliance", archive: original, query: audit }
app_log: { keep: 30d, basis: "ops", pii: scrubbed_at_ingest }
trace: { keep: 7d, basis: "ops", sample: 0.1 }
training_set: { keep: 1y, basis: "product", revocable: true }
配置化的价值不是省事,而是可审计:有版本、有变更记录、有批准人。散在各服务的常量无法审计,也答不出为什么这张表留 5 年。
变更要留三字段:改了什么、依据哪条要求、谁批准。没有依据的保留期,半年后没人敢缩短。
12. 成本与合规的平衡
| 手段 | 省什么 | 代价 |
|---|---|---|
| 缩短热存期 | 存储与备份成本 | 回溯窗口变短 |
| 分层归档 | 存储单价 | 取回延迟与运维面 |
| 聚合后删明细 | 存储最多 | 失去单条追溯 |
| 加密 + 销毁密钥 | 备份成本 | 密钥管理成单点 |
第三项最易被误用:为省存储删明细只留汇总,遇单笔争议就无法举证。能删明细的前提是法律与业务都不再需要单条追溯,这判断不能由运维做。
判据:先算“保留成本”,再算“无法举证的成本”。后者通常更高,只是不在账单上。
13. 度量保留与删除是否真的在跑
各位置删除完成率 = 断言通过数 / 应删位置数 (目标 100%,缺口要有人认领)
超期保留量 = 超过保留期仍存在的行数 (应按月归零)
删除工单时长 = 受理 → 全链路回执的中位时间
备份过期偏差 = 实际过期 - 承诺过期 (应为 0 或负)
第二个指标最能暴露治理是否落地:写了保留期却没人执行,超期数据持续堆积。它长期不降,说明策略只是文档。
14. 排错:删除没生效的六种样子
| 现象 | 根因 | 处理 |
|---|---|---|
| 接口成功但数据还在 | 只做了软删 | 补硬删与派生清理 |
| 搜索还能搜到 | 索引未跟随删除 | 接事件,幂等消费 |
| 数仓仍有明细 | CDC 只同步新增 | 透传删除 + 分区重算 |
| 备份里有 | 备份不可变 | 缩保留期 + 销毁密钥 |
| 日志有手机号 | 采集端未脱敏 | 写入前脱敏 |
| 样本无法撤回 | 无来源 id | 补溯源或明示不可撤 |
15. 边界:什么时候不要做精细保留策略
| 场景 | 判断 | 替代做法 |
|---|---|---|
| 数据量小、无 PII | 治理成本高于收益 | 统一保留期 |
| 必须长期留存 | 保留期不可调 | 只做归档与访问控制 |
| 单库、无派生链路 | 位置少,人工可控 | 清单 + 季度核对 |
| 业务尚未稳定 | 保留期频繁变 | 先定上限,不细分 |
| 已有合规系统 | 重复建设 | 复用审计与工单 |
最后一类:合规体系通常已含保留与删除要求,再建并行流程会生出两个口径。新体系的价值在执行与验证,不在再写策略文档。
16. 自测
- 为什么第一次交付必须是位置清单,而不是删除脚本?
- 保留期由三个来源叠加,冲突时按什么顺序取舍?
- 软删在什么情况下不能对外宣称"已删除"?
- 备份不可变,删除请求在备份侧靠什么生效?
- 派生数据里最难撤回的是哪一类,为什么?
- 验证删除时只查主库,会漏掉哪些位置?