数据维护:逻辑过期、分批清理与对象回收
验证码超过有效期时应立即拒绝使用,后台清理可以稍后删除记录。用户是否登录、清理任务是否按时运行,都不应改变验证码的有效性。
同样的分工也适用于临时下载链接、过期会话和缓存记录:请求路径判断能否使用,后台任务回收占用的存储。
从到期失效推导后台回收
假设验证码有效 5 分钟,开发者可能先想到“5 分钟后删掉这一行”。删除发生得晚、清理进程停止或记录尚在副本上,都不应让一次业务校验继续接受过期凭据。
| 候选方案 | 能完成什么 | 需要补充的条件 |
|---|---|---|
| 仅靠延迟删除或 TTL | 回收部分过期存储 | 不能把物理存在当作仍有效 |
| 仅在请求内检查期限 | 正确拒绝过期使用 | 长期残留仍占用存储 |
| 请求内校验加后台分批回收 | 分别满足有效性与容量要求 | 保留策略、清理范围与删除恢复 |
本篇选择第三种。逻辑有效期由业务操作判断,清理期限由保留策略判断。审计记录即使不再可用,也可能仍需要保留;业务过期不自动意味着允许马上物理删除。
逻辑校验与物理回收时序
图中前一部分是验证码业务校验,后一部分是临时对象回收,分别说明数据库内原子操作和跨系统删除的边界。
业务失效不必等待物理删除;外部删除不确定时则要核对原目标,不能扩大清理范围。数据库内到期记录的分批删除见后文 SQL。
维护需求与数据保留边界
示例把验证码逻辑有效期设为 5 分钟,后台按自己的清理周期回收。5 分钟是业务规则样例;审计记录、文件留存和用户删除请求分别定义保留期限与完成承诺。
| 需求 | 示例决策 | 实现约束 |
|---|---|---|
| 到期是否还能使用 | 不能,独立于清理进度 | 请求路径比较期限和撤销状态 |
| 哪些记录可删除 | 到期、无活跃引用、符合保留策略 | 候选查询与删除前复核 |
| 删除失败怎么办 | 记录目标,按对象独立重试 | 不整批重新扩大范围 |
| 保留例外怎么办 | 审计、冻结或活跃会话显式排除 | 有版本的保留策略与 hold 标记 |
| 谁能手动清理 | 受限的维护身份 | 目标范围、操作原因和结果可审计 |
创建记录时保存期限
| 数据 | 业务侧判断 | 后台回收的对象 |
|---|---|---|
| 验证码 | 已使用或当前时间达到期限即不可用 | 符合保留策略的过期挑战记录 |
| 临时文件 | 按下载授权、引用状态和有效期判断能否访问 | 无引用的文件或对象版本 |
| 会话 | 按有效期、撤销状态和安全策略判断能否继续使用 | 已失效会话记录 |
| 任务历史 | 已完成且超过保留窗口才可回收 | 归档后的实例与尝试记录 |
验证码校验还需要绑定用户或业务上下文,控制尝试次数,并用原子操作避免重复使用。只依靠清理线程删掉旧记录,会让清理延迟变成有效期延长。
MongoDB 官方说明 TTL 删除是后台进行的,到期记录不保证立即消失。TTL 可以回收空间,授权与有效性判断仍要自己完成。
数据结构与使用接口
| 记录 | 关键字段与约束 |
|---|---|
verification_challenges | 用户、用途、摘要、有效期、剩余尝试、使用时间 |
temporary_objects | 对象版本、所有者、引用计数或关系、期限、hold、回收状态 |
cleanup_runs | 策略版本、过滤范围、截止水位、检查点、扫描与成功数量 |
cleanup_items | 本轮目标身份、对象版本、删除结果、失败原因与尝试 |
签发接口返回挑战标识,不回传可复用的服务器凭据;校验接口绑定当前用户和业务用途。临时下载接口校验权限与期限,物理对象仍存在不代表允许继续访问。
维护操作可以分为候选预览、创建清理运行和查询进度。预览之后对象可能被重新引用,因此正式执行仍要在事务中复核,不能直接删除整份旧预览列表。
验证码的一次性消费
下面的设计片段假定请求已经通过认证、用途绑定与频率控制,摘要由服务端根据本次输入计算。它只展示“未使用且未过期”的原子消费条件:
UPDATE verification_challenges
SET used_at = statement_timestamp()
WHERE id = :challenge_id
AND user_id = :user_id
AND purpose = :purpose
AND code_digest = :computed_digest
AND used_at IS NULL
AND attempts_remaining > 0
AND expires_at > statement_timestamp()
RETURNING id;错误输入还要原子扣减尝试额度并限流;成功消费与需要完成的本地业务尽可能同事务提交。生成、保存和校验还需满足随机性、安全存储、单次使用等要求,可参考 OWASP 的一次性令牌建议。上面片段不涵盖完整凭据保护流程,不能把摘要放进访问日志或错误响应。
并发消费时只有符合条件的更新能够成功。清理线程不参与有效性判断,暂停清理也不会扩大 5 分钟窗口。
used_at IS NULL 保护单次使用,expires_at 保护时间边界,user_id 与 purpose 保护使用上下文。只保留“摘要相同”这一个条件,会丢掉这些不同约束。条件更新成功后,受保护的本地业务仍需在同一事务完成,才能避免验证码消耗了、业务却没有提交。
1000 条记录同时过期
为到期字段建立合适索引,每次领取一小批并提交。下面是 PostgreSQL 18 的清理语句设计示例,展示每批最多 100 条;它没有在真实数据库执行。
WITH expired AS (
SELECT id
FROM verification_challenges
WHERE expires_at <= statement_timestamp()
ORDER BY expires_at, id
LIMIT 100
FOR UPDATE SKIP LOCKED
)
DELETE FROM verification_challenges AS v
USING expired AS e
WHERE v.id = e.id
AND v.expires_at <= statement_timestamp()
RETURNING v.id;1000 条约需 10 批,但批次之间可以持续推进,不必每处理 100 条就再等一轮很长的定时周期。根据在线负载设置每轮最大耗时、总批数和退避。
实际删除还需要检查外键、审计和数据保留要求。数据库行被删除后,磁盘空间如何回收取决于存储引擎维护机制,不能直接按删掉的行数计算立即释放的磁盘。
数据库清理的运行边界
短事务删除适用于能够直接删除的记录;需要外部操作的对象先保存 cleanup_item,再交给独立 Worker。不要在数据库事务里等待对象存储删除。
每轮记录策略版本、截止水位和实际处理数量。重试可以按精确目标继续,也可以安全重扫剩余候选,但不能因为一次失败改为清理更宽的用户或路径范围。
并发清理器使用领取锁或条件更新协调。正在续期的记录重新满足保留条件时,删除语句需要重新检查,避免使用过期候选快照。
临时文件需要跨系统回收
文件元数据与对象存储通常不在同一事务中,可以采用标记、删除、确认的流程:
- 领取到期候选,检查没有活跃引用或上传会话。
- 条件标记待删除,保存对象键和版本,阻止新的正常引用;需要恢复时走明确的状态转换。
- 事务外删除确切的对象版本,按存储接口语义处理“已不存在”。
- 保存删除结果,失败时仅重试对应对象。
删除前必须防止对象键复用。领取时看到的旧临时文件,不应因为相同路径被新文件替换而误删新内容。
这里描述的是清理系统设计,不是让扫描任务任意按宽泛路径删除文件。清理目标应来自受控业务记录和精确对象身份。
生命周期策略的边界
对象存储的生命周期规则可以自动回收部分临时对象,但要核对版本与删除标记的语义。例如 S3 对象过期在不同版本控制状态下行为不同,当前版本过期不等于所有历史版本都已物理删除。
业务已经删除而对象仍在、对象已删除而元数据未确认,都需要可重试的核对流程。单次调用超时不能确定删除究竟有没有完成。
不要把去重依据一起删掉
任务历史到期后,如果同时删掉唯一业务键,历史事件重放时可能再次创建同一通知或补偿动作。保留期限要覆盖恢复与重放窗口,必要时把长期幂等记录从任务实例中分离。
监控最老待清理记录年龄、每批耗时、失败目标和在线数据库压力。大多数清理失败需要通知运维负责人,而不是逐条推送给用户;实际用户数据删除进度则按产品约定单独展示。
对象回收 Worker
领取 cleanup_item,核对本轮策略与精确对象版本
BEGIN
锁定元数据,检查 hold、引用、期限和当前回收状态
如果重新被使用:退出清理或进入明确的恢复流程
标记 deleting,阻止未协调的新引用,保存外部删除意图
COMMIT
事务外按确切版本提交删除
确认已删除:条件更新元数据与 item 为完成
返回暂时失败或结果不确定:保留待核对状态后重试或查询创建新引用的业务路径必须遵守同一 deleting 状态约束,否则清理器的标记无法保护对象。删除请求已经在途时,也不能简单清掉 deleting 标志就认为对象恢复可用。
一次验证码失效与对象回收的推演
| 操作或时间 | 逻辑判断 | 后台结果 |
|---|---|---|
| 验证码 V7 在 10:00 签发,10:05 到期 | 到期前按用途与使用状态校验 | 清理可以尚未启动 |
| 10:05:01 用户提交 V7 | 期限条件不成立,拒绝 | 记录物理存在不改变结果 |
| 10:10 清理恢复 | 对符合保留策略的 V7 执行回收 | 不回头修改先前的校验结论 |
| 临时对象 X2 被列为待清理后重新引用 | 正式领取时发现有效引用,跳过 | 预览列表不能直接授权删除 |
| 旧键后来写入新对象版本 | 清理仍绑定原来的精确版本 | 新内容不能被旧任务顺带删除 |
数据库到期行的删除,与外部对象的两阶段回收,具有不同恢复窗口。看到两者都叫“清理任务”时,应先问状态能否与副作用同事务提交,再决定使用一条删除语句还是独立的删除意图与核对流程。
验收与排查依据
| 场景 | 应断言的结果 |
|---|---|
| 清理停机一小时 | 过期验证码仍被拒绝使用 |
| 同一验证码被两个请求同时消费 | 只有一次有效消费 |
| 候选预览后文件被重新引用 | 正式领取时复核,阻止误删 |
| 相同对象键写入新版本 | 清理只能命中已领取的旧版本 |
| 删除成功但确认响应丢失 | 查询或幂等重试原目标,不扩张删除范围 |
| 历史任务归档后事件重放 | 长期业务去重依据仍阻止重复副作用 |
区分逻辑失效延迟与物理回收延迟:前者应满足业务有效期,后者按存储与维护预算约束。记录跳过原因、删除失败年龄与受影响目标数量。
阿里云 OSS 生命周期与业务回收的分工
阿里云 OSS 的生命周期概述介绍了按前缀、标签等条件筛选对象后进行转储、过期删除或清理未完成分片。规则加载和周期扫描需要时间,因此它适合存储维护,不能用来实现验证码到秒失效或即时撤销下载权限。
本文建议先由业务标记对象的回收资格,再对用途与保留期限明确的临时目录使用生命周期规则。订单附件、取证文件与普通缓存不应仅因位于同一 Bucket 就使用相同删除规则;文档也说明,对象保留期会影响删除。1000 个文件到期时,业务访问层先按期限拒绝访问,物理回收可以随后分批完成。
配置本身也有故障窗口:官方说明通过 API、SDK 或 ossutil 调用 PutBucketLifecycle 会覆盖整个 Bucket 的生命周期配置。自动化维护规则时,应读取完整配置、合并修改并校验差异,再提交全部规则;这与逐个追加一条规则的直觉不同。本文仍需单独保留任务去重依据和删除结果记录,不能把存储生命周期当成业务审计账本。
火山引擎 TOS:清理当前对象、历史版本和上传碎片
火山引擎技术服务的 TOS 生命周期清理案例以清空大量对象的存储桶为例,讨论当前对象、先前版本和未完成分片的处理。它说明维护对象可能不止文件列表中看到的当前对象;当前文件不可见也不能直接证明历史数据与碎片都已回收。
应用到本文时,可以把业务临时文件、历史版本和中断上传分别统计。回收目标记录精确版本或其他可验证的对象身份,核对本轮实际释放结果。该案例的全桶范围不适用于混有正式附件的业务桶;这里只借用清理对象分类,示例保留期限仍由业务自行确定。
美团 MySQL 巡检:检测结果需要进入治理闭环
美团的 MySQL 巡检系统实践介绍使用 Crane 调度巡检,执行机异常时可重新执行,并要求结果幂等入库;还将处理方案、负责人催办与治理周期纳入系统。这使“任务执行完成”和“发现的风险已经消除”成为可分别跟踪的结果。
本文的数据维护可以借鉴这一点:清理任务发现某文件被保留策略阻止,应记录原因和责任方,后续依据策略变化重试或人工处理,而不是每晚重复报一个笼统失败。涉及大表或大量对象时,还要记录清理速率、剩余量和对在线业务的影响。巡检发现只读风险与执行不可逆删除是不同权限等级,不能因同由调度器触发就合并授权。
参考资料
火山引擎技术服务:如何使用生命周期配置规则清空 TOS 存储桶
美团技术团队:美团 MySQL 数据库巡检系统的设计与应用
阿里云官方文档:OSS 生命周期概述
MongoDB:TTL Indexes
PostgreSQL 18:SELECT:锁与 SKIP LOCKED
Amazon S3:Expiring objects
PostgreSQL 18:UPDATE:条件更新与 RETURNING