从 DynamoDB 中删除大量项目的推荐方法是什么?

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

我有一个由 user _ id 散列和一个时间戳(Unix epoch int)范围键控的日志表。

当服务的用户终止其帐户时,我需要删除表中的所有项,而不管范围值如何。

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

在我看来,我的选择是:

执行一个扫描操作,对每个返回的项目调用 delete,直到没有项目留下

B: 执行一个 BatchGet 操作,再次对每个条目调用 delete,直到完全删除

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

理想情况下,我想要做的是调用 LogTable。DeleteItem (user _ id)-不提供范围,让它为我删除所有内容。

186892 次浏览

理想情况下,我想要做的是调用 LogTable.DeleteItem (user _ id)- 没有提供范围,并有它删除我的一切。

这确实是一个可以理解的要求; 我可以想象,随着时间的推移,AWS 团队可能会添加这样的高级操作(他们有先从有限的功能集开始,然后根据客户的反馈评估扩展的历史) ,但是至少为了避免全面扫描的成本,你应该这样做:

  1. 使用 质疑而不是 扫描来检索 user_id的所有项目——不管使用的组合散列/范围主键是什么,这都可以工作,因为 HashKeyValueRangeKey 条件在这个 API 中是独立的参数,前者只针对 组合主键的哈希组件的属性值。

    • 请注意,您必须像往常一样在这里处理查询 API 分页,请参阅 独家启动密钥参数:

      继续以前查询的项的主键 前面的查询可以提供此值作为 LastevalatedKey,如果 查询操作在完成查询之前被中断; 由于结果集大小或限制参数的原因 可以在新的查询请求中传回 LastValatedKey 以继续 从那时开始的行动。

  2. 循环遍历所有返回的项目,并像往常一样方便 删除项目

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

更新

正如 伊凡所强调的,BatchWriteItem操作 使您能够在一个 API 调用中跨多个表放置 < strong > 或者删除 多个条目:

要上传一个条目,可以使用 PutItem API 并删除一个条目 项时,可以使用 DeleteItem API 或删除大量资料,例如上载大量 数据从亚马逊弹性 MapReduce (EMR)或迁移数据从另一个 数据库到 AmazonDynamoDB,这个 API 提供了一个高效的 另一种选择。

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

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

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

尽管如此,这显然为像手边这样的用例提供了一个潜在的重要收益。

根据 DynamoDB 文档,您可以直接删除完整的表。

见下文:

“删除整个表比逐个删除项要高效得多,这实际上使写入吞吐量增加了一倍,因为删除操作的数量与放入操作的数量相当。”

如果您希望只删除数据的一个子集,那么您可以为每个月、年或类似的数据制作单独的表。这样,您可以删除“上个月”,并保持其余的数据完整。

下面是如何使用 AWS SDK 在 Java 中删除表:

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

这个问题的答案取决于项目的数量、它们的大小和你的预算。这取决于我们有以下三个案例:

表中项目的数量和大小不是很多。然后就像 Steffen Opel 说的那样,你可以使用 Query 而不是 Scan 来检索 user _ id 的所有条目,然后循环遍历所有返回的条目,以方便 DeleteItemBatchWriteItem。但是请记住,您可能会在这里消耗大量吞吐能力。例如,假设您需要从 DynamoDB 表中删除1000个项目。假设每个项目的大小为1KB,结果是大约1MB 的数据。这个批量删除任务总共需要2000个写容量单位来进行查询和删除。要在10秒内执行此数据加载(在某些应用程序中甚至不认为这样快) ,您需要将表的规定写吞吐量设置为200个写容量单位。正如你可以看到它的可行性使用这种方式,如果它的项目数量较少或小型项目。

我们有很多项目或非常大的项目在表中,我们可以存储他们根据时间到不同的表。然后 Jonathan 说你可以删掉这张表。这个好多了,但我觉得和你的案子不符。因为您希望删除所有用户数据,而不管日志的创建时间是什么时候,所以在这种情况下,您不能删除特定的表。如果你想有一个单独的表为每个用户然后我猜如果用户数量很高,那么它是如此昂贵,这是不实际的情况下。

3-如果你有很多数据,你不能把你的热数据和冷数据分成不同的表,你需要经常做大规模删除,那么不幸的是 DynamoDB 根本不是一个好的选择。它可能变得更加昂贵或非常缓慢(取决于你的预算)。在这些情况下,我建议为您的数据找到另一个数据库。

我们没有选择截断发电机表。我们必须删除表并重新创建。DynamoDB 的收费基于 ReadCapacityUnit 和 WriteCapacityUnit。如果我们使用 BatchWriteItem 函数删除所有项目,那么它将更好地使用 writecapacityunits.so 来删除特定的记录或删除表,然后重新开始。

如果你想在一段时间后(例如一个月后)删除项目,只需使用 Time To Live 选项。它将 没有计数写单位。

在您的示例中,我将在日志过期时添加 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

我从一个表 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/CloudForm 创建表,那么删除表然后从 Web 控制台重新创建它可能会导致问题。

考虑过用这个测试来通过变种人测试吗? 比如:

测试输入应该是这样的:

{
"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;
};

因此,仅仅是一个更新,DynamoDB 控制台上就有一个版本,其中包含一个称为 PartiQL 编辑器的新特性。它是一个用于 DynamoDB 操作的类 SQL 编辑器。

删除特定记录

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

缺点: 一次只能删除一个项目

下面是我用来删除 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)


});
});


}