DynamoDB 热分区检测与缓解

前提条件:本文假设已理解分区键、全局二级索引(GSI)及其设计影响。如未了解这些概念,建议先阅读相关内容。

分区键与全局二级索引基础

分区键

分区键决定数据存储在哪个物理分区。DynamoDB 使用分区键值计算哈希,将数据分布到多个物理存储分区。分区键的选择直接影响:

  • 数据分布均匀性
  • 访问模式性能
  • 系统扩展能力

全局二级索引(GSI)

GSI 是独立的索引结构,具有自己的分区键和排序键。主要特性:

  • 支持不同的查询模式
  • 独立配置吞吐量
  • 最终一致性读取
  • 需要额外的写入容量(每次写入主表同时写入 GSI)

设计影响

  • 分区键基数:唯一值数量过少会导致数据倾斜。DynamoDB 建议选择具有大量不同值的属性作为分区键,这些值应相对于表中的项目数量而言
  • 键分布:访问模式必须均匀分布到不同键值,避免集中在特定键
  • GSI 开销:每个 GSI 增加写入成本和存储成本。GSI 是独立的索引结构,数据异步复制,需要为 GSI 配置独立的吞吐量
  • GSI 分区键:需要与主表分区键同样谨慎设计。GSI 的分区键选择不当同样会导致热分区问题

分区键高基数设计

高基数的重要性

分区键应具有高基数(大量唯一值),以确保数据均匀分布。DynamoDB 使用分区键值的哈希函数确定存储位置。低基数分区键会导致:

  • 数据集中在少数分区
  • 访问流量集中在特定分区键值
  • 分区级限流(热分区)

高基数与低基数对比

推荐的高基数属性

  • userId(用户唯一标识)
  • orderId(订单唯一标识)
  • sessionId(会话唯一标识)
  • deviceId(设备唯一标识)

应避免的低基数属性

  • status(如:active/inactive)
  • region(如:us-east-1, us-west-2)
  • type(如:admin, user, guest)
  • createdDate(日期格式,如:2024-12-22)
  • isActive(布尔值)

根据 AWS 最佳实践,分区键应具有大量不同的值,且这些值应相对于表中的项目数量而言。这意味着项目数量增长时,分区键值的分布应保持相对均匀。

高基数场景下的挑战

即使使用高基数分区键,仍可能出现热分区:

  • 某些用户比其他用户活跃度高 10 倍
  • VIP 客户访问模式与普通客户不同
  • 特定时间段内某些业务实体流量激增

这些情况需要采用键扩展或时间分桶策略。

高基数设计原则

  1. 评估业务访问模式:识别可能产生流量偏斜的业务场景
  2. 监控访问分布:使用 CloudWatch Contributor Insights 监控键值访问频率
  3. 预留扩展能力:即使初期数据分布均匀,也应预留应对未来流量变化的能力
  4. 考虑复合键方案:当单一属性无法满足分布需求时,使用复合分区键

复合分区键分隔符

# 符号的作用

分区键 partitionKey = accountId#YYYYMM 中的 # 是复合分区键的分隔符。

技术含义

# 将多个值连接成单个分区键:

partitionKey = value1#value2#value3

示例(accountId=“ACC123”, 年月=“202412”):

partitionKey = "ACC123#202412"

使用优势

  • 可读性:便于日志分析和调试
  • 查询支持:可使用 begins_with() 查询前缀匹配
  • CloudWatch 集成:Contributor Insights 可按前缀分析
  • AWS 标准:AWS 官方文档推荐的复合键格式

查询示例

// 查询特定账户-月份
KeyConditionExpression: "partitionKey = :pk",
ExpressionAttributeValues: {
  ":pk": "ACC123#202412"
}

// 查询特定账户所有月份
KeyConditionExpression: "begins_with(partitionKey, :prefix)",
ExpressionAttributeValues: {
  ":prefix": "ACC123#"
}

分隔符选择

# 是推荐分隔符,备选方案:

  • |(竖线)
  • _(下划线)
  • -(连字符)

注意:分隔符不应在业务数据中出现。

热分区定义

热分区指单个分区键值接收不成比例的高流量,超出其支持的物理分区吞吐量限制。

关键概念:DynamoDB 按分区级别而非表级别进行限流。因此,即使整体表指标正常,也可能存在单个键值导致限流的情况。

热分区难以察觉的原因

CloudWatch 显示内容

  • 表级别的读取容量单位(RCU)和写入容量单位(WCU)
  • 平均延迟
  • 总体限流次数

CloudWatch 隐藏内容

  • 哪个分区键值已过载
  • 哪种访问模式导致问题
  • 哪个 GSI 是实际原因

结果:常将问题归咎于流量峰值、SDK 或 AWS 本身,而非数据模型设计。

