跳转到内容
← 返回系统剖析
系统剖析对象存储 · 分布式存储27 分钟阅读

对象存储:S3 如何改变了存储的形状

Object Storage: How S3 Reshaped What Storage Is

2006 年 3 月 14 日,Amazon 上线了一个只有四个主要动词的存储服务:PUT、GET、DELETE、LIST。定价是每 GB 每月 0.15 美元,外加每 GB 传出 0.20 美元。它没有目录,没有文件句柄,不能定位到文件中间某个字节去改写,也不能把一个名字改成另一个名字。 按 1970 年代以来的存储…

对象存储纠删码一致性模型存储分层不可变数据

2006 年 3 月 14 日,Amazon 上线了一个只有四个主要动词的存储服务:PUT、GET、DELETE、LIST。定价是每 GB 每月 0.15 美元,外加每 GB 传出 0.20 美元。它没有目录,没有文件句柄,不能定位到文件中间某个字节去改写,也不能把一个名字改成另一个名字。

按 1970 年代以来的存储标准,这几乎是一份缺陷清单。Allan Vermeulen——当时 Amazon 的首席技术官、S3 的最初提案人——和团队在设计阶段曾在白板上写下六个月后的对象数预估,然后往后面多加了两个零"以防万一",结果两个月就冲破了这个数字。到 2023 年 11 月,AWS 公布 S3 存有超过 350 万亿个对象,平均每秒处理超过 1 亿次请求。

这篇文章想讲清楚的是:那份"缺陷清单"不是妥协,而是交易。砍掉的每一样东西,都换回了一样在分布式规模上更值钱的东西。而当有人试图把砍掉的东西再装回去——把 S3 挂载成文件系统——账单和延迟会立刻告诉他这笔交易不能反着做。

破除误解

误解一:S3 的桶里有文件夹。

没有。S3 的键(key)空间是完全扁平的,一个桶里就是一张巨大的"字符串 → 对象"的映射表。logs/2026/08/a.txt 这个键里的斜杠对 S3 而言只是普通字符,和字母 x 没有区别。你在控制台看到的文件夹图标,是客户端用 ListObjectsV2prefix=logs/2026/08/delimiter=/ 参数请求来的:S3 把所有共享同一段前缀的键折叠成 CommonPrefixes 返回,看起来像目录,实际上是一次前缀扫描的结果。

由此产生一串违反直觉的后果:删掉 logs/2026/08/ 下的所有对象,这个"文件夹"就凭空消失了,因为它从来不曾作为一个实体存在;你也无法创建一个空文件夹(工具的做法是造一个以 / 结尾的零字节对象来假装);更重要的是,没有目录 = 没有目录级的原子操作,这是后面所有麻烦的总根源。

误解二:前缀既然不是目录,那键叫什么就无所谓。

恰恰相反,前缀是性能的分区单位。S3 内部按键的字典序区间把索引切分成分区,每个分区独立承载请求。官方给出的水位是:每个前缀分区每秒支持约 3500 次 PUT/COPY/POST/DELETE 和 5500 次 GET/HEAD。2018 年 7 月之前,S3 不会自动分裂热点分区,所以那个年代的最佳实践是在键的开头手工加随机哈希a3f9-2026-08-04.log)把负载摊开——这条建议今天已经过时,S3 会自动分裂,但"键的前缀决定了并发上限"这件事没变。把一亿个对象全塞进 data/ 一个前缀下,你会撞到墙。

误解三:2020 年 S3 变成强一致之后,就可以当数据库用了。

S3 的强一致是单个对象、单个键上的顺序保证:写成功之后读一定读到新值,LIST 一定看得到。它对跨对象没有任何原子性承诺——你没法在一个操作里同时替换两个对象。而且直到 2024 年 8 月 S3 才支持条件写(If-None-Match),2024 年 11 月才支持条件覆盖(If-Match);在此之前 S3 连"仅当对象不存在时才创建"这种最基本的比较并交换(compare-and-swap)都做不到。此外,桶级配置(策略、ACL、生命周期规则)至今仍是最终一致的,跨区复制也仍是异步的。

一个只有四个动词的 API

先把接口的边界画清楚,因为整篇文章的推理都从这里出发。

PUT    /bucket/key      整个对象一次写入,全有或全无
GET    /bucket/key      可带 Range 头,读任意字节区间
HEAD   /bucket/key      只取元数据
DELETE /bucket/key      删除(开启版本控制时是插入删除标记)
GET    /bucket?list-type=2&prefix=...&delimiter=/&continuation-token=...
```

几个关键的不对称:

可以部分读,不可以部分写。 Range: bytes=1048576-2097151 让你从一个 5 TB 的对象里精确取出 1 MB,这是列式存储格式(Parquet、ORC)能在 S3 上高效工作的前提。但反过来没有 Range PUT。要改对象的第 100 万个字节,唯一办法是重新上传整个对象。

单次 PUT 最大 5 GB,靠分段上传(multipart upload)突破到 5 TB。 分段上传最多 10000 片,除最后一片外每片至少 5 MB。它的语义很值得注意:各片可以并行、乱序、失败重传,但在你调用 CompleteMultipartUpload 之前,目标键上什么都看不见。这是"并行写入 + 原子发布"的模式——你永远不会读到半个对象。

对比一下 POSIX:本地文件系统里 write() 写到一半进程被杀,你会得到一个截断的文件;要拿到"全有或全无",程序员必须自己走"写临时文件 → fsync → 原子 rename"这套仪式。S3 把这个性质做成了 API 的默认值,代价是你失去了原地修改的能力。

ETag 不是内容哈希。 单段上传的 ETag 确实是对象内容的 MD5;但分段上传的 ETag 是"各分片 MD5 拼接后再取 MD5",末尾加 -分片数,例如 d41d8...-17。用 KMS 加密的对象 ETag 也不是 MD5。所以拿 ETag 当跨系统的内容指纹,会在切换上传方式的那天崩掉。真正想校验内容,要用 S3 后来提供的额外校验和(CRC32、CRC32C、SHA-1、SHA-256、CRC-64/NVME)。

不可变对象与版本:把"修改"变成"追加"

S3 的对象一旦写入就不能改。所谓"覆盖",实际是写一个新对象、让键指向它。开启版本控制(versioning)之后,这件事变得显式:每次 PUT 生成一个新的 versionId,旧版本原地保留;DELETE 不删除任何数据,只是插入一个零字节的删除标记(delete marker)成为最新版本,普通 GET 就开始返回 404,而带 versionId 的 GET 仍能取到旧数据。真正的物理删除需要显式指定 versionId

一个容易踩的细节:桶的版本控制有三个状态——未启用、已启用、已暂停。一旦启用就再也回不到"未启用",只能暂停。暂停之后新写入的版本 ID 是 null,而此前产生的历史版本仍然存在、仍然计费。很多人的 S3 账单里躺着几十 TB 谁也不记得的旧版本,就是这么来的,清理它们要靠生命周期规则里的"非当前版本过期"策略。

不可变性带来的最大好处是幂等重试的正确性。分布式系统里,客户端发出 PUT 后网络超时,它不知道服务端到底写没写。如果存储支持原地部分修改,重试就可能造成撕裂写;而在 S3 上,重发同一个 PUT 最坏结果就是多写一个完全相同的对象版本。这让整条链路上的重试逻辑可以简单得多。

这些约束换来了什么

现在可以回答核心问题了:删掉目录树、部分写和重命名,买到了什么?

买到了无共享的水平扩展。 目录树是一棵有状态的、需要跨节点加锁的数据结构。mv /a/x /b/x 要同时修改两个目录项,还要保证并发的 ls /a 不会看到中间态;这在单机上靠 inode 锁解决,在跨越几十万台机器时就变成了分布式事务。扁平键空间没有这个问题:每个键的元数据可以独立分片、独立复制、独立扩容,键与键之间不共享任何可变结构。S3 能从"六个月存几千万个对象"长到 350 万亿个对象,靠的就是这一点。

买到了简单的一致性推理。 对象不可变,意味着"某个 versionId 的内容"是一个永远不变的事实,可以随意缓存、随意跨区复制、随意让 CDN 持有。需要协调的只剩"键当前指向哪个版本"这一个极小的元数据。存储系统里最难的一致性问题,被压缩成了一个单键寄存器问题。

代价也很明确。 你失去了:原地更新(改一个字节要重写整个对象)、跨对象事务(写数据文件和更新索引之间总有窗口)、目录重命名(发布一批文件没有原子切换)、以及 POSIX 那套 uid/gid/mode/硬链接/文件锁语义。这些不是"暂未实现",是这个数据模型里没有安放它们的位置。

纠删码:用数学换副本

对象存进来之后,S3 要保证它不丢。官方口径是"设计为 99.999999999%(11 个 9)的持久性",AWS 自己给的直观解释是:如果你存 1000 万个对象,平均要一万年才会丢掉其中一个。

最朴素的做法是存三份完整副本,任意两块盘同时坏都不丢数据,代价是 3 倍存储。纠删码(erasure coding)用线性代数换掉了这笔开销:把对象切成 k 个数据片,用 Reed–Solomon 码算出 m 个校验片,任意 k 片就能还原全部内容,因此可容忍任意 m 片丢失,而存储开销只有 (k+m)/k。

方案存储开销可容忍同时故障重建一片需读取
三副本3.00x21 片(整份数据)
RS(6,3)(HDFS-EC 默认)1.50x36 片
RS(10,4)(Facebook f4)1.40x410 片
RS(12,4)1.33x412 片
LRC(12,2,2)(Azure Storage)1.33x任意 3 片,多数 4 片组合也可恢复常见单片故障只读 6 片

表里最右边那一列才是真正的取舍所在。三副本的重建成本是 1 倍:一块盘坏了,从另一份副本上顺序拷回来就行。纠删码的重建成本是 k 倍:丢一片,要从 k 台不同机器上各读一片,跨网络汇聚,再做一次解码运算。对 RS(12,4) 来说,恢复 1 GB 数据要产生 12 GB 的内网流量。数据中心里盘每天都在坏,这笔重建流量是持续的背景负载,会直接和用户请求抢带宽。

这也是 Cheng Huang 等人在 USENIX ATC 2012(获最佳论文)提出本地重建码(Local Reconstruction Code, LRC)的动机。Azure 的 LRC(12,2,2) 把 12 个数据片分成两组各 6 片,每组配一个"本地校验片",另外再加 2 个覆盖全部 12 片的"全局校验片",一共 16 片、开销同样是 1.33x。区别在于:单片故障是最常见的情形,此时只需读同组的 5 个数据片加 1 个本地校验片,共 6 片就能重建,比同等开销的 RS(12,4) 要读的 12 片省了一半。用一点额外的编码复杂度,换掉一半的日常重建流量。

还有两个工程细节常被忽略:

小对象不能直接纠删码。 把一个 4 KB 的对象切成 12 片,每片 342 字节,加上每片自己的元数据和校验和,开销比数据本身还大;而且 16 次分散的小 I/O 远比 1 次顺序读昂贵。真实系统的做法是先把大量小对象打包进一个大容器再对容器编码——Facebook 的 f4(OSDI 2014)就是把 blob 聚合成 1 GB 的卷再做 RS(10,4),把有效复制系数从 3.6 降到 2.1。S3 的存储节点 ShardStore(SOSP 2021 最佳论文,约 4 万行 Rust)采用 LSM 树结构,并把分片数据放在树之外以降低写放大,思路同源。

降级读会一直存在。 一片丢失后到重建完成前的这段窗口里,每一次读取该对象都要现场做一次解码,读放大 k 倍、延迟显著变高。三副本没有这个问题——挂一份还有两份可以直接读。所以热数据倾向于副本或低 k 值编码,冷数据才敢用高开销比的编码。

最后要说清楚:11 个 9 这个数字建立在故障独立的假设上。真正吃掉数据的从来不是"12 块盘碰巧同时坏",而是相关性故障——同一批次固件的缺陷、机架掉电、可用区级事故、以及排在最前面的运维误操作。S3 标准存储把分片打散到至少三个可用区,正是为了对抗这一类相关性;而 One Zone-IA 明确只落在单个可用区,便宜 20% 左右,换回来的风险就是这个可用区整体损毁时数据真的没了。

2020 年那次一致性升级,到底改了什么

2020 年 12 月 1 日,AWS 宣布 S3 对所有 GET、PUT、LIST 提供强读后写一致性——不加钱、不改性能、不用开开关、对存量对象同样生效。

此前的模型是这样的:从 2015 年起,新对象的 PUT 已经是读后写一致的,但覆盖写和删除仍然是最终一致,LIST 也是最终一致。这意味着一串具体的、必须被应用绕开的现象:

  • 刚写完的对象,ListObjectsV2 可能列不出来;
  • 刚删掉的对象,还会在列表里出现一会儿;
  • 覆盖写之后,GET 有一定概率返回旧内容;
  • 最阴险的一条:如果你在 PUT 之前先 GET 过这个键,S3 的负载均衡层会缓存那次 404,导致 PUT 成功后一段时间内 GET 仍然返回"不存在"。

对单个上传脚本,这些是偶发的怪事。对数据处理框架,这是致命的——Spark/Hive 作业写完一批 Parquet 文件后立即 LIST 来发现它们,列表少了一个文件,下游算出来的结果就静默地少了一部分数据。没有报错,只有错误答案。

当年的绕法基本是同一招:在 S3 旁边挂一个强一致的元数据库。 Netflix 2014 年开源的 s3mper、AWS EMR 的 EMRFS Consistent View、以及 Apache Hadoop 的 S3Guard,都是把"这个目录下应该有哪些文件"写进 DynamoDB 一张表,LIST 时以 DynamoDB 为准、以 S3 为辅。这套东西正确性能撑住,但引入了一个额外的有状态组件、额外的账单、以及一类新故障——两边不同步时,DynamoDB 说文件在、S3 说不在,作业直接卡死。S3Guard 从 2016 年写到 2020 年,S3 转强一致后就没有存在意义了,Apache 在 HADOOP-17409 里于 2022 年把它整个删除。

S3 内部是怎么做到的? Werner Vogels 在 2021 年的文章里给了轮廓:S3 的元数据子系统前面有一层缓存,问题在于"写可能流经缓存基础设施的一部分,而读却查询了另一部分"。他们加了一个新组件作为写入的见证者(witness)——每个对象发生变更时它都被通知,只在内存里维护极少量的"哪些键最近被写过"的状态。读路径先问 witness:如果它说这个键最近没动过,缓存里的值就可以直接返回;如果说动过,缓存条目立即失效,转而去权威存储取。因为 witness 的状态极小,它可以做得非常快、失败后也能迅速恢复,不必搬运昂贵的状态。配合新的复制逻辑给出的"每个对象的操作顺序",就构成了缓存一致性协议。

强一致仍然没有给你的东西:跨对象原子性。写数据文件和更新清单文件之间的窗口依然存在。Delta Lake、Apache Iceberg 这类表格式的做法是把整张表的状态压缩到"一个指针对象"上,于是问题化归为"原子地替换一个对象"——可 2024 年之前 S3 连这个都给不了,因为没有条件写,两个并发提交会互相覆盖。所以 Iceberg 长期依赖外部 catalog(Hive Metastore、Glue、DynamoDB)来做这一步互斥。2024 年 8 月 If-None-Match 上线之后,"仅当对象不存在时才创建"成了原生能力,纯 S3 的乐观并发控制第一次成立;同年 11 月的 If-Match 又补上了"仅当 ETag 未变时才覆盖"。这两个头加起来,才把 S3 从"一致的存储"推到"可以在上面做协调的存储"。

分层的经济学

存储分层看起来是个产品目录问题,本质是一道成本函数题。以下是美国东部区域的挂牌价量级(价格会调整,重要的是相对关系与结构):

存储类约每 GB·月最短计费时长最小计费对象取回费取回延迟
S3 标准$0.023毫秒
标准-IA$0.012530 天128 KB$0.01/GB毫秒
单区-IA$0.01030 天128 KB$0.01/GB毫秒(单可用区)
Glacier 即时取回$0.00490 天128 KB$0.03/GB毫秒
Glacier 灵活取回$0.003690 天40 KB分档1–5 分钟 / 3–5 小时 / 5–12 小时
Glacier 深度归档$0.00099180 天40 KB分档12 小时 / 48 小时

深度归档大约是每 TB 每月 1 美元,标准存储的二十三分之一。这个跨度不是定价策略造出来的,而是物理成本的差异:热数据必须放在通电旋转、随时可寻址、有足够副本承载并发读的介质上;冷数据可以放在低转速、大容量、低冗余度、甚至平时不通电的介质上。存储的边际成本里,很大一块其实是维持"随时可读"这个状态所需要的持续能耗

真正决定账单的是表格右边三列,而不是最左边那一列:

  • 最短计费时长是一道违约金。对象在标准-IA 里放 5 天就删掉,仍然按 30 天收费。把一批生命周期只有一周的临时文件设成 IA,成本会上升
  • 最小计费对象是小文件的杀手。一亿个 8 KB 的对象放进 Glacier 即时取回,每个按 128 KB 计费,实际存了 0.8 TB 却按 12.8 TB 付钱——16 倍的差距。这条规则也是 Intelligent-Tiering 不自动降级小于 128 KB 对象的原因。
  • 取回费把"冷"这个假设写进了合同。深度归档存 100 TB 一个月约 100 美元,听上去很便宜;但要是每月都得全量取回一遍,取回费加上传出费会让它比标准存储贵得多。分层的前提是访问确实稀疏,判断错了方向,省下的存储费会被取回费吃光。

再往外一层是出站流量费(互联网方向约 $0.09/GB,每月前 100 GB 免费)。它通常和存储费不在一个量级上:存 10 TB 一个月约 230 美元,把这 10 TB 拉出云一次约 900 美元。这条价格结构对架构的塑造力极强——它让"计算靠近数据"从一句箴言变成一条财务约束,也构成了实际存在的迁移成本。

为什么"把 S3 当文件系统用"总是出问题

几乎每个团队都会在某个时刻产生这个念头:用 s3fs、goofys 或者 Mountpoint 把桶挂到 /mnt/data,让老代码不用改就能跑。前十分钟一切正常,然后开始出事。原因不是实现不好,是语义在源头就对不上。

第一,没有重命名,所以提交协议是假的。 POSIX 里 rename() 是原子的、O(1) 的,几乎所有"先写临时目录、再一次性发布"的模式都建立在这上面。S3 没有 rename,任何"重命名"都是复制 + 删除:耗时正比于对象大小,中间不原子,且要花两次请求。Hadoop 经典的 FileOutputCommitter v1 算法正是靠"把任务输出目录 rename 到作业输出目录"来提交结果的——在 HDFS 上这是一次元数据操作,在 S3 上,提交一个产出 100 万个文件的作业等于 200 万次请求和一次完整的数据重写,可能比作业本身还慢,而且中途失败会留下一半发布、一半没发布的状态。业界后来专门写了 S3A committer(magic 与 staging 两种)绕开这个模型:利用分段上传"上传完但不 complete 就不可见"的特性,把提交时刻推迟到最后一次 CompleteMultipartUpload

第二,lsstat 是网络请求,还要计费。 在本地文件系统上,判断一个目录是否存在是一次内存中的 dentry 查找,微秒级。在 S3 上,判断前缀 a/b/ 是否"存在"必须发一次 LIST——因为目录本来就不存在,只能靠"有没有键以这个前缀开头"来推断。构建系统、findgit status 这类会做几万到几十万次 stat() 的程序,挂载后每一次都变成一次几十毫秒的网络往返。列一个千万对象的桶要 1 万次 LIST 请求(每页最多 1000 个键)。开发者看到的是"怎么这么慢",账单上看到的是 LIST 请求数暴涨。

第三,没有部分写,追加就是重写。 往日志文件末尾加一行,在 S3 上意味着下载整个对象、在本地拼接、再整个上传。FUSE 层为了维持 POSIX 假象只能在本地缓存整个文件,于是要么内存被大文件撑爆,要么写入延迟高得离谱。

第四,POSIX 的那一半元数据根本不存在。 没有 inode、没有硬链接与符号链接、没有 uid/gid/mode(S3 有 IAM 策略和 ACL,但那是另一套模型)、没有文件锁、mtime 是服务端写入时间而不是你能设定的值、也没有 fsync 对应的语义。AWS 官方的 Mountpoint for Amazon S3 在文档里把这些限制列得很直白:面向"大对象高吞吐读取"和"单客户端顺序写入新对象"优化,不支持随机写,通用桶上不支持重命名,文件锁、目录重命名、软硬链接、完整的文件模式控制均不支持。这是老实的做法——它没有假装自己是文件系统,只是在读多写少、顺序访问大文件这个交集里提供便利。

最有意思的是这件事的反转。2023 年 re:Invent 上 AWS 推出 S3 Express One Zone,其"目录桶"(directory bucket)与通用桶最大的区别就是:它把层级命名空间加回来了,数据按目录而非扁平前缀组织;2025 年 6 月又为它加上了单次 API 调用的原子重命名。也就是说,当目标从"近乎无限的规模"切换到"个位数毫秒的延迟"时,当年被砍掉的目录树,又被请了回来。

这恰恰印证了本文的主线:扁平键空间不是对目录树的降维打击,它是一次明码标价的交易。用原地修改、目录语义和跨对象事务,换水平扩展、单键的一致性和幂等重试。清楚自己站在交易的哪一侧,S3 是极好的工具;忘了自己做过这笔交易,它就会以延迟和账单的形式提醒你。

跨域连接

  • 概率论:11 个 9 的持久性不是测出来的,是算出来的——把单盘年化故障率、纠删码参数与重建窗口代入模型后得到的期望值。整个推导依赖故障相互独立这一前提;一旦故障有相关性(同批次固件缺陷、机架掉电、可用区事故),联合概率就不再是各自概率的乘积,模型算出的天文数字瞬间失去意义。这也是为什么 S3 标准存储把分片打散到至少三个可用区:真正的工程动作不是把小数点后的 9 再多加一个,而是尽力让"独立"这个数学假设在物理世界里成立。
  • 失效分析:可靠性工程里的浴盆曲线、平均修复时间与隐性失效,在存储系统里有一一对应。纠删码把"修复时间"变成了核心变量——重建窗口内系统的冗余度是降低的,窗口越长、期间又坏一块盘的概率越高,所以 LRC 减半重建流量的价值不只是省带宽,而是缩短了暴露窗口本身。而位腐蚀这类不报错的隐性失效,必须靠后台巡检主动扫描发现,否则会一直潜伏到重建时才暴露,届时已经无从补救。
  • 热力学定律:存储的本质是维持一个低熵的有序状态对抗环境噪声,而维持有序必须持续消耗能量。归档层每 TB 每月 1 美元与标准层二十三倍的价差,根子上是"随时可读"这个状态的能耗差——通电旋转、保持寻址就绪、承载并发读,每一项都在持续付电费。取回延迟从毫秒放宽到 12 小时,实质是允许系统把介质长时间置于低能耗状态,用时间换能量。分层定价表因此不是营销分段,而是物理成本曲线的直接投影。
  • 产业组织:出站流量约 $0.09/GB 而存储仅 $0.023/GB·月,意味着搬走 10 TB 数据的一次性成本接近存放四个月的费用。这在产业组织理论里是教科书式的转换成本:单价看似不高,却随数据积累线性增长,形成随时间自我强化的锁定。存储层因此成为云平台竞争的真正战场——计算可以随时换供应商,数据不行。S3 的 API 被众多厂商实现为兼容接口,也说明事实标准本身就是一种进入壁垒。
  • 认知偏差:把 S3 挂载成文件系统之所以屡试屡败,一半是技术问题,一半是心智模型迁移的问题。人一旦见到斜杠分隔的键和文件夹图标,就自动套用几十年养成的目录直觉——以为 ls 便宜、mv 原子、追加只写增量。可用性启发让熟悉的模型压过了实际语义,错误只有在账单和超时里才显形。界面像什么,比文档说什么更能塑造使用者的假设,这是接口设计中难以回避的代价。

参考文献

  • Werner Vogels, "Diving Deep on S3 Consistency", All Things Distributed, 2021 — S3 强一致性实现中缓存、witness 与操作顺序的官方说明。
  • James Bornholt et al., "Using Lightweight Formal Methods to Validate a Key-Value Storage Node in Amazon S3", SOSP 2021(最佳论文)— S3 存储节点 ShardStore 的结构与验证方法。
  • Cheng Huang et al., "Erasure Coding in Windows Azure Storage", USENIX ATC 2012(最佳论文)— 本地重建码 LRC 的设计与重建代价分析。
  • Subramanian Muralidhar et al., "f4: Facebook's Warm BLOB Storage System", OSDI 2014 — 小对象聚合后纠删码、有效复制系数从 3.6 降至 2.1 的工程路径。
  • Amazon Web Services, "Amazon S3 User Guide"(持续更新)— 一致性模型、条件写、存储类计费规则、分段上传与请求速率限制的权威口径。
  • Amazon Web Services, "Summary of the Amazon S3 Service Disruption in the Northern Virginia (US-EAST-1) Region", 2017 — 2017 年 2 月 28 日事故复盘,说明索引与放置子系统的角色及重启代价。

延伸阅读

  • Werner Vogels, "Eventually Consistent", Communications of the ACM, 2009 — 理解 2020 年之前 S3 一致性模型所处的思想背景。
  • Apache Hadoop 项目文档,"S3Guard: Consistency and Metadata Caching for S3A (retired)" — 一份完整记录了应用层如何绕开最终一致、以及为何在 2022 年被移除的一手材料。
  • awslabs, "Mountpoint for Amazon S3 — SEMANTICS.md" — 官方逐条列出对象存储与 POSIX 语义的差异,是"S3 不是文件系统"最简洁的证据清单。