【问题标题】:What is the recommended way to delete a large number of items from DynamoDB?从 DynamoDB 中删除大量项目的推荐方法是什么?
【发布时间】:2012-02-27 13:59:14
【问题描述】:

我正在 DynamoDB 中编写一个简单的日志记录服务。

我有一个由 user_id 哈希和时间戳(Unix epoch int)范围作为键的日志表。

当服务的用户终止他们的帐户时,我需要删除表中的所有项目,无论范围值如何。

进行此类操作的推荐方法是什么(记住可能有数百万个项目要删除)?

据我所知,我的选择是:

A:执行 Scan 操作,对每个返回的项目调用 delete,直到没有剩余项目为止

B:执行 BatchGet 操作,对每个项目再次调用 delete,直到没有剩余为止

这两个在我看来都很糟糕,因为它们需要很长时间。

我最理想的做法是调用 LogTable.DeleteItem(user_id) - 不提供范围,让它为我删除所有内容。

【问题讨论】:

    标签: database nosql amazon-web-services cloud amazon-dynamodb


    【解决方案1】:

    这是一个递归函数,用于删除带有batchWriteItems 的所有项目。定义你的表的键模式和表名并调用clearTable:

    var AWS = require("aws-sdk");
    var docClient = new AWS.DynamoDB.DocumentClient();
    
    const TABLE_NAME = ""
    const TABLE_PRIMARY_KEY = ""
    
    const clearTable = async () => {
    
        const batch = await getItemBatch();
    
        await recursiveDeleteTableItems(batch)
    
    }
    
    const recursiveDeleteTableItems = async (batch) => {
    
        if(batch && batch.length > 0) {
          await deleteItemBatch(batch)
        } else {
          return
        }
    
        const newItemBatch = await getItemBatch()
    
        await recursiveDeleteTableItems(newItemBatch)
    
    }
    
    const deleteItemBatch = async (batch) => {
    
       const deleteOperations = batch.map( i => ({ 
         "DeleteRequest": { 
           "Key": { 
            [TABLE_PRIMARY_KEY] : i.KEY_VALUE
           }
         }
       }))
    
       return new Promise(async (resolve, reject) => {
    
         const params = {
           "RequestItems": {
             [TABLE_NAME]: deleteOperations
           }
         }
    
         docClient.batchWrite(params, (err, data) => {
    
           if (err) {
             reject(`Unable to query. Error: ${err} ${JSON.stringify(err, null, 2)}`);
             return
           }
    
           resolve(data)
    
          })
    
        })
    
    }
    
    const getItemBatch = async () => {
    
      var params = {
        TableName: TABLE_NAME,
        Limit: 25 // match batchWriteItem
      };
    
      return new Promise(async (resolve, reject) => {
    
        docClient.scan(params, async function (err, data) {
    
            if (err) {
                reject(`Unable to query. Error: ${err} ${JSON.stringify(err, null, 2)}`);
                return
            }
    
            resolve(data.Items)
    
          });
      });
    
    }
    

    【讨论】:

      【解决方案2】:

      所以只是一个更新,DynamoDB 控制台上有一个版本,其中包含一个名为 PartiQL 编辑器的新功能。它是用于 DynamoDB 操作的类似 SQL 的编辑器。

      删除特定记录

      DELETE FROM <Table-Name> WHERE id=some-Id;
      

      缺点:一次只能删除一项

      【讨论】:

        【解决方案3】:

        考虑过使用测试来传递变量吗?比如:

        测试输入类似于:

        {
          "TABLE_NAME": "MyDevTable",
          "PARTITION_KEY": "REGION",
          "SORT_KEY": "COUNTRY"
        }
        

        调整您的代码以接受输入:

        const AWS = require('aws-sdk');
        const docClient = new AWS.DynamoDB.DocumentClient({ apiVersion: '2012-08-10' });
        
        exports.handler = async (event) => {
            const TABLE_NAME = event.TABLE_NAME;
            const PARTITION_KEY = event.PARTITION_KEY;
            const SORT_KEY = event.SORT_KEY;
            let params = {
                TableName: TABLE_NAME,
            };
            console.log(`keys: ${PARTITION_KEY} ${SORT_KEY}`);
        
            let items = [];
            let data = await docClient.scan(params).promise();
            items = [...items, ...data.Items];
            
            while (typeof data.LastEvaluatedKey != 'undefined') {
                params.ExclusiveStartKey = data.LastEvaluatedKey;
        
                data = await docClient.scan(params).promise();
                items = [...items, ...data.Items];
            }
        
            let leftItems = items.length;
            let group = [];
            let groupNumber = 0;
        
            console.log('Total items to be deleted', leftItems);
        
            for (const i of items) {
                // console.log(`item: ${i[PARTITION_KEY] } ${i[SORT_KEY]}`);
                const deleteReq = {DeleteRequest: {Key: {},},};
                deleteReq.DeleteRequest.Key[PARTITION_KEY] = i[PARTITION_KEY];
                deleteReq.DeleteRequest.Key[SORT_KEY] = i[SORT_KEY];
        
                // console.log(`DeleteRequest: ${JSON.stringify(deleteReq)}`);
                group.push(deleteReq);
                leftItems--;
        
                if (group.length === 25 || leftItems < 1) {
                    groupNumber++;
        
                    console.log(`Batch ${groupNumber} to be deleted.`);
        
                    const params = {
                        RequestItems: {
                            [TABLE_NAME]: group,
                        },
                    };
        
                    await docClient.batchWrite(params).promise();
        
                    console.log(
                        `Batch ${groupNumber} processed. Left items: ${leftItems}`
                    );
        
                    // reset
                    group = [];
                }
            }
        
            const response = {
                statusCode: 200,
                //  Uncomment below to enable CORS requests
                headers: {
                    "Access-Control-Allow-Origin": "*"
                },
                body: JSON.stringify('Hello from Lambda!'),
            };
            return response;
        };
        

        【讨论】:

          【解决方案4】:

          我从表 i DynamoDb 中删除所有行的方法只是使用 DynamoDbs ScanAsync 从表中拉出所有行,然后将结果列表提供给 DynamoDbs AddDeleteItems。 下面的 C# 代码对我来说很好。

                  public async Task DeleteAllReadModelEntitiesInTable()
              {
                  List<ReadModelEntity> readModels;
          
                  var conditions = new List<ScanCondition>();
                  readModels = await _context.ScanAsync<ReadModelEntity>(conditions).GetRemainingAsync();
          
                  var batchWork = _context.CreateBatchWrite<ReadModelEntity>();
                  batchWork.AddDeleteItems(readModels);
                  await batchWork.ExecuteAsync();
              }
          

          注意:如果使用 YAML/CloudFormation 创建表,删除表然后从 Web 控制台重新创建可能会导致问题。

          【讨论】:

            【解决方案5】:

            我们没有截断发电机表的选项。我们必须删除表并重新创建。 DynamoDB 费用基于 ReadCapacityUnits 和 WriteCapacityUnits 。如果我们使用 BatchWriteItem 函数删除所有项目,它将使用 WriteCapacityUnits。所以最好删除特定记录或删除表重新开始。

            【讨论】:

              【解决方案6】:

              如果您想在一段时间后删除项目,例如一个月后,只需使用“生存时间”选项。它将计算写入单位。

              在您的情况下,我会在日志过期时添加 ttl,并在删除用户后保留这些内容。 TTL 将确保最终删除日志。

              当在表上启用生存时间时,后台作业会检查 商品的 TTL 属性,查看是否过期。

              DynamoDB 通常会在 48 小时内删除过期项目 到期。项目真正被删除的确切持续时间 到期后具体取决于工作量的性质和 表的大小。已过期且未删除的项目将 仍然出现在读取、查询和扫描中。这些项目仍然可以 更新并成功更新以更改或删除过期 属性将被尊重。

              https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/TTL.html https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/howitworks-ttl.html

              【讨论】:

              • 添加 TTL 是“更新”(写入操作)。我不确定进行“更新”而不是“删除”有什么好处。
              • 您可以使用原始写入插入该数据并使用任何其他更新操作进行更新。当然,如果你有一堆数据然后你想删除它,这不是一个选择。但对于您可以为插入或更新的数据设置 ttl 的情况,这是一个有效的选项。
              • 我同意,如果已经配置了 TTL 并且清理可以等待长达 48 小时,那绝对是最佳选择。如果我不清楚,我很抱歉。
              【解决方案7】:

              我最理想的做法是调用 LogTable.DeleteItem(user_id) - 在不提供范围的情况下,让它为我删除所有内容。

              确实是一个可以理解的请求;我可以想象 AWS 团队可能会随着时间的推移添加此类高级操作(他们有先从有限的功能集开始并根据客户反馈评估扩展的历史),但您应该采取以下措施来避免成本至少完整扫描:

              1. 使用Query 而不是Scan 来检索user_id 的所有项目 - 无论使用的组合哈希/范围主键如何,这都有效,因为 HashKeyValueRangeKeyCondition在这个API中是单独的参数,前者只针对复合主键的散列分量的属性值。.

                • 请注意,您必须像往常一样在此处处理查询 API 分页,请参阅 ExclusiveStartKey 参数:

                  从其继续先前查询的项目的主键。一个 较早的查询可能会将此值作为 LastEvaluatedKey 提供,如果 查询操作在完成查询之前被中断;任何一个 由于结果集大小或 Limit 参数。这 LastEvaluatedKey 可以在新的查询请求中传回以继续 从那时开始的操作。

              2. 遍历所有返回的项目,或者像往常一样促进DeleteItem

                • 更新:很可能BatchWriteItem 更适合这样的用例(详见下文)。

              更新

              正如ivant 所强调的那样,BatchWriteItem 操作使您能够在单个 API 调用中跨多个表放置或删除多个项目 [强调我的]

              要上传一项,您可以使用 PutItem API 并删除一项 项目,您可以使用 DeleteItem API。但是,当您要上传 或删除大量数据,如上传大量 来自 Amazon Elastic MapReduce (EMR) 的数据或从另一个迁移数据 数据库到 Amazon DynamoDB,这个 API 提供了一个高效的 替代。

              请注意,这仍然有一些相关限制,最值得注意的是:

              • 单个请求中的最大操作数 — 您最多可以指定总共 25 个放置或删除操作;但是,总请求大小不能超过 1 MB(HTTP 负载)。

              • 不是原子操作 — BatchWriteItem 中指定的各个操作是原子的;但是 BatchWriteItem 整体上是“尽力而为”的操作,而不是原子操作。也就是说,在 BatchWriteItem 请求中,某些操作可能会成功,而其他操作可能会失败。 [...]

              尽管如此,这显然为手头这样的用例提供了潜在的显着收益。

              【讨论】:

              • 我认为在第二步中使用批量删除是有意义的(它被“屏蔽”为batch write operation
              • @ivant - 非常感谢您的提示,当时 BatchWriteItem 的这种“屏蔽”删除功能确实让我无法理解;我已经相应地更新了答案。
              • BatchWriteItem删除的项目需要通过TableWriteItems指定
              • 我意识到这是旧的,并且 OP 没有提到特定的语言 SDK,但在 Python 中有一个高级 batch_writer() 作为 boto3.resource.Table API 的一部分,它将“自动处理批量缓冲和发送项目。此外,批处理写入器还将自动处理任何未处理的项目并根据需要重新发送它们“即它是 BatchWriteItem 的包装器,用于管理烦人的部分。 boto3.amazonaws.com/v1/documentation/api/latest/reference/…
              【解决方案8】:

              这个问题的答案取决于物品的数量及其大小和您的预算。取决于我们有以下3种情况:

              1- 表中项目的数量和大小不是很多。然后正如 Steffen Opel 所说,您可以使用 Query 而不是 Scan 来检索 user_id 的所有项目,然后遍历所有返回的项目并促进 DeleteItemBatchWriteItem。但请记住,您可能会在这里消耗大量吞吐量。例如,考虑需要从 DynamoDB 表中删除 1000 个项目的情况。假设每个项目的大小为 1 KB,从而产生大约 1MB 的数据。此批量删除任务总共需要 2000 个写入容量单位用于查询和删除。要在 10 秒内执行此数据加载(在某些应用程序中甚至被认为不那么快),您需要将表的预置写入吞吐量设置为 200 个写入容量单位。如您所见,如果它用于较少数量的物品或小尺寸物品,则可以使用这种方式。

              2- 我们的表中有很多项目或非常大的项目,我们可以根据时间将它们存储到不同的表中。那么作为 jonathan 说你可以直接删除表。这要好得多,但我认为它与您的情况不匹配。因为无论创建日志的时间是什么时候,您都想删除所有用户数据,所以在这种情况下,您不能删除特定的表。如果您想为每个用户创建一个单独的表,那么我想如果用户数量很高,那么它会很贵,而且对您的情况不实用。

              3- 如果您有大量数据,并且无法将冷热数据划分到不同的表中,并且需要经常进行大规模删除,那么不幸的是,DynamoDB 对您来说根本不是一个好的选择。它可能会变得更加昂贵或非常缓慢(取决于您的预算)。在这些情况下,我建议为您的数据寻找另一个数据库。

              【讨论】:

                【解决方案9】:

                根据 DynamoDB 文档,您可以删除整个表。

                见下文:

                “删除整个表比逐个删除项目效率高得多,这基本上使写入吞吐量翻倍,因为您执行的删除操作与放置操作一样多”

                如果您只想删除数据的一个子集,那么您可以为每个月、每个年或类似的时间创建单独的表格。这样您就可以删除“上个月”并保持其余数据不变。

                这是您使用 AWS 开发工具包在 Java 中删除表的方法:

                DeleteTableRequest deleteTableRequest = new DeleteTableRequest()
                  .withTableName(tableName);
                DeleteTableResult result = client.deleteTable(deleteTableRequest);
                

                【讨论】:

                • 我也喜欢这个答案,但请注意:这可能会在您的系统中创建许多表,我们会按表的配置付费。因此,在未删除此表的情况下,您需要在月底后减少预置(如果您的表是每月)。
                • 同意这个答案,如果您需要从表中删除所有记录,则适用,但这里提问者想要删除用户群条目而不是整个表。
                • 考虑到 DynamoDB 的定价,为每个用户提供一个单独的表会很昂贵。每月一张桌子实际上会让事情变得更糟。这显然是针对一个不同的、非常具体的问题的答案。
                • 如果您使用 CloudFormation 等自动配置将表作为堆栈的一部分进行管理,那么删除表也可能不是一个有吸引力的选择。我不知道有一种简单的方法可以让 CloudFormation 重新创建您手动删除的表。
                • 这种方法需要相当长的时间来删除和重新创建(在需要时)表,使其在整个过程中都不可用。该问题明确指出要删除用户数据,将其拆分为单独的每个用户表是不切实际的。
                猜你喜欢
                • 1970-01-01
                • 1970-01-01
                • 1970-01-01
                • 1970-01-01
                • 2012-10-12
                • 2020-08-05
                • 1970-01-01
                • 1970-01-01
                • 1970-01-01
                相关资源
                最近更新 更多