高级阅读约 3 分钟

从 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、读修复)没了。
  • 你不再调那些旋钮。 论文里的 NRW 变成了一个选择: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 固定
一致性旋钮RW 法定多数调节一个标志:ConsistentRead
冲突解决向量时钟,读取时应用端合并后写者胜;你选择加入条件写入
成员关系peer 之间的 gossip 协议完全托管;对你隐形
多 key 操作无——纯键值Query、GSI、事务叠在上面

论文的 API 是两个调用:get(key)put(key, value)。DynamoDB 在同样的键值内核上加了一个 sort key、索引和 query——这就是为什么一个 Query 便宜(一个分区)而一个 Scan 不便宜(它走 环曾创造的每一个分区)。

一次写入如何旅行,那时与现在

下面的流程对比论文的法定多数写入和 DynamoDB 的托管写入。形状押韵;责任从你的代码挪到了 AWS。

论文:你调 N,R,WDynamoDB:固定 3份可用区副本put(key, value)哈希 key 到环上写入 N 个副本收到 W 个确认?读取时通过向量时钟调和后写者胜,法定多数隐藏

在论文里你拥有那套法定多数算术和合并;在 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 追上来。

更新于