【问题标题】:DynamoDB read spikes using scanDynamoDB 使用扫描读取峰值
【发布时间】:2018-12-10 20:45:26
【问题描述】:

我有一个小作业,每分钟运行一次,并在一个有近 3000 行的表中执行扫描:

async execute (dialStatus) {
  if (!process.env.DIAL_TABLE) {
    throw new Error('Dial table not found')
  }

  const params = {
    TableName: process.env.DIAL_TABLE,
    FilterExpression: '#name = :name AND #dial_status = :dial_status AND #expires_on > :expires_on',
    ExpressionAttributeNames: {
      '#name': 'name',
      '#dial_status': 'dial_status',
      '#expires_on': 'expires_on'
    },
    ExpressionAttributeValues: {
      ':name': { 'S': this.name },
      ':dial_status': { 'S': dialStatus ? dialStatus : 'received' },
      ':expires_on': { 'N': Math.floor(moment().valueOf() / 1000).toString() }
    }
  }

  console.log('params', params)

  const dynamodb = new AWS.DynamoDB()
  const data = await dynamodb.scan(params).promise()
  return this._buildObject(data)
}

我在 dynamodb 上遇到了关于读取单元和超时的问题。现在,我使用 50 个读取单元,与 RDS 相比,它变得越来越贵。

scan 函数中使用的属性名称不是我的主键:name 是二级索引,dial_status 是我的 json 上的普通属性,但每一行都有这个属性。

此作业每分钟运行一次,以获取参数列表(即:如果我有 10 个参数,我将在一分钟内执行此scan 10 次)。

我的表具有以下架构:

  • 电话(PK 哈希)
  • 配置:字符串格式的 JSON;
  • dial_status 字符串;
  • expires_on:TTL 号;
  • 名称:字符串
  • 来源:字符串;

作业应根据 name 和 dial_status 获取所有项目,并且每次执行(每分钟)项目的数量限制为 15 个元素。对于每个元素,都应该在 SQS 上排队进行处理。

我确实需要减少这些读取单元,但我不确定如何优化此功能。我读过有关减小页面大小或避免扫描的信息。如果我没有主键并且我想返回一组行,我有哪些替代方法可以避免 scan

知道如何修复此代码,使其每分钟被调用 10-15 次吗?

【问题讨论】:

  • 您应该告诉我们更多关于您的表架构以及这项工作的用途。
  • 检查编辑,请@MatthewPope
  • 你是如何处理事情的?您是否希望每个项目只处理一次,无论何时更新、每天还是其他?
  • 正是一个。处理该项目时,它应该更新列dial_status。项目由另一个函数处理,完成后应该更新 dynamodb。如果可能的话,我可以检索项目,删除并发送到 sqs。
  • 如果您需要一次性处理,那么您应该使用 DynamoDB Streams。正如您正确识别的那样,任何类型的查询或表扫描都无法很好地扩展。查看aws.amazon.com/blogs/database/…

标签: javascript node.js amazon-dynamodb


【解决方案1】:

我建议你用键创建一个 GSI(全球二级索引):

  • 哈希:name_dialStatus
  • 范围:expiresOn

正如您已经猜到的,哈希键的值是namedialStatus 两个独立字段的串联。

现在您可以在此 GSI 上使用查询,因为它不会扫描所有表,而是仅探索您感兴趣的项目,因此效率更高:

async execute(dialStatus) {
  if (!process.env.DIAL_TABLE) {
    throw new Error('Dial table not found')
  }

  const params = {
    TableName: process.env.DIAL_TABLE,
    IndexName: 'MY_GSI_NAME',
    // replace `FilterExpression`
    // always test the partition key for equality!
    KeyConditionExpression: '#pk = :pk AND #sk > :skLow', 
    ExpressionAttributeNames: {
      '#pk': 'name_dialStatus', // partition key name
      '#sk': 'expires_on' // sorting key name
    },
    ExpressionAttributeValues: {
      ':pk': { 'S': `${this.name}:${dialStatus || 'received'}` },
      ':skLow': { 'N': Math.floor(moment().valueOf() / 1000).toString() }
    }
  }

  console.log('params', params)

  // Using AWS.DynamoDB.DocumentClient() there is no need to specify the type of fields. This is a friendly advice :)
  const dynamodb = new AWS.DynamoDB();
  // `scan` becomes `query` !!!
  const data = await dynamodb.query(params).promise();
  return this._buildObject(data);
}

【讨论】:

  • 使用Limit安全吗?或者Limit 像扫描一样工作?它会根据 Limit 获取一些项目,然后应用我的 Expressions?
  • 我认为Limit 不安全。当您开始扫描时,您将访问很多项目,直到您获得Limit 指定的项目数量。因此,您会更快完成(然后是完整扫描),但您仍然会迟到(这仅取决于您的 运气 和哈希算法。主要取决于您的运气。您是否很幸运?:) )。查询只访问你关心的项目,没有噪音,也不需要运气。 ;)
【解决方案2】:

始终建议根据访问模式设计 dynamodb 表,以便使用键(主键/排序键)轻松查询它并避免昂贵的扫描操作。

  1. 如果为时不晚,请重新访问您的表架构。
  2. 如果已经晚了,则创建 GSI,将“name”作为 PrimaryKey,将“expires_on”作为具有 Projected 属性的 SortKey,例如“dialStatus”,这样您就可以只查询所需的数据以降低就绪容量。
  3. 如果您仍然不想使用选项 1 和选项 2 使用 RateLimiter 进行扫描操作并仅传递 25% 的读取容量,这样您就可以避免尖峰。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2019-06-19
    • 1970-01-01
    • 2021-05-25
    • 2016-10-15
    相关资源
    最近更新 更多