DynamoDB 以原子方式附加到多个列表

DynamoDB appending to multiple lists atomically

我有一个包含两个列表的记录。 多个服务同时附加到这些列表,每个服务只更新其中一个列表。

这是我创建 UpdateItemSpec

的代码
private UpdateItemSpec updateList1(List<String> values) {
        return new UpdateItemSpec()
                .withPrimaryKey("recordKey", "key")
                .withUpdateExpression("SET list1 = list_append(if_not_exists(list1, :initialVal), :val)")
                .withValueMap(new ValueMap().withList(":val", values)
                        .withList(":initialVal", new ArrayList<>()))
                .withReturnValues(ReturnValue.ALL_NEW);
    }

    private UpdateItemSpec updateList2(List<String> values) {
        return new UpdateItemSpec()
                .withPrimaryKey("recordKey", "key")
                .withUpdateExpression("SET list2 = list_append(if_not_exists(list2, :initialVal), :val)")
                .withValueMap(new ValueMap().withList(":val", values)
                        .withList(":initialVal", new ArrayList<>()))
                .withReturnValues(ReturnValue.ALL_NEW);
    }

考虑到更新应该让我进入列表相等的状态(我不知道需要多少次迭代),我的目标是在每次更新后检查列表是否相等 update(使用从 update 返回的项目)。我是否需要做任何事情来确保 updates 在记录中是原子的,并且我不会错过列表变得相等的迭代?

换句话说,update 是完全锁定记录,还是只锁定当前更新的字段?

单个项目的更新是原子的,因此如果您使用 ReturnValues.ALL_NEW,您将能够看到该项目的完整状态,如果列表相等,您将不会错过它。 (请参阅 update 文档。)

尽管您会看到列表何时相等,但当您能够对列表相等作出反应时,列表可能不再相等。如果你想确保列表保持相等直到你可以做出反应,那么你将需要一个条件表达式来防止进一步更新或分布式锁定解决方案。

编辑 (2018-11-22)

没有文档直接说明项目的更新是以原子方式发生的,但我们可以推断单个项目中的所有属性都是以原子方式更新的。

让我们从以下假设开始:

  1. 如果条件为真,则 conditional write 对项目完全成功。
  2. 如果条件为假,条件写入将被拒绝。
  3. 条件写入可用于 Optimistic Locking with Version Number.
  4. 写入不会自动更新所有属性。

假设我们有一个属性为 hashkeyversiondata 的项目。假设 hashkey=123data = "Foo"version = 1.

还假设有两个请求(请求 A 和请求 B)几乎同时进入,它们都使用乐观锁定,并且都试图为 data 设置一个新值。

这是一个可能的事件顺序

  • 请求 A – 检查条件 version==1
  • 请求 B – 检查条件 version==1
  • 请求 A – data = "Bar"
  • 请求 B – data = "Bag"
  • 请求 A – version=version+1
  • 请求 B – version=version+1

项目的最终状态是

hashkey=123
version=3
data="Bag"

由于乐观锁定,这种状态不应该发生。

If you use this strategy, then your database writes are protected from being overwritten by the writes of others — and vice-versa.

现在,您可能会反对,因为这表明版本增量是通过条件评估自动发生的。但是,您还应该记住,条件表达式可以对任意数量的属性具有任意数量的条件,因此条件表达式中提到的所有属性都必须与条件表达式评估一起自动更新。

但是,这仅说明至少某些属性必须以原子方式更新,而不是所有属性都是。那么,这里有两种可能的实现。 (1) DynamoDB 确定条件表达式中存在哪些属性(从 0 到所有属性)并仅自动更新那些属性。 (2) DynamoDB 以原子方式更新项目的所有属性。但是请考虑这个示例,其中不在 ConditionExpression 中的属性会影响更新的正确性。

假设我们的商品还有 1 个属性:canEdit,最初为真。这次请求 A 设置 canEdit=false,请求 B 设置 values = list_append(values, "Bar").

如果所有属性都没有自动更新,则可能发生以下事件:

  • 请求 A – 检查条件 version==1 AND data=="Foo"
  • 请求 A – version = version+1
  • 请求A – `data = "Foo"
  • 请求 B – 检查条件 canEdit==true
  • 请求 B – version = version+1
  • 请求 B – canEdit = true
  • 请求 A – canEdit = false
  • 请求 B – data = "Bar"

在这两种情况下,最初评估时条件都为真,并且在这两种情况下,请求写入的内容都被另一个请求覆盖。 DynamoDB 不允许这两个条件更新都成功,因此 DynamoDB 必须以原子方式更新项目中的所有属性,因为这是保证 A 或 B 将因 ConditionCheckException 而失败的唯一方法。

如果我的推理是正确的,那么为了使 1-3 为真,4 必须为假,并且单个项目中的所有属性都自动更新。