高级阅读约 3 分钟

DynamoDB 物理分区

一个 物理分区 是 DynamoDB 真正用来存你数据的单元:一片 SSD,跨可用区复制,装着你 key 空间的一片。你的表是一个逻辑的东西。分区才是字节——和吞吐量限制——真正住的地方。

DynamoDB 分区是如何工作的?

DynamoDB 将你的表存储在多个物理分区上——跨可用区复制的 SSD 片段。每个分区封顶在 ~10 GB、每秒 3,000 读取单元和每秒 1,000 写入单元。你的 partition key 的哈希决定一个 item 落在哪个分区上,DynamoDB 会在分区增长或变热时自动拆分它们。

  • 每个分区封顶在 ~10 GB 存储、每秒 3,000 读取单元、每秒 1,000 写入单元。 那些上限是 按分区的,不是按表的。
  • 你 partition key 的哈希挑分区。 相同 key 的 item 落在一起;一个热 key 意味着一个热 分区。
  • DynamoDB 替你拆分分区——按大小,以及按持续的热度——但它修不了一个把所有流量都漏斗到 一处的 key。
  • 在还有富余容量时被限流是那个信号。 在你的表只用了 5% 时出现一个 ProvisionedThroughputExceeded 错误,意味着单个分区被打满了。

一个 item 如何找到它的分区

DynamoDB 把你的 partition key 值喂进一个内部哈希函数。哈希输出挑物理分区。同样的 key 进去, 同样的分区出来——每一次都是。

从 SQL 过来,没有类比。没有你去调的索引 B 树,没有你手工指派的分片 key。放置是一个你不控制、 也永远看不见的哈希。

共享一个 partition key 的 item 组成一个 item 集合,存在一起并按 sort key 排序。那正是 为什么对一个 key 的 Query 便宜——它在一个分区上读一段连续的run。(见 Query vs Scan。)

拿一个游戏的对局事件存储来说。表的 key 是 arenaId(partition)和 eventKey(sort):

# Item
arenaId    = "ARENA#7f3a"
eventKey   = "EVT#1719100800#a91c"
playerTag  = "Nightjar"
dmgDealt   = 412

arena 7f3a 的每个事件都哈希到同一个分区,并按 sort key 顺序堆叠。对"读这局的时间线"很棒。 如果那一个 arena 拿到所有流量,就是个负担。

每个分区强制执行的三个上限

单个分区被设计为最多交付:

限制每分区计为
存储~10 GB原始 item 字节
读取容量每秒 3,000 读取单元1 RU = 一次 4 KB 的强一致读取
写入容量每秒 1,000 写入单元1 WU = 一次 1 KB 的写入

来源:AWS 设计 partition key 的最佳实践 指南。

item 大小会缩放这套算术。一个 20 KB 的 item 每次强一致读取花 5 个读取单元,所以一个分区在 被限流前服务约 600 次这样的读取/秒——不是 3,000。写入成本按每 1 KB 向上取整,读取成本按每 4 KB 向上取整。

陷阱:这些是 分区 限制,不是 限制。你的表可以预置为 40,000 WCU 却仍然被限流,因为所有 写入都在猛锤一个封顶在 1,000 的分区。

分区如何拆分

DynamoDB 在两种情况下自动增加分区。你从不运行命令。

按大小拆分。 当一个分区填向 ~10 GB 时,DynamoDB 把它的 key 范围一分为二,把一半 item 挪到一个新分区。存储透明地增长;你的读取和写入全程照常工作。

按热度拆分。 当一个分区承受接近其吞吐量上限的持续流量时,DynamoDB 拆分那个热 key 范围, 让每一半落在它自己的分区上。AWS 把这叫作 按热拆分 机制。会自己停下来的短暂限流突发通常 意味着刚发生了一次拆分。

大小热度分区 A~10 GB /拆分触发?范围按存储字节减半范围按流量减半两个分区各自有容量一个热 KEY仍在一个分区上

拆分跨许多 key 买来空间,但底部那个节点是要害:一次拆分分的是一个 key 范围,从不是单个 key。

为什么一个热 key 击败拆分器

这里是自坑。拆分重新分布的是 partition key 的 范围。如果你的流量集中在一个 key 值上,每个 请求都哈希到同一个分区,就没有范围可分了。

如果 arena 7f3a 是一场决赛,拉着 4,000 写入/秒,而其他每个 arena 都闲着,你会在 1,000 限流——而拆分帮不上忙,因为所有 4,000 都共享一个 key。较新的 KeyRangeThroughputExceeded 限流原因精确地点出这一点:是一个分区的 key 范围,不是表,超了它的限制。

修法在数据模型里,不在容量滑块上。写入分片 那个热 key:追加一个小后缀,让一个逻辑 arena 铺到 N 个物理分区上。

arenaId = "ARENA#7f3a#3"   # 分片 0..9,每次写入时选

读取随后扇出到各分片并在客户端合并。你可以在 DynamoDB 表达式构建器里原型化 key 形态和每个分片的 Query,再去碰一行应用代码。

一个细微之处:LSI 例外

有一种情况下存储 确实 是按 partition key 封顶的。没有 Local Secondary Index 时,一个 item 集合会按它需要的,自动跨多少分区就拆到多少分区——上十亿个 sort key 值都没问题。

加一个 LSI,那么一个 partition key 的整个集合就必须装进单个 10 GB 分区,因为 LSI 共享 它。那是 GSI vs LSI里讲到的"每 PK 悬崖"——也是多数团队伸手去拿 GSI 的又一个 原因。

设计得让分区保持凉爽

你实际控制的杠杆是 partition key。挑一个相对行数有许多不同值的,让流量均匀铺开。(更多模式在 单表设计里。)

  • 高基数 key。 一个按用户或按租户的 key 胜过一个大家在同一刻一起猛锤的按天或按状态的 key。
  • 留意已知的热 key。 一个"当前赛事"或"今天"的值在你上线 之前 就是一个集中风险,不是 之后。
  • 分片不可避免的热 key。 当一个 key 必须承受超大流量时,一个后缀是标准的逃生口。

在还有富余容量时被限流是你的信号:某个分区是热的。在 DynoTable里检查肇事的 item 并演练一个分片的 key 布局——把它指向你自己的表, 找到那个被过载的 key,并在它把你呼起来之前把修法建好模。

更新于