【问题标题】:How can I update an attribute in a DynamoDB table regardless of the key or condition?无论键或条件如何,如何更新 DynamoDB 表中的属性?
【发布时间】:2020-08-20 22:05:58
【问题描述】:

我有一个 DynamoDB 表,例如 data。该表有 400k 项。每个项目有 4 个字段 -

  • id (string) 这是我的分区键
  • 状态(是/否)
  • 添加日期
  • 来源

现在所有项目都有一个status = "Y"。无论密钥或任何条件如何,如何更新所有项目并将所有 400k 项目的状态设置为“N”?

在 MySQL 中,等效语句是 -

UPDATE data SET status = 'N';

我希望通过命令行或在 python 中使用 boto3 来完成它

【问题讨论】:

    标签: python amazon-web-services amazon-dynamodb boto3


    【解决方案1】:

    没有简单或便宜的方法来做你想做的事。您基本上需要做的是读取写入整个数据库:

    1. 如果您知道单个项目的密钥,则可以使用UpdateExpression"set status = :N" 进行UpdateItem 请求。这只会修改“状态”属性(其余部分保持不变),但您将产生的成本(或您将使用的预置吞吐量)将是编写整个项目的成本。所以所有这些操作的总和将是重写整个数据库的成本。
    2. 您应该在上面的UpdateItem 中添加一个ConditionExpression,它只会在项目实际仍然存在时更新项目(您可以在其关键属性上使用attribute_exists() 条件来验证项目是否存在)。这将允许您的工作负载在进行这些更改时删除项目。
    3. 在开始此更改过程之前,请更改您的客户端代码以编写状态 = N 的 项。更改过程可能会遗漏这些新项目,但如果它们已经创建且状态 = N 也没关系.
    4. 您不能使用BatchWriteItems(boto3 中的batch_writer())一起修改一组项目,因为此批量操作只能替换项目 - 不能修改现有项目的属性。在任何情况下,BatchWriteItems 都不会降低成本(批次成本与它们包含的请求相同)。

    阅读

    1. 要获取数据库中所有现存键的列表,要进行上述读取,您需要使用Scan 操作,将Projection 设置为KEYS_ONLY 以仅获取键(您不需要需要数据)。不幸的是,您的成本将与阅读整个项目相同,而不仅仅是阅读钥匙。因此,所有这些 Scan 操作的成本总和将是读取整个数据库。

    如果您为此表使用预置容量,则您可以使用客户端请求未使用的任何多余容量在后台缓慢地进行此更改,基本上是“免费”。

    这在您的情况下是否有意义实际上取决于您配置了多少过剩容量(读取和写入!)。如果你这样做,你需要注意不要为这个后台操作使用太多容量并伤害你的真实用户 - 你需要有某种控制器来通知容量超出错误并减少容量由后台进程使用。

    如果您实际上有很多已经支付的超额预置容量,您可以随心所欲地执行此后台操作!读取部分 Scan 可以尽可能快地并行完成(使用“并行扫描”功能),不同键的写入部分显然也可以并行完成。

    【讨论】:

      【解决方案2】:

      以下代码使用batch_write_item DynamoDB API 以 25 的大小批量更新项目,这是 batch_write_item 在单个 API 调用中可以接收的最大项目数。如果您的商品很大,您可能需要调整这个数字。

      警告:这只是一个概念证明示例。您应自行承担使用风险。

      import boto3
      
      def update_status(item):
          item['status'] = {
              'S': 'N'
          }
          return item
      
      client = boto3.client('dynamodb', region_name='<ddb-region>')
      paginator = client.get_paginator('scan')
      operation_parameters = {
        'TableName': '<ddb-table-name>',
        'PaginationConfig': {
          'PageSize': 25
        }
      }
      page_iterator = paginator.paginate(**operation_parameters)
      for page in page_iterator:
          response = client.batch_write_item(RequestItems={
              '<ddb-table-name>': [
                  {
                      'PutRequest': {
                          'Item': update_status(item)
                      }
                  }
                  for item in page['Items']
              ]
          })
          print(response)
      

      【讨论】:

      • 请注意,此示例代码读取整个现有项目,在客户端修改它们,然后写回 整个 项目。如果对同一个数据库有并发写入,这是有风险的,而且是错误的——您的循环可能会覆盖那些其他更改。这就是为什么在我上面的回答中我建议更新 just 'status' 属性 - 而不是重写整个项目(我也提到了 BatchWriteItem,但那是错误的,我现在会修复它)。
      • 嗯,这只是一个 PoC。您当然可以添加一些条件来防止并发写入。 @NadavHar'El
      • 但是如果你可以让你的写修改'status'属性,你不需要阻止并发写,没有别的。没有真正需要读取所有属性并将它们全部写回。我在自己的回答中指出,您确实需要添加一个条件来处理并发删除(以防有人在您“修复”其“状态”的同时删除一个项目)。
      • 一次更新一项非常慢。 @NadavHar'El 这只是加快速度的另一个想法。
      • 正如我在回答中指出的那样,您可以根据需要并行化此更新。您可以并行发送 100 个 UpdateItem 请求。不幸的是,DynamoDB 不支持 HTTP 2,因此您需要通过 100 个 HTTP 连接执行这 100 个请求,但它仍然很实用。我同意你的观点,如果你有相对较小的项目并且没有并发更新,你的方法也会很好。
      猜你喜欢
      • 2018-10-10
      • 1970-01-01
      • 1970-01-01
      • 2022-01-22
      • 1970-01-01
      • 2021-07-22
      • 2023-01-26
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多