从 Dynamo 论文到 DynamoDB
2007 年的 "Dynamo: Amazon's Highly Available Key-value Store" 论文和你今天调用的 DynamoDB 共享一个名字和一个目标——任何规模下都可预测的性能——但它们不是同一个系统。论文 描述的是一个你自己运行的、内部的、最终一致的存储。DynamoDB 是一个托管服务,它保留了教训, 扔掉了大部分机械装置。
DynamoDB 是基于 Dynamo 论文的吗?
部分是。DynamoDB 的名字和核心目标——大规模下可预测的性能和高可用性——都来自 2007 年的 Amazon Dynamo 论文,而且它几乎一字不差地保留了 partition key 哈希这个想法。但它是一个不同的托管系统:论文里的向量时钟、gossip 成员关系,以及可调的读/写法定多数都没了,取而代之的是 AWS 自有的内部机制。
- 论文解决的是可用性,不是好用。 它的活儿是在假日流量尖峰期间永不拒绝一次写入,哪怕代价 是返回一次陈旧的读取。
- DynamoDB 保留了形态,替换了内部。 按 key 的哈希分区、跨可用区复制、横向扩展——但冲突 解决的内脏(向量时钟、gossip、读修复)没了。
- 你不再调那些旋钮。 论文里的
N、R和W变成了一个选择:ConsistentRead真或假。 其余的 AWS 拥有。 - 那个心智模型仍然有回报。 知道这条血脉解释了为什么一个
Scan贵、为什么一个 GSI 读取 会滞后——两者都从最初的设计里掉出来。
论文到底在解决什么
Amazon 的购物车不能宕。一个在负载下拒绝写入——或在一个失败的副本上阻塞——的关系型数据库是 不可接受的。2007 年的 Dynamo 论文选了 可用性而非一致性:总是接受写入,稍后再调和分歧。 那个交换是下面一切的根。
为了不靠单个 master 做到这点,Dynamo 得自己回答两个问题:一个 key 住在哪,以及一次读取或写入 算数之前必须有多少份副本达成一致?
一致性哈希:一个 key 住在哪
论文把每个节点放在一个哈希环上。一个 key 的位置是它 key 的哈希;它被顺时针的下一个节点拥有,
并复制到接下来的 N-1 个节点。添加或移除一个节点只重排它邻居的 key——不是整个数据集。那是
一致性哈希,是 DynamoDB 几乎一字不差保留的那一个想法。
DynamoDB 仍然哈希你的 partition key 来决定哪个物理分区存这个 item。挑一个低基数的 partition
key——比如有两个值的 STATUS——那么带相同值的每个 item 都落在同一个分区里。那是 热分区
自坑,是环的一个直接后果:哈希把相同的 key 送到相同的家。
法定多数:多少份副本必须一致
论文的第二个旋钮是一个 法定多数。有 N 个副本时,一次写入在它们中 W 个确认后成功,
一次读取查询它们中的 R 个。设 R + W > N,那么任何读取都至少与一个持有最新写入的节点重叠——
强一致。把它们设低,你就用新鲜换速度和正常运行时间。
Dynamo 跑"草率"法定多数:如果一个目标节点宕了,写入去一个替补,稍后再交还(hinted handoff)。 冲突的版本被打上 向量时钟,并在读取时由应用调和。
DynamoDB 保留了什么、改了什么
DynamoDB 继承了目标和分区,然后删掉了让原系统难以运维的那些部分。
| 关注点 | 2007 Dynamo 论文 | 今天的 DynamoDB |
|---|---|---|
| key 放置 | 一致性哈希环 | partition key 的哈希 → 托管分区 |
| 复制 | N 个节点,你选 | 跨可用区 3 份,由 AWS 固定 |
| 一致性旋钮 | R、W 法定多数调节 | 一个标志:ConsistentRead |
| 冲突解决 | 向量时钟,读取时应用端合并 | 后写者胜;你选择加入条件写入 |
| 成员关系 | peer 之间的 gossip 协议 | 完全托管;对你隐形 |
| 多 key 操作 | 无——纯键值 | Query、GSI、事务叠在上面 |
论文的 API 是两个调用:get(key) 和 put(key, value)。DynamoDB 在同样的键值内核上加了一个
sort key、索引和 query——这就是为什么一个 Query 便宜(一个分区)而一个 Scan 不便宜(它走
环曾创造的每一个分区)。
一次写入如何旅行,那时与现在
下面的流程对比论文的法定多数写入和 DynamoDB 的托管写入。形状押韵;责任从你的代码挪到了 AWS。
在论文里你拥有那套法定多数算术和合并;在 DynamoDB 里那整个下半部分是托管的,你每个请求只
选 ConsistentRead。
这条血脉在哪里渗进你的代码
最终一致的默认就是论文透出来的。一个 global secondary index 被异步复制,所以一个刚写入的 item 可能有一瞬间从索引里缺失——同样的"稍后调和"的交换,只是在索引层。见 GSI vs LSI了解那个滞后何时要紧。
你有两种方式把强一致买回来。在一次基表读取上用 ConsistentRead: true(它路由到 leader
副本),或用一个 ConditionExpression 给一次写入加护栏,让它只在 item 的当前状态匹配时才
落地。在
DynamoDB 表达式构建器里勾画一个——例如
attribute_not_exists(PK) 让一个 PutItem 成为一个仅插入的操作,是论文里冲突检测的现代
替身。
要记住的那一件事
论文为永不对一次写入说不而优化。DynamoDB 继承了那个偏向,这就是为什么它的默认偏好可用性、
为什么强读取花得更多。为单分区 Query 把你的 key 建好模,就像
单表设计里那样,并且
只在你真正必须时才伸手去拿一个 Scan——环让一次全表行走和它听起来
一样贵。
试用 DynoTable,去检查你的分区、按需跑一致性读取,并对你自己的表看着一个 GSI 追上来。