SpringBoot集成DynamoDB:高效删除指定日期前数据的方案咨询
解决DynamoDB按creation_date批量删除数据的高效方案
问题背景
我在开发Java SpringBoot集成DynamoDB的项目时,需要删除所有creation_date(存储为秒级时间戳)小于指定日期的数据。当前只能用效率极低的DynamoDBScanExpression实现扫描删除,而尝试用Query构建条件时无法添加ComparisonOperator.LE——因为现有全局二级索引(GSI)的哈希键是creation_date,不支持范围查询。允许修改表结构,但必须保留主索引为哈希键my_id+范围键my_type。
当前表结构创建命令
aws dynamodb create-table \ --table-name my-table \ --attribute-definitions \ AttributeName=my_id,AttributeType=S \ AttributeName=my_type,AttributeType=S \ AttributeName=creation_date,AttributeType=N \ --key-schema \ AttributeName=my_id,KeyType=HASH \ AttributeName=my_type,KeyType=RANGE \ --billing-mode PAY_PER_REQUEST \ --endpoint-url $ENDPOINT:$PORT \ --global-secondary-indexes \ "[ \ { \ \"IndexName\": \"CreationDateIndex\", \ \"KeySchema\": [{\"AttributeName\":\"creation_date\",\"KeyType\":\"HASH\"}], \ \"Projection\":{ \ \"ProjectionType\":\"ALL\" \ }, \ \"ProvisionedThroughput\": { \ \"ReadCapacityUnits\": 10, \ \"WriteCapacityUnits\": 5 \ } \ } \ ]"
当前实体类定义
@NotNull @DynamoDBHashKey(attributeName = "my_id") protected UUID myId; @NotNull @DynamoDBRangeKey(attributeName = "my_type") protected String myType; @DynamoDBIndexHashKey(globalSecondaryIndexName = "CreationDateIndex") @DynamoDBAttribute(attributeName = "creation_date") protected long creationDate;
当前低效实现代码
@Override public boolean delete(long creationDateBefore) throws InterruptedException { System.out.println("STARTING DELETING objects"); List<MyEntity> objectsToDelete = findByCreationDateBefore(creationDateBefore); System.out.println("DELETING " + objectsToDelete.size() + " objects"); return batchDelete(objectsToDelete).isEmpty(); } private List<MyEntity> findByCreationDateBefore(long creationDateBefore) { DynamoDBScanExpression scanExpression = new DynamoDBScanExpression(); scanExpression.addFilterCondition( "creation_date", new Condition() .withComparisonOperator(ComparisonOperator.LE) .withAttributeValueList(new AttributeValue().withN(String.valueOf(creationDateBefore))) ); return dbMapper.scan(MyEntity.class, scanExpression); }
可行解决方案
方案1:重构GSI结构实现高效Query查询
当前GSI把creation_date作为哈希键,仅支持等值查询,无法做范围过滤。我们可以修改GSI:用一个固定值的哈希键(比如"ALL"),将creation_date设为GSI的范围键,这样就能通过Query高效筛选符合条件的数据。
步骤1:更新表GSI结构
aws dynamodb update-table \ --table-name my-table \ --attribute-definitions AttributeName=fixed_partition,AttributeType=S \ --global-secondary-index-updates "[{ \"Update\": { \"IndexName\": \"CreationDateIndex\", \"KeySchema\": [{\"AttributeName\":\"fixed_partition\",\"KeyType\":\"HASH\"}, {\"AttributeName\":\"creation_date\",\"KeyType\":\"RANGE\"}], \"Projection\": {\"ProjectionType\":\"ALL\"}, \"ProvisionedThroughput\": {\"ReadCapacityUnits\":10, \"WriteCapacityUnits\":5} } }]"
步骤2:更新实体类
新增固定哈希键字段,调整GSI注解:
@NotNull @DynamoDBHashKey(attributeName = "my_id") protected UUID myId; @NotNull @DynamoDBRangeKey(attributeName = "my_type") protected String myType; @DynamoDBAttribute(attributeName = "creation_date") @DynamoDBIndexRangeKey(globalSecondaryIndexName = "CreationDateIndex") protected long creationDate; // GSI固定哈希键,所有数据统一设为"ALL" @DynamoDBIndexHashKey(globalSecondaryIndexName = "CreationDateIndex") @DynamoDBAttribute(attributeName = "fixed_partition") protected String fixedPartition = "ALL";
步骤3:用Query替代Scan查询
private List<MyEntity> findByCreationDateBefore(long creationDateBefore) { MyEntity hashKeyTemplate = new MyEntity(); hashKeyTemplate.setFixedPartition("ALL"); DynamoDBQueryExpression<MyEntity> queryExpr = new DynamoDBQueryExpression<MyEntity>() .withHashKeyValues(hashKeyTemplate) .withIndexName("CreationDateIndex") .withRangeKeyCondition("creation_date", new Condition() .withComparisonOperator(ComparisonOperator.LE) .withAttributeValueList(new AttributeValue().withN(String.valueOf(creationDateBefore)))) .withConsistentRead(false); // GSI不支持强一致读,必须设为false return dbMapper.query(MyEntity.class, queryExpr); }
方案2:使用PartiQL批量操作(中小数据量适用)
通过PartiQL先查询符合条件的主键,再批量删除,效率比Scan更高,需注意单次批量删除最多25条的限制,要做分页处理。
示例代码
private List<MyEntity> findByCreationDateBeforeWithPartiQL(long creationDateBefore) { String querySql = "SELECT my_id, my_type FROM \"my-table\" WHERE creation_date <= ?"; List<AttributeValue> params = Collections.singletonList(new AttributeValue().withN(String.valueOf(creationDateBefore))); List<MyEntity> result = new ArrayList<>(); PaginatedQueryList<Map<String, AttributeValue>> queryResult = dbMapper.query(Map.class, querySql, params); for (Map<String, AttributeValue> item : queryResult) { MyEntity entity = new MyEntity(); entity.setMyId(UUID.fromString(item.get("my_id").getS())); entity.setMyType(item.get("my_type").getS()); result.add(entity); } return result; }
方案3:异步自动清理(定期清理场景适用)
如果是定期清理旧数据的需求,可采用以下两种方式:
- DynamoDB TTL:将
creation_date设置为数据的过期时间戳(或基于创建时间计算出过期时间),开启TTL功能后,DynamoDB会自动异步删除过期数据,无需手动编写删除逻辑。 - Streams+Lambda:给表开启DynamoDB Streams,用Lambda函数定期通过方案1的GSI查询旧数据,批量删除。
注意:TTL功能是异步执行,数据不会立即被删除,适合对删除时效性要求不高的场景。
总结
优先选择方案1,通过重构GSI将creation_date设为范围键,用Query替代Scan,这是效率最高的方案。如果无法修改表结构,方案2的PartiQL查询比Scan更高效,但需注意分页和批量操作限制。方案3适合定期清理场景,无需手动维护删除逻辑。
内容的提问来源于stack exchange,提问作者Aldo Aiello
相关产品推荐
相关产品推荐

