高级阅读约 3 分钟

DynamoDB 并行扫描

一次并行扫描把一个 Scan 拆成 N 个独立的 Scan 请求,每个认领表的一个 Segment,这样 多个 worker 同时读它。这是把整张表读得比单个分区吞吐量允许的更快的唯一办法。

DynamoDB 并行扫描是什么?

DynamoDB 并行扫描通过 SegmentTotalSegments 把一个 Scan 拆成 N 个独立请求,每个认领表的一个 Segment,让多个 worker 并发读取。这是把整张表读得比单个分区吞吐量更快的唯一方式——但它仍是全表读取,你为每一个被扫描的条目付费。

  • 一次顺序 Scan 一次读一个分区——它的速度被钉在单个分区的吞吐量上,不管表有多大。
  • Segment + TotalSegments 把读取分片TotalSegments 个 worker;每个 worker 并行扫它自己的那一片。
  • DynamoDB 通过哈希 partition key 来分配 segment,所以分片可能不均衡——更多 worker 不一定意味着更快。
  • 它仍然是一个 Scan:你为读取每一个 item 付钱,而一个臃肿的并行扫描会从你的在线 流量底下把表的吞吐量抽干。

为什么顺序 Scan 慢

从 SQL 过来,全表读取感觉像一次流式操作。在 DynamoDB 里它不是。表的数据散落在许多物理 分区上,但单个 Scan 一次一个 地走它们,每页 1 MB。

这意味着一个普通 Scan 在某一刻只能从一个分区的吞吐量预算里取——哪怕表铺在几十个分区上、 还有闲置容量。表越大,它爬得越久。 (AWS:并行扫描

用 Segment 和 TotalSegments 拆分读取

一次并行扫描修掉这个瓶颈。你挑一个 worker 数量,把 TotalSegments 设成那个数,并给每个 worker 一个不同的、从零开始的 Segment。每个 worker 发出它自己的 Scan;DynamoDB 并发 地服务它们。

Worker 0Scan  Segment=0  TotalSegments=4
Worker 1Scan  Segment=1  TotalSegments=4
Worker 2Scan  Segment=2  TotalSegments=4
Worker 3Scan  Segment=3  TotalSegments=4

每个 worker 仍然独立地用 LastEvaluatedKey 分页——它从第一页到最后一页拥有自己的 segment。应用把这四条流再拼回去。你现在一次读到了四个分区的吞吐量,而不是一个。

一个实战例子:每夜导出

假设你跑一张遥测表,sensor-readings。每个 item 是一台现场设备的一次读数:

PK = "DEVICE#a83f"          (partition key — 设备 id)
SK = "TS#2026-06-22T03:14"  (sort key — ISO 时间戳)
batteryMv  = 3120
tempC      = 41.8
firmwareTag = "fw-7.2.1"

每晚一个 cron 任务把整张表转储到 S3,给分析仓库用。一次对 80 GB 的顺序 Scan 要花几个 小时,却几乎用不到你预置的读取容量。所以你把它扇出到八个 worker:

Scan  sensor-readings  Segment=0  TotalSegments=8  ConsistentRead=falseScan  sensor-readings  Segment=7  TotalSegments=8  ConsistentRead=false

八个 worker,八个 segment,一次表读取大约快了八倍。如果你只需要近期的读数,加一个 FilterExpression 在行上线之前丢掉旧时间戳——在 表达式构建器里构建并查看那个表达式:

FilterExpression:  begins_with(SK, :today)

DynamoDB 如何把 item 分配给 segment

这里是绊倒人的地方。DynamoDB 通过 哈希它的 partition key 把每个 item 分配给一个 segment——不是按行数,不是按字节数。

所以共享一个 PK 的每个 item 都落进同一个 segment。在 sensor-readings 里,DEVICE#a83f 的所有读数都去同一个 worker,不管那台设备有多少个时间戳、它的 item 集合有多大。 (AWS:并行扫描

sensor-readings哈希 partition keySegment 0DEVICE#a83fDEVICE#1c20Segment 1DEVICE#9be4Segment 2(空)

后果是:segment 不均匀。一个 worker 可能拥有三台话痨设备、上百万条读数;另一个可能抽到 一片空的。如果你的 partition key 扎堆,把 TotalSegments 调更高也帮不上忙——你只是加了些 闲着等热门那个的 worker。均匀的 key 分布才是让扇出物有所值的东西。

在运行之前看清读取成本

并行扫描是一桩吞吐量事件,不是免费午餐。诚实的问题是"读完整张表要花多少个读取单元?"—— 而 DynoTable 给你看一次扫描对你真实表的计量读取成本,逐 segment 地看,这样那个每夜任务就 不会在账单上给你惊吓。

坑,以及什么时候别费这劲

  • 吞吐量悬崖。 一次高 TotalSegments 的扫描可以在几秒内吃掉表的整个读取容量,把在线 流量饿死。在一张服务用户的表上,用 Limit 参数给每个 worker 限速,或在低峰扫描。 (AWS:并行扫描
  • 对一个访问模式来说它仍然是错的工具。 并行扫描是给深思熟虑的全表任务用的——导出、 回填、迁移。如果你伸手去拿它来回答一个反复出现的 query,那是一个建模信号:加一个 GSI,把它变成一个 Query
  • PartiQL 里的 SELECT * 是同一次扫描的伪装。 它编译成一个顺序 Scan。当你真正需要 跨 item 的分析——一个 GROUP BY、一个 JOIN、一个聚合——DynoTable 的 SQL Workbench 在一个有界的结果集上于客户端跑这些,而不是猛锤那张表。
  • 强一致让账单翻倍。 一个 Scan 默认是最终一致读取。对导出来说,除非你真的需要一个 时间点快照,否则把 ConsistentRead=false 留着。

下一步

把你的 key 建模好,让日常读取永远不需要扫描——从 单表设计Query vs Scan开始。当一个全表任务确实是对的选择时, 试用 DynoTable,对你自己的表跑一次并行扫描,并实时看着读取成本。

更新于