生产环境典型症状

出现以下三种情况时,极可能存在热分区:

  1. 间歇性限流
  2. 仅影响部分用户或账户
  3. 重试无法解决问题

这不是随机行为,而是确定性的,与键值设计直接相关。

常见热分区成因

以下分区键设计会导致热分区:

  • status
  • createdDate
  • region
  • isActive
  • type

更严重的情况:GSI 分区键 = status 这不是二级索引,而是集中式流量漏斗。

实际系统中的检测方法

1. 按操作类型分析限流

如果限流主要发生在以下操作:

  • Query
  • UpdateItem
  • PutItem

通常表明正在访问特定键值,而非达到表限制。

2. 启用 Contributor Insights

CloudWatch Contributor Insights 提供分区键级别的访问和限流分析。DynamoDB 集成 CloudWatch Contributor Insights 支持两种模式:

限流键模式:专注于限流请求,提供性能问题的洞察,无需跟踪所有访问模式的开销。

  • 识别最常被限流的项目
  • 适合持续启用以进行实时限流检测
  • 成本优化模式

访问键和限流键模式:全面监控访问和限流项目。

  • 识别访问频率最高的项目
  • 识别最常被限流的项目
  • 适合需要完整访问模式可见性的场景

两种模式均提供:

  • 分区键值排名
  • 每个键值的访问频率
  • 流量分布偏斜模式
  • 限流计数和消耗的吞吐量单位
  • 时间序列图表显示趋势

配置建议:生产环境应至少启用限流键模式,以获得关键可见性。如需分析访问模式,可临时启用访问键和限流键模式进行诊断。CloudWatch Contributor Insights 的费用基于处理的 DynamoDB 事件数量和选择的模式。限流键模式下,只有限流请求产生计费事件,更具成本效益。

3. 关联业务数据

需要分析:

  • 哪个客户具有最高流量?
  • 哪个账户是"特殊"账户?
  • 哪个工作流在特定时间激增?

热分区通常与业务异常值对齐。

修复策略

策略 1:键扩展(最实用的修复方法)

不使用:

partitionKey = userId

改用:

partitionKey = userId#bucket

其中:- bucket = hash(timestamp) % N- N 的值为 5–20

结果:- 写入操作分散到多个分区- 读取操作保持可预测性- 模式变更最小

这是写入密集型系统的最低风险修复方案。

策略 2:基于时间的分桶

如果流量按时间窗口激增:

不使用:

partitionKey = accountId

改用:

partitionKey = accountId#YYYYMM

结果:- 自然流量分布- 易于归档- 可预测的增长

权衡:跨月份查询需要多个请求,但在大多数场景下值得采用。

策略 3:重新评估 GSI 设计

如果热分区发生在 GSI,问题比预期更严重。GSI 是独立的索引结构,具有自己的分区键和吞吐量配置。

GSI 热分区特点

  • GSI 具有独立的吞吐量配置,与主表分开计费
  • GSI 分区级限流独立于主表限流
  • 写入操作首先受到影响(每次写入主表都会触发 GSI 写入)
  • GSI 数据异步复制,查询结果可能不是最新的

GSI 设计考量

  • 多属性键支持:GSI 支持由多个属性组成的复合分区键(最多4个分区键属性和4个排序键属性),可简化数据建模,避免手动连接属性
  • 投影策略:GSI 可以投影所有属性或仅投影必要属性。投影较少属性可降低存储成本和写入容量消耗,但限制查询灵活性。建议仅投影查询所需的属性
  • 最终一致性:GSI 只支持最终一致性读取,应用应处理查询结果可能不是最新的情况

修复方案

  • 增加 GSI 分区键的基数,避免低基数属性作为分区键
  • 优化 GSI 投影,减少不必要属性的投影以降低写操作开销
  • 使用多属性键改善分布,避免热键集中
  • 考虑使用稀疏索引(Sparse Index)模式,仅对需要的项目进行索引
  • 如果 GSI 是核心流量路径,考虑完全移除并重新设计数据模型

重要提醒:如果主要工作流依赖 GSI,且 GSI 存在热分区问题,数据模型可能需要根本性调整。GSI 的分区键设计应与主表同样谨慎。

不应采取的措施

以下措施仅延迟失败,无法解决问题:

  • 增加表容量
  • 添加重试逻辑
  • 添加更多 GSI
  • 忽略"单个客户"问题

核心结论

热分区不是:

  • 随机现象
  • 临时问题
  • AWS 缺陷

热分区是设计反馈。DynamoDB 非常可预测——前提是理解其数据分布机制。

设计原则

如果一个分区键可能接收比其他键多 10 倍的流量,最终会发生这种情况。设计应基于这一现实,而非理想情况。