LabHub
学习 学习路径 课程

对象存储与 S3

delete marker 与非当前版本

在 LabHub 中继续学习

一句话总结

启用版本控制后,删除不再是擦除数据,而是再添加一个“已删除标记(delete marker)”。原始数据仍然存在,费用也会继续产生。

概念图: 异步 · 代码依赖的到底是哪种保证,应通过实测确认,而不能只看文档。 · 两个进程同时写入同一个键 · 如何撤销误覆盖或误删除

为什么需要了解这些

S3 曾长期采用最终一致性。覆盖对象后立即读取,有时仍会得到旧值,因此许多应用程序都加入了重试逻辑。自 2020 年 12 月起,AWS S3 提供强读写一致性,PUT 后紧接着执行 GET 就能读到新值。

不过,必须准确理解这项保证的范围。

操作 当前保证 注意事项
PUT 后 GET(新键) 强一致性
PUT 后 GET(覆盖写入) 强一致性
DELETE 后 GET 强一致性 启用版本控制时,返回 404 后数据仍然存在
LIST 强一致性 其他实现通常在这里较弱
多区域复制 异步 副本随时可能落后

不能把“S3 现在具有强一致性”这句话原样套用到所有兼容 S3 的存储。MinIO 具有强一致性,Ceph RGW 会因配置而异,SeaweedFS 的列表查询则可能根据 filer 配置而滞后。本实验使用的存储也不例外。代码依赖的到底是哪种保证,应通过实测确认,而不能只看文档。

用条件请求防止竞争

即使具备强一致性,两个进程同时写入同一个键的问题依然存在。后写入者获胜,先前的更新会悄无声息地消失。S3 使用条件请求头来阻止这种情况。

# 없을 때만 만든다 — 있으면 412 Precondition Failed
s3.put_object(Bucket=b, Key=k, Body=data, IfNoneMatch="*")

# 내가 읽은 그 버전일 때만 덮어쓴다(낙관적 잠금)
head = s3.head_object(Bucket=b, Key=k)
s3.put_object(Bucket=b, Key=k, Body=new, IfMatch=head["ETag"])

如果 IfMatch 返回 412,说明在此期间有人修改了对象。此时应重新读取、合并并重试。缺少这一模式,就会发生“两个人同时点击保存,最后只剩一个人的内容”这类事故。

版本控制解决的是另一个问题

版本控制解决的不是一致性,而是如何撤销误覆盖或误删除

启用版本控制后,每次写入同一个键都会生成新的版本 ID。最新版本是当前版本,其余均为非当前版本。只要指定版本 ID,旧版本随时都可以读取。

删除行为尤其值得注意。不指定版本 ID 执行 DELETE 时,数据并不会真正被删除,而是会在最上层增加一个名为 delete marker 的特殊版本。因此普通 GET 会返回 404,但数据仍完整保留在下方。

키 report.csv 의 버전 스택 (위가 최신)

  delete marker   (v4)  ← 지금 GET 하면 404
  ────────────────────
  2026-09-05 판   (v3)  ← v4 를 지우면 이것이 다시 현행이 된다
  2026-09-04 판   (v2)
  2026-09-03 판   (v1)

删除 delete marker 后,紧邻其下的版本会重新成为当前版本。这就是误操作恢复机制。若要真正删除数据,必须指定版本 ID 执行 DELETE,而且该操作无法撤销。

还要注意一点:版本控制只能启用,不能关闭。 它只能切换为 Suspended 状态,已经累积的版本仍会保留。在 Suspended 状态下写入的对象会成为版本 ID 为 null 的特殊版本,再次写入时会覆盖该版本。因此,应在启用版本控制前先确定生命周期规则。

生产现场会看到什么

版本控制的成本经常被忽视。如果对每天用同一个键覆盖日志文件的代码启用版本控制,一年后单个键下就会累积 365 个版本。列表中看起来只有一个对象,费用却是 365 倍。“明明全删了,费用却没变”的情况也源于 delete marker。

还有一项隐藏成本:失败的分段上传。 上传 5GB 文件时若在 80% 处中断,已经上传的分段会在任何常规列表中都不可见,却仍然产生费用。对象列表和版本列表都不会显示它们,只能通过 list-multipart-uploads 查看。

因此,启用版本控制时必须同时配置生命周期规则。以下三条规则应作为基础组合。

{"Rules": [
  {"ID": "expire-noncurrent", "Status": "Enabled", "Filter": {},
   "NoncurrentVersionExpiration": {"NoncurrentDays": 30}},
  {"ID": "clean-delete-markers", "Status": "Enabled", "Filter": {},
   "Expiration": {"ExpiredObjectDeleteMarker": true}},
  {"ID": "abort-incomplete-mpu", "Status": "Enabled", "Filter": {},
   "AbortIncompleteMultipartUpload": {"DaysAfterInitiation": 7}}
]}

规则每天异步运行一次,因此不会立即生效。如果应用规则后的第二天容量开始下降,便属于正常现象。

对于必须彻底阻止不可逆删除的数据,应使用 Object Lock。GOVERNANCE 模式可由具备特殊权限的主体解除,而 COMPLIANCE 模式在保留期内连根账户也无法删除。需要接受审计的日志应使用后者。

下一次实验要做什么

启用版本控制,对同一个键写入三次以累积版本;读取旧版本;删除后确认 delete marker;移除该标记以恢复对象;计算非当前版本占用的容量,然后应用生命周期规则。最后亲自确认条件 PUT 会返回 412。