AI 应用的数据删除与导出工程 2026:扇出图与验证闭环
AI 应用里的一次删除请求,横跨会话原文、嵌入向量、语义缓存、trace 快照、训练数据集与模型供应商六类派生副本,是一张有向无环图上的可达性遍历,而非一条 DELETE 语句。本文给出 DSAR 四元组与删除扇出图的形式化、三套法定时钟的对照、五种存储介质各自不同的删除语义,以及一套让「删掉了」变成「可复现地证明删掉了」的删除台账与三类验证证据。
约 53 分钟阅读15,752 字3 次阅读博主

AI 应用的数据删除与导出工程 2026:扇出图与验证闭环
一、问题的提出:为什么 AI 应用的删除比 CRUD 难一个数量级
在传统 SaaS 里,用户注销账号是一件机械的事:一段 DELETE FROM users WHERE id = ?,外加若干张关联表的级联删除,日志跑一次批处理,三十分钟内收工。数据模型是有限的、外键是显式的、副本数量是可以枚举的,因此"这个用户的全部数据在哪里"这个问题有一个封闭的答案。
AI 应用把这个问题彻底打碎了。一次对话会产生至少七类彼此派生、存储介质完全不同、生命周期完全不同的人工数据副本:会话原文与消息树、向量库里的切块与嵌入向量、语义缓存里的相似命中结果、摘要与长期记忆事实条目、可观测性平台里的 trace span 与 prompt 快照、评测与反馈数据集、以及模型供应商侧的滥用监控日志与应用状态。它们之间不是外键关系,而是派生关系——嵌入向量是切块的投影,摘要事实是会话原文的压缩,语义缓存命中是对话输入的函数,trace 快照是请求体的副本,训练数据集是反馈样本的汇编。删掉源头的原文,其余副本不会自动消失;删掉派生副本,源头又能在下一秒把它重新算出来。
这带来一个反直觉的结论:在 AI 应用里,"删除"的主要工作量不在删除,而在证明删除与穷尽删除。真正的困难有三层。
第一层是物理层。关系库里的 DELETE 在 MVCC 机制下只写一条可见性标记,旧版本元组仍留在堆文件里,直到 VACUUM 才回收空间;而 VACUUM FULL 需要 ACCESS EXCLUSIVE 锁并重写整表。向量库更微妙:以 pgvector 为例,官方文档明确写着近似索引与普通索引不同,"you will see different results for queries after adding an approximate index",删除向量后近似图里的节点连接并不会自动消失,查询路径仍可能穿过已删除的点。缓存层则常常是不可寻址的:语义缓存按向量相似度命中,键不是用户 ID,事后按用户维度扫描根本扫不到。
第二层是法律层。删除请求不是一次性动作,而是一条带时钟、有例外、有举证责任的程序性义务。欧盟《通用数据保护条例》第 12 条第 3 款规定控制者须在收到请求后一个月内给出答复,必要时可再延长两个月,并在一个月内告知延期理由;第 12 条第 5 款规定这类信息与行动原则上免费,只有在请求明显无根据或过度时才可收取合理费用或拒绝,但举证责任在控制者。第 19 条进一步要求,任何更正、删除或限制处理都必须传达给每一个曾被披露数据的接收方,除非证明不可能或工作量不成比例,并要在数据主体要求时告知这些接收方是谁。这条对 AI 应用杀伤力极大:向量库、缓存、trace 后端、模型供应商,全都是"接收方"。中国的《生成式人工智能服务管理暂行办法》第十一条则直接要求提供者"不得非法留存能够识别使用者身份的输入信息和使用记录",并"应当依法及时受理和处理个人关于查阅、复制、更正、补充、删除其个人信息等的请求"。
第三层是模型层。有些副本是不可逆投影:嵌入向量无法还原为原文,摘要事实无法还原为原句,而已经参与过微调或偏好训练的数据更是直接改变了模型权重——删掉训练集不能让权重里的知识倒流。诚实的工程答案不是"做不到所以不做",而是把删除拆成两段:从请求时刻起切断未来使用(这条完全可以做到,而且必须做到),以及尽可能回溯清除历史副本(这条要按副本类型分级承诺,并对无法回溯的部分明确标注法律依据与理由)。
本文要建立的东西有四样:一个把删除请求形式化的四元组,一个描述派生关系的删除扇出图,一张把五种存储介质的删除语义并排摆开的对照表,以及一条把"删掉了"变成"可复现地证明删掉了"的删除台账。
二、形式化:DSAR 四元组、删除扇出图与三条不变量
先把"删除请求"和"删除动作"分开。前者是法务概念,后者是工程动作,两者之间需要一层翻译。
DSAR 四元组。一条数据主体权利请求记为 R = (S, Σ, T, L):
S是主体(subject),即请求人的可验证身份。这一项不是形式主义。第 12 条第 6 款允许控制者在对自然人身份存在合理怀疑时要求补充信息,因此身份校验本身是请求流程的一部分,而不是前置条件。Σ是范围(scope),即请求覆盖的数据类别集合。它由第 15 条的知情权清单定义:处理目的、数据主体类别与个人数据类别、接收方或接收方类别(含第三国与国际组织)、可能情况下的保存期限或判定标准、更正/删除/限制/反对等权利的存在、投诉权、来源信息、自动决策的存在与逻辑。这一串清单在传统 SaaS 里可以人工回答,在 AI 应用里必须由数据地图自动生成。T是时限(deadline),即法定与承诺的完成时间。存在三套互不相同的时钟,见第三节。L是台账(ledger),即执行与举证所需的可复现记录。
四元组里最容易被忽略的是 L。删除不是"做完了"就结束,而是"做完了且能证明"才算结束。
删除扇出图。把应用持有的数据副本建模成一张有向无环图 G = (V, E),节点是存储位置或副本类型,边 u → v 表示 v 的内容可由 u 的内容计算得出。典型的一条路径是:
会话原文 (PG messages)
├──> 切块 (chunk store)
│ └──> 嵌入向量 (pgvector / Qdrant / Milvus)
├──> 摘要与长期记忆事实 (memory store)
├──> 语义缓存条目 (Redis / KV)
├──> trace span 与 prompt 快照 (可观测性后端)
└──> 反馈样本 → 训练/评测数据集
删除请求 R 在这张图上的语义是:求一个以主体 S 为根的可达子图 G_S,并对该子图上的每个节点执行与介质匹配的删除动作,同时为无法删除的节点给出法律依据。 这里有两个关键推论。
推论一:删除的方向是"沿边反向"的。删掉会话原文并不会自动删掉嵌入向量,因为向量是原文的函数而非原文的副本引用;反过来,删掉向量也不会让原文消失。工程上必须沿着边做显式的级联删除,而这张边表必须显式维护,不能指望数据库外键——嵌入向量与切块之间没有外键,摘要与源会话之间也没有。
推论二:扇出图必须和数据地图是同一份东西。欧盟《通用数据保护条例》第 30 条第 1 款要求控制者维护处理活动记录,其中包含"where possible, the envisaged time limits for erasure of the different categories of data",即不同数据类别的预期删除时限。一个从不更新的处理活动记录与一张从不更新的删除扇出图,在工程上是同一个缺陷的两种表现。
三条不变量。判定一次删除是否合格,用三条不变量而不是一条"删了没"的断言:
- 覆盖性不变量。扇出图中每个由
S可达的节点,最终状态必须是三者之一:已被物理或逻辑删除;被明确标记为法律保留并附有依据与期限;或被判定为不可逆投影且已被记录并对用户披露。三选一,不允许存在"没人知道还有这个副本"的第四态。 - 可证性不变量。对每一次删除动作,必须存在一条可独立复核的记录:请求 ID、目标节点、执行的语句或 API 调用、受影响行数或点数、执行时间、执行结果、以及失败重试的最终结论。复核者不应需要访问原始业务库就能重建结论。
- 时间性不变量。
T必须被系统而非人记住:每个阶段的开始、结束、超时与升级都要落在L里,且延期必须符合第 12 条第 3 款"在收到请求后一个月内告知延期理由"的形式要求。
这三条不变量把"合规"从法务文档变成了可测项——这是本篇与既有合规工程讨论最重要的分野:既有的护栏类工程把安全当成谓词(输入是否可判定为恶意),把删除当成合规声明(政策里写了会删)。而在一个有嵌入向量、有语义缓存、有训练数据集的系统里,删除必须当成图上的可达性遍历来实测。
三、法源锚点:三套时钟与一个必须传达的动作
工程上最容易做错的是把合规当成人文关怀而不是程序。下面把可引用的条款与由此推出的时钟列清楚。
时钟一:欧盟 GDPR。 第 12 条第 3 款是最硬的一条:"The controller shall provide information on action taken on a request under Articles 15 to 22 to the data subject without undue delay and in any event within one month of receipt of the request.",可延长两个月,但须在收到请求后一个月内连同延期理由告知数据主体。注意这里的措辞是"action taken on a request"——法律要求的不是"决定",而是"已采取行动"。如果只回复"我们已受理,正在处理中",不构成合规答复。同样,第 12 条第 5 款把举证责任放在控制者一侧:请求明显无根据或过度时控制者才可收费或拒绝,且"the controller shall bear the burden of demonstrating the manifestly unfounded or excessive character of the request"。
时钟二:加州 CCPA。 加州总检察长办公室发布的 CCPA 问答给出明确的日历日口径:对删除请求,"Businesses must respond to your request to delete within 45 calendar days. They can extend that deadline by another 45 days (90 days total) if they notify you."。这是 45 + 45 = 90 个日历日,与 GDPR 的 1 + 2 = 3 个月不是同一把尺子,且以日历日而非工作日计。同一套全球 AI 应用要同时满足两者,唯一稳妥的做法是取更严者并按日历日计算。
时钟三:中国。 《生成式人工智能服务管理暂行办法》自 2023 年 8 月 15 日起施行。第十一条对提供者提出两项直接约束:其一,"提供者对使用者的输入信息和使用记录应当依法履行保护义务,不得收集非必要个人信息,不得非法留存能够识别使用者身份的输入信息和使用记录,不得非法向他人提供使用者的输入信息和使用记录";其二,"提供者应当依法及时受理和处理个人关于查阅、复制、更正、补充、删除其个人信息等的请求"。这里用的是"及时"而非具体天数,因此工程上的时钟仍应参照 GDPR 与 CCPA 取严。第二十一条则给出了执法后果的阶梯:警告、通报批评、责令限期改正,拒不改正或情节严重的责令暂停提供相关服务,构成违反治安管理处罚的依《治安管理处罚法》处理,构成犯罪的依法追究刑事责任。第十七条还要求具有舆论属性或社会动员能力的服务开展安全评估并履行算法备案——这条与删除无关,但它意味着备案材料里的数据描述必须与实际扇出图一致,否则删了代码里的副本,备案文档里的那份还在。
一个必须传达的动作。GDPR 第 19 条第 1 款:"The controller shall communicate any rectification or erasure of personal data or restriction of processing carried out in accordance with Article 16, Article 17 (1) and Article 18 to each recipient to whom the personal data have been disclosed, unless this proves impossible or involves disproportionate effort."第 2 款:"The controller shall inform the data subject about those recipients if the data subject requests it." 这在 AI 应用里的具体形态是删除传播协议:删除动作必须沿扇出图的边向下游发送,且发送对象不只是数据库,还包括向量库服务、缓存服务、可观测性 SaaS、以及模型 API 供应商。后两者尤其容易被漏掉,因为它们是 HTTP 调用而非数据库事务,失败也不会让事务回滚。
另一个容易被漏掉的条款。第 17 条第 2 款:当控制者已把个人数据公开、且有义务依第 1 款删除时,控制者"taking account of available technology and the cost of implementation, shall take reasonable steps, including technical measures, to inform controllers which are processing the personal data that the data subject has requested the erasure by such controllers of any links to, or copy or replication of, those personal data"。对 AI 应用而言,"已公开"这个前提非常容易满足:被切块、被嵌入、被检索命中的内容往往已经通过检索结果对用户可见过一次。因此向量库里的内容也属于需要向下游传播删除的公开副本,而不仅仅是"内部缓存"。
四、存储层扇出:五种介质的删除语义互不相同
这是全文最实操的一节。同一句"删除用户 X 的数据",在五种存储介质上意味着五件不同的事。
关系型主库:MVCC 死元组与 VACUUM。 PostgreSQL 官方文档在 VACUUM 页面写得非常直白:"In normal PostgreSQL operation, tuples that are deleted or obsoleted by an update are not physically removed from their table; they remain present until a VACUUM is done."。也就是说 DELETE 之后数据仍在磁盘上,直到 VACUUM 回收空间;而普通 VACUUM 只是"reclaims space and makes it available for re-use",空间并不会还给操作系统。真正把空间归还给 OS 的是 VACUUM FULL,但它"requires an ACCESS EXCLUSIVE lock on each table while it is being processed",并且"requires extra disk space, since it writes a new copy of the table and doesn't release the old copy until the operation is complete"。工程结论有两层:其一,"已删除"与"已不可读"是两个状态,做数据地图与保留期承诺时必须区分;其二,如果合规要求是"物理不可读",单靠 DELETE 不够,需要额外的加密擦除(销毁密钥)或对备份的生命周期管理。
向量库:删除是软删除,近似索引会留痕。 pgvector 的官方 README 给出操作方式本身非常朴素——DELETE FROM items WHERE id = 1——但紧跟着的一处说明决定了它的工程含义:"Unlike typical indexes, you will see different results for queries after adding an approximate index."。HNSW 是一个多层图,删除一个点不会把它从邻居的连接列表里物理摘除,于是查询路径仍可能经过已删点,命中后再被过滤。默认参数是 m(每层最大连接数)16、ef_construction(构建期动态候选列表大小)64;为了在过滤场景下找回召回,官方还提供迭代扫描,用 hnsw.max_scan_tuples(默认 20000)与 ivfflat.max_probes 控制扫描上限。此外它的类型维度上限分别是 vector 2000 维、halfvec 4000 维、bit 64000 维、sparsevec 1000 个非零元素——这意味着"用稀疏向量降低删除成本"并不是可行路径,因为稀疏向量的非零元素上限反而更低。
Qdrant 的删除端点是 POST /collections/{collection_name}/points/delete,请求体是 PointsSelector,二选一:PointIdsList(显式 ID 列表)或 FilterSelector(按 payload 过滤条件)。三个查询参数对合规删除非常关键:wait(是否等待变更真正落地)、ordering(保证级别,取值 weak / medium / strong)、timeout。DSAR 的批量删除几乎总是走 FilterSelector,也就是按 user_id 之类的 payload 条件删——这与"先查 ID 再删 ID"相比,少一次竞态窗口,但把成本转移到了索引选择性上:若 payload 没有对 user_id 建索引,删除会退化成全扫描。Milvus 在这一层的思路类似,但它的删除是异步任务,天然带一个"请求已受理但物理清理未完成"的窗口,这类窗口必须在删除台账里作为状态而非完成来记录。
缓存层:语义缓存是不可寻址的。 精确缓存好办,DEL 或按前缀扫描删除即可;语义缓存则是另一回事——它的键是查询向量的相似匹配,命中结果里往往只存最终答案而不存原始输入,因此按用户维度事后扫描是做不到的,除非缓存条目本身携带了归属元数据。Redis 官方文档给出的两个可用原语值得注意:UNLINK 是异步删除,"the command just unlinks the keys from the keyspace. The actual removal will happen later asynchronously",它把内存回收放到另一线程,从而不阻塞;EXPIRE 设置超时后键会自动删除,且超时只会被删除或覆写内容的命令清除,而 INCR、LPUSH、HSET 这类"概念上改变内容但不是替换"的命令不会清除超时。这一点对合规删除很有价值——给缓存条目挂 TTL 是删除失败时的兜底,让忘记删除的副本也能自然过期,而不是无限期留存。
可观测性后端:最容易被整体遗忘的一层。 trace 平台默认保存完整的请求体与响应体,等于把每一次对话都复制了一份到第三方 SaaS。删除请求若不覆盖 trace 后端,等于最大的副本没删。LangSmith 的 Python SDK 仓库 langchain-ai/langsmith-sdk 已在 2026 年 10 月 16 日归档,官方说明是"Python SDK moved to langchain-ai/langsmith-python",这类仓库状态变动本身也会影响合规评估的取证路径——引用某个官方实现时必须同时核对归档状态。
备份与快照:唯一诚实的答案是承认它做不到。 对象存储的版本化、数据库的物理备份、日志归档系统都不接受按行删除。对这一层,可承诺的只有两件事:其一,明确各备份的保留期与轮转周期,把"最长多久仍可能存在"写进给用户的答复里;其二,对确实需要"备份不可读"的场景,用加密擦除——让备份密文的密钥在保留期结束时销毁。这条不是万能的,但对加密擦除可覆盖的层是唯一真正闭环的做法。
五、派生层扇出:不可逆投影的三种形态与分级承诺
把删除对象按可逆性分成三档,比按存储位置分类更能指导承诺。
第一档:可直接寻址的原文副本。 包括消息表、原始上传文件、用户填写的表单、trace 中的 prompt 快照、反馈样本。这一档的共同特点是存在稳定主键,删除即 DELETE,困难只在批量与一致性。
第二档:可重建的派生副本。 包括切块文本、摘要、长期记忆事实条目、评测数据集。这类副本可以从原文重建,因此如果源头还在,删掉派生副本只是暂时失效——用户下次提问,记忆系统会重新抽取出一条同样的事实并写回去。这带来一个容易被忽略的合规陷阱:记忆抽取本身是一个持续写入的用户数据处理过程。合规的删除必须先把用户从"记忆抽取的触发集合"里摘出去(关闭该用户的记忆写入、偏好学习、飞轮回流),再清理已抽取的存量,否则系统会一边删除一边重建。这与 id=736 提出的"用户主导的记忆治理"是同一个方向,但本篇的落点不同:那里讨论记忆的表达形式与用户界面,这里讨论记忆的删除时序与写入闸门。
第三档:不可逆投影。 嵌入向量、已进入微调或偏好训练的数据集、以及已经改变模型权重的影响。这里必须接受一个事实:嵌入向量是原文的连续投影,虽然无法逐字还原,但在给定候选集与度量函数下可被用于成员推断与近似重建,因此它仍属于需要处理的对象,只是处理手段只能是删除而非"使其不可还原"。对训练数据集,唯一现实的做法是划定训练窗口边界:请求到达之后产生的数据不得再进入训练,已经在权重里的知识只能通过未来的模型版本自然稀释,不能通过删除数据集回滚。
分级承诺的写法。 既然三档的可逆性不同,答复用户时就不应使用单一措辞。可行的做法是给出一条与扇出图节点一一对应的清单,每类副本写明:是否已删除、删除方式(物理/逻辑/加密擦除)、是否可重建、不可回溯部分的依据与期限。这份清单同时是台账的用户可见投影,第 12 条第 3 款要求的"已采取行动"由此变得可验证,而不是一句承诺。
六、供应商层扇出:三个存储类别与一处不对称
模型 API 供应商是整个扇出图上最容易被忽略、也最不可控的一层。OpenAI 的开发者文档把 API 侧数据分成两类并给出了端点级别的保留期表,事实非常具体:
第一类是 abuse monitoring logs。文档原文:"Abuse monitoring logs may contain certain customer content, such as prompts and responses, as well as metadata derived from that customer content, such as classifier outputs. By default, abuse monitoring logs are generated for all API feature usage and retained for up to 30 days, unless longer retention is required by law, or is reasonably necessary to protect our services or any third party from harm."。注意后半句的例外:法定留存期更长、或保护服务与第三方免受伤害所必需时,可以超过 30 天。这意味着**"我们删干净了"这句话在供应商侧是有保质期的**,必须以"请求后 30 天内供应商侧仍可能存在滥用监控日志"这样的准确措辞写进答复。
第二类是 application state,即为完成任务而持久化的应用状态。端点级别的保留期差异极大,构成全文最重要的一处不对称:/v1/chat/completions、/v1/responses、/v1/assistants、/v1/threads、/v1/agents、/v1/vector_stores 等的滥用监控保留期是 30 天,而 /v1/conversations、/v1/conversations/items、/v1/chatkit/threads 的 application state 保留期是 "Until deleted",即由控制者显式删除为止。这意味着:对话式应用如果把会话存在供应商侧的 conversations 接口上,"删除"是一次显式的 API 调用;而如果用无状态的 chat/completions,你连会话都没有、也就无从逐条删除,只能依赖 30 天的滥用监控窗口自动过期。 换句话说,"有没有把会话持久化在供应商侧"这个架构决策,直接决定了 DSAR 能不能被满足,而不是只影响功能。
第三类是 prompt caching 的驻留。文档写明:提示缓存"may store encrypted key/value tensors in GPU-local storage as application state",且"This data is stored on the local GPU machines and is not retained after the 24-hour expiration"。这条是本节里少见的好消息——缓存驻留有 24 小时硬上限,语义上接近"自动过期",但它同时提醒了一件事:提示缓存的键就是提示前缀,用户数据以 KV 张量形式驻留在 GPU 上,这条路径的删除诉求只能靠过期而非主动清除。
第四类,对应的是训练侧的不对称。Anthropic 的消费者条款把这一点写得很直白:"We may use Materials to provide, maintain, and improve the Services and to develop other products and services, including training our models, unless you opt out of training through your account settings.",并且紧接着给出一个关键例外——"Even if you opt out, we will use Materials for model training when: (1) you provide Feedback to us regarding any Materials, or (2) your Materials are flagged for safety review to improve our ability to detect harmful content, enforce our policies, or advance our safety research."。也就是说,即使用户关闭了训练选项,用户的反馈内容与被安全标记的内容仍会进入训练。对 DSAR 流程的直接含义是:删除范围必须显式包含"反馈"这一类数据,并且要向用户准确披露"你提交的反馈可能被用于训练,即使你已关闭训练"。
工程结论。 供应商层不可控,但并非不可管理。可管理的部分有三条:其一,架构层选择可删除的持久化形态——把会话放在支持显式删除的接口上,而不是依赖无状态接口的滥用监控窗口;其二,申请并记录保留豁免——OpenAI 文档说明,符合条件的客户可获得 Zero Data Retention 或 Modified Abuse Monitoring 控制,将客户内容排除在滥用监控日志之外,但需要事先审批并接受附加要求;其三,把这些事实写进用户的答复与隐私政策,而不是留到被审计时现查。
七、请求接入与编排:把一条请求变成一条可执行的状态机
有了法源与扇出图,剩下的是把"用户点了删除"变成一条可靠的状态机。
接入层的三道闸。 第一道是身份校验,依据第 12 条第 6 款,在对自然人身份存在合理怀疑时可要求补充信息;第二道是范围确认,Σ 需要落成可执行的节点集合——前端应当展示"将被删除的类别清单"(会话、记忆、向量索引、上传文件、评测与反馈记录、trace),让用户在动作前就看到范围,这与第 12 条第 1 款要求的"concise, transparent, intelligible and easily accessible"信息原则一致;第三道是请求登记,生成不可变的请求 ID,作为后续所有台账记录的主键。
编排层用扇出图遍历而非硬编码脚本。 硬编码的删除脚本在第一次新增一种存储后就失效了。正确做法是:扇出图以数据驱动的方式描述"节点类型 → 删除适配器",编排器按拓扑序遍历并对每个节点调用对应适配器。适配器的统一接口至少要返回四样东西:状态(成功/部分成功/失败/不适用)、受影响数量、无法处理的部分及其理由、以及该节点的幂等键。幂等键在这里不是锦上添花——删除请求必然会被重试,尤其是网络超时之后,而向量库的 FilterSelector 删除在重试时不会报错但会重复消耗配额。
并发与竞态。 删除执行期间,用户可能仍在发起新对话,产生新的记忆抽取与新的 trace。必须有一道写入闸门:请求登记成功即把该主体标记为"删除中",记忆抽取、飞轮回流、评测采样、训练集汇编这几条写入路径必须检查该标记并跳过或延迟。这道闸门如果不存在,删除会永远追不上写入——这是这类系统里最典型的"删不干净"根因,而且它不会报错,只会让指标缓慢劣化。
失败分类与升级路径。 把失败分成三类并对应不同处置:技术性失败(超时、限流、依赖不可用)走重试与退避,不计入 SLA 但必须计入台账;权限性失败(无权访问某个下游、合同拒绝处理删除请求)走人工升级,因为第 19 条的"disproportionate effort"例外需要举证,而这个举证本身是法律判断不是工程判断;不可行失败(备份、已训练权重)走保留登记,附依据、期限与用户可见说明。
导出侧要单独设计。 第 20 条要求提供的数据须为"a structured, commonly used and machine-readable format",并要求数据主体有权"to have the personal data transmitted directly from one controller to another, where technically feasible"。对 AI 应用,导出的完整形态不是"我的聊天记录 JSON",而是会话 + 记忆事实 + 上传文件清单 + 反馈记录 + 派生元数据的打包,且派生元数据必须包含"这条记忆由哪次会话的第几条消息抽取而来"这样的溯源字段,否则用户拿到的导出文件自己无法理解。反过来说,一份没有溯源的导出在事实层面是有缺陷的:它把用户的数据交回去了,却没告诉用户这些数据在系统里扮演什么角色。
八、验证闭环:删除台账、可复现证据与"不可删除"清单
一个没有验证环节的删除流程,和一个没有验收测试的功能在工程地位上是一样的:无法判断它是否工作。
台账的最小字段集。 每条记录至少包含:请求 ID、主体 ID(可哈希)、节点类型、适配器版本、执行的语句或 API 参数模板(值可脱敏,但结构必须保留)、受影响行数或点数、开始与结束时间、返回码、重试次数、最终状态、以及失败时的错误分类。台账本身也是个人数据——它记录了"这个用户在某天删除了什么",所以台账必须有它自己的保留期与访问控制;把台账无限期保留是一种典型的用合规换合规。
三类可复现的证据。
第一类是计数证据:对每个节点类型,用删除前后的独立计数来验证。例如主库执行 SELECT count(*) FROM messages WHERE user_id = $1 应为 0;向量库用等价过滤条件检索应返回 0 点。关键在于计数必须由独立路径得出——用删除语句同一条连接、同一个过滤条件去验证,等于用被测代码验证被测代码。
第二类是不可寻址证据:语义缓存这类无法按用户扫描的介质,验证方式是构造一条针对该用户原始输入的探针查询,确认不再命中旧结果,并连续观察超过缓存 TTL 上限。这一条把"不可寻址"从工程借口变成了有界的可测量对象。
第三类是传播证据:对每个下游接收方,保存删除通知的发出记录、对方返回的受理标识、以及是否属于第 19 条所述"不可能或工作量不成比例"的例外及其举证材料。
"不可删除"清单。 每次删除完成后,产出一份显式的保留清单,逐项写明:数据类别、保留依据(法条、内部合法利益、合同义务)、保留期限、到期处理方式。这份清单有两个用途:内部它是第 30 条第 1 款要求的那部分"处理活动记录",外部它就是给用户的答复正文。把"我们删掉了 A、B、C,D 因 X 依据保留至 Y"写成结构化清单,比一句"已按要求处理"在监管沟通中有效得多。
把验证做成回归测试。 扇出图上的每一种节点类型都应有一个对应的删除契约测试:造一份带标记的数据,走完整删除流程,断言所有节点归零、台账完整、保留清单与预期一致。这套测试放进 CI,并且在扇出图或适配器版本变更时必须重跑——这正是第 30 条要求的处理活动记录"随实际处理变化而更新"在工程上的等价物。
九、落地路线、六个反模式与开放问题
分四步走。 第一步是测绘:把扇出图与数据地图画出来,先不自动化。这一步的价值在于它会立刻暴露缺口——经验上,第一次画图就会发现至少一个此前完全没被记录的副本(最常见的三个是语义缓存、trace 后端、反馈与评测数据集)。第二步是建适配器:为每种节点类型实现统一接口的删除适配器,并各自带一个契约测试。第三步是接流程:状态机、写入闸门、幂等键、时钟与升级路径。第四步是做验证与披露:三类证据、保留清单、面向用户的答复模板、以及放进 CI 的端到端回归。
六个反模式。
一,只删主表。最常见,也最致命——它把"删了 90% 的副本"包装成"已删除"。
二,把 DELETE 当作物理擦除。在 MVCC 主库里它不是,在近似向量索引里它也不是。
三,删除期间不关写入闸门。系统一边删一边重建,用户永远看不到干净的状态。
四,漏掉供应商侧。滥用监控日志的 30 天窗口与应用状态里的 "Until deleted",两种语义必须分别处理。
五,用训练豁免替代训练边界。"我们不再用你的数据训练"是承诺,"请求到达之后产生的数据不进入训练"才是机制;前者无法验证,后者可以。
六,只做删除不做导出。第 15 条与第 20 条是并列的权利,只实现删除的应用在权利清单上并不完整,而用户感知上"能导出"往往是比"能删除"更被低估的需求。
开放问题。 第一个是不可逆投影的边界到底画在哪。本文主张按可逆性分三档并分级承诺,但三档之间的边界在法理上并不清晰,监管口径截至本文写作时没有针对嵌入向量给出专门解释。第二个是删除与记忆质量的张力:长期记忆事实的存在本身就会改善个性化体验,删除即降级,产品需要在体验降级与权利满足之间做显式取舍而不是假装二者一致。第三个是派生层删除的证明标准:一条"用户 U 的记忆事实已被删除"的台账记录,究竟需要证明到什么强度——是行数为零,还是需要提供一次针对该事实原文的探针检索返回空。前者便宜,后者昂贵且脆弱。第四个是备份层的诚实边界:如果保留期是 90 天,那么第 12 条第 3 款要求在一个月内答复时应当如何措辞才既准确又不误导——这可能是本文留下的最实际的一个未决问题。
最后的判断。 数据主体权利在传统 Web 应用里是"政策"问题,在 AI 应用里是"图"问题:因为你真正要回答的不是"我们有没有删这个用户的数据",而是"这个用户的数据在系统里派生出了哪些副本、这些副本分别住在哪种介质的哪种结构里、每种介质的删除语义是什么、其中哪些已经不可逆"。能把这张图画出来、把每条边配上删除适配器、把每次删除留下可复核的台账,DSAR 才从一个需要法务救火的工单,变成一条有确定时钟、可预测成本、可自动化验收的流水线。
参考文献
- Regulation (EU) 2016/679, Article 12 — Transparent information, communication and modalities for the exercise of the rights of the data subject. https://gdpr-info.eu/art-12-gdpr/
- Regulation (EU) 2016/679, Article 15 — Right of access by the data subject. https://gdpr-info.eu/art-15-gdpr/
- Regulation (EU) 2016/679, Article 17 — Right to erasure ('right to be forgotten'). https://gdpr-info.eu/art-17-gdpr/
- Regulation (EU) 2016/679, Article 19 — Notification obligation regarding rectification or erasure of personal data or restriction of processing. https://gdpr-info.eu/art-19-gdpr/
- Regulation (EU) 2016/679, Article 20 — Right to data portability. https://gdpr-info.eu/art-20-gdpr/
- Regulation (EU) 2016/679, Article 30 — Records of processing activities. https://gdpr-info.eu/art-30-gdpr/
- California Attorney General, California Consumer Privacy Act (CCPA) as amended — Frequently Asked Questions. https://oag.ca.gov/privacy/ccpa
- 国家互联网信息办公室等七部门,《生成式人工智能服务管理暂行办法》(2023 年 8 月 15 日施行),第十一条、第十七条、第二十一条、第二十四条. https://www.cac.gov.cn/2023-07/13/c_1690898327029107.htm
- OpenAI, "Your data" — API data usage, abuse monitoring logs retention, application state retention by endpoint, Zero Data Retention and Modified Abuse Monitoring. https://developers.openai.com/api/docs/guides/your-data
- Anthropic, Consumer Terms of Service — "Our use of Materials"(含训练退出与反馈/安全标记例外). https://www.anthropic.com/legal/consumer-terms
- PostgreSQL Global Development Group, VACUUM — dead tuples、MVCC 下的物理保留行为、VACUUM FULL 的 ACCESS EXCLUSIVE 锁与额外磁盘需求. https://www.postgresql.org/docs/current/sql-vacuum.html
- PostgreSQL Global Development Group, Storage File Layout — 堆文件与可见性映射的物理结构. https://www.postgresql.org/docs/current/storage-file-layout.html
- pgvector, README — 近似索引与精确索引结果差异、HNSW 参数(m=16、ef_construction=64)、迭代扫描与 max_scan_tuples、类型维度上限. https://github.com/pgvector/pgvector
- Qdrant, API Reference — Delete points:PointsSelector、wait、ordering(weak/medium/strong)、timeout. https://api.qdrant.tech/api-reference/points/delete-points
- Redis, UNLINK command — 异步删除与后台内存回收. https://redis.io/docs/latest/commands/unlink/
- Redis, EXPIRE command — 键超时自动删除、超时的清除条件与 NX/XX/GT/LT 选项. https://redis.io/docs/latest/commands/expire/
- LangChain, langsmith-sdk 仓库状态(2026-10-16 归档,Python SDK 迁移至 langchain-ai/langsmith-python). https://github.com/langchain-ai/langsmith-sdk
- Qdrant 源码仓库(Rust,Apache-2.0). https://github.com/qdrant/qdrant
- pgvector 源码仓库(C). https://github.com/pgvector/pgvector
- Milvus 源码仓库(Go,Apache-2.0). https://github.com/milvus-io/milvus
- Redis 源码仓库(C). https://github.com/redis/redis
- PostgreSQL 官方 Git 镜像仓库(C). https://github.com/postgres/postgres
- Weaviate 源码仓库(Go). https://github.com/weaviate/weaviate
- Chroma 源码仓库(Rust,Apache-2.0). https://github.com/chroma-core/chroma
- LangGraph 源码仓库(Python)—— 状态机与检查点机制的对照实现. https://github.com/langchain-ai/langgraph
一句话摘要
AI 应用里的"删除我的数据"不是一条 DELETE 语句,而是沿嵌入向量、语义缓存、trace 后端、训练数据集与模型供应商构成的一张派生扇出图逐层执行的可达性遍历,本文给出这张图的建模方法、三套法定时钟、五种存储介质的删除语义对照,以及一套让"删掉了"变成"可复现地证明删掉了"的删除台账与验证闭环。