首页 > 解决方案 > 如何在并发环境中获取 DynamoDB 中的下一个免费项目

问题描述

我在 DynamoDB 中有一个表和我的用户(部分键 = 键,排序键 = 否):

关键 是活动

用户 1 真

用户 2 假

……

在我的代码中,我需要返回下一个状态为未激活(isActive=false)的用户。如果我需要解决方案,那么最好的方法是什么?

  1. 一张巨大的桌子
  2. 并发环境

我编写了有效的代码,但由于扫描和过滤表达式,我不确定这是一个好的解决方案:

public String getFreeUser() throws IOException {
        Table table = dynamoDB.getTable("usersTableName");

        ScanSpec spec = new ScanSpec()
                .withFilterExpression("isActive = :is_active")
                .withValueMap(new ValueMap().withBoolean(":is_active", false));

        ItemCollection<ScanOutcome> items = table.scan(spec);

        Iterator<Item> iterator = items.iterator();
        Item item = null;

        while (iterator.hasNext()) {
            item = iterator.next();
            try {
                UpdateItemSpec updateItemSpec = new UpdateItemSpec()
                        .withPrimaryKey(new PrimaryKey("key", item.getString("key")))
                        .withUpdateExpression("set #ian=:is_active_new")
                        .withConditionExpression("isActive = :is_active_old")
                        .withNameMap(new NameMap()
                                .with("#ian", "isActive"))
                        .withValueMap(new ValueMap()
                                .withBoolean(":is_active_old", false)
                                .withBoolean(":is_active_new", true))
                        .withReturnValues(ReturnValue.ALL_OLD);

                UpdateItemOutcome outcome = table.updateItem(updateItemSpec);

                return outcome.getItem().getString("key");

            } catch (Exception e) {

            }
        }

        throw new IOException("No active users were found");
    }

标签: javafilteramazon-dynamodbdbscan

解决方案


GSI + 查询 == 好

userID (PK) | isActive | otherAttribute | ...
user1       | true     | foo            | ...
user2       | false    | bar            | ...
user3       | true     | baz            | ...
user4       | false    | 42             | ...
...

GSI:
userID | isActive (GSI-PK)
user1  | true
user2  | false
user3  | true
user4  | false

添加一个散列键为 的 GSI isActive。这将允许您直接查询isActive==所在的项目false

与扫描和过滤相比的好处是读取效率更高。代价是您的 GSI 需要它自己的存储空间,因此如果您的表很大(根据您的假设),那么您可能需要考虑使用稀疏索引。

稀疏索引 GSI + 查询 == 更好

userID (PK) | isNotActive | otherAttribute | ...
user1       |             | foo            | ...
user2       | false       | bar            | ...
user3       |             | baz            | ...
user4       | false       | 42             | ...
...

GSI:
userId | isNotActive (GSI-PK)
user2  | false
user4  | false

考虑将属性替换为isActive并且isNotActive不要将此属性提供给活动用户。也就是说,非活动用户将具有true但活动用户根本没有此属性。然后,您可以使用此isNotActive属性创建 GSI。由于它只包含不活跃的用户,因此存储和查询将更小,更高效。

请注意,当用户变为活动状态时,您需要删除此属性,对于变为非活动状态的活动用户,反之亦然。

属性投影

无论您决定哪种 GSI 最适合您,如果您知道在查询这些非活动用户时需要哪些属性(即使只是“所有这些”),您都可以将这些属性投射到您的 GSI 中,这样您就不需要t 需要按键进行第二次查找。这将增加您的 GSI 的大小,但根据您的表大小、活跃用户与非活跃用户的比率以及您预期的访问模式,这可能是一个值得权衡的选择。

更新

针对第一条评论,要明确 GSI 密钥(现在标记为“GSI-PK”)不是用户 ID。我可以将isActiveoractive列放在 GSI 表的最左侧,但这不是它在 AWS 控制台中的显示方式,因此为了与 AWS 显示它的方式保持一致,我将其保留为原始顺序。

关于并发的第二条评论,你说得对,我没有解决这个问题。我的解决方案将在并发环境中工作,除了一件事 - 你只能进行最终一致的读取,而不是强一致的读取。这意味着最近的新非活动用户(在大多数情况下,我指的是几分之一秒)可能尚未复制到 GSI。同样,最近从非活动更改为活动的用户可能尚未更新 GSI。您需要考虑您的用例是否可以接受最终一致的读取。

另一个考虑因素是,如果这将是一个非常大的表,如果查询结果总计 >1MB,那么您无论如何都会得到分页结果,因为 DynamoDB 强制执行该限制。如果没有全局表锁,由于页面查询之间来自其他客户端的更新,您将获得一些不一致,在这种情况下,最终一致的读取将需要为您工作。


推荐阅读