delete marker 与非当前版本
一句话总结
启用版本控制后,删除不再是擦除数据,而是再添加一个“已删除标记(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。