全球二级索引的 DynamoDB 一致性读取

DynamoDB consistent reads for Global Secondary Index

为什么我无法获得全局二级索引的一致读取?

我有以下设置:

table: tblUsers (id as hash)

全球二级索引:tblUsersEmailIndex(电子邮件作为哈希,id 作为属性)

全局二级索引:tblUsersUsernameIndex(用户名作为哈希值,id 作为属性)

我查询索引以检查给定的电子邮件或用户名是否存在,因此我不会创建重复的用户。

现在,问题是我无法对索引查询进行一致读取。但为什么不呢?这是我实际上需要最新数据的少数情况之一。

根据 AWS 文档:

Queries on global secondary indexes support eventual consistency only.

Changes to the table data are propagated to the global secondary indexes within a fraction of a second, under normal conditions. However, in some unlikely failure scenarios, longer propagation delays might occur. Because of this, your applications need to anticipate and handle situations where a query on a global secondary index returns results that are not up-to-date.

但是我该如何处理这种情况呢?我如何确保数据库中不存在给定的电子邮件或用户名?

你可能已经经历过这个: http://docs.aws.amazon.com/amazondynamodb/latest/developerguide/GSI.html

简短的回答是您不能对全局二级索引做您想做的事情(即它始终是最终一致性)。

这里的一个解决方案是使用一个单独的 table w/您感兴趣的属性作为键,并在那里进行一致的读取。您需要确保在插入新实体时更新它,并且您还必须担心插入成功的边缘情况,但不会在主要 table 中(即您需要确保它们同步)

另一种解决方案是扫描整个 table,但如果 table 很大,那可能就太过分了。

如果有人使用同一封电子邮件创建 2 个帐户,您为什么要关心?您可以只使用用户名作为主哈希键,而不强制电子邮件的唯一性。

根据您的情况并考虑所有备选方案,当您第一次在 GSI 上找不到任何内容时添加自动重试可能是可以接受的,以解决缺乏强一致性读取的问题。我什至没有想到这一点,直到我遇到其他选项的障碍然后意识到这很简单并且不会对我们的特定用例造成任何问题。

{
"TableName": "tokens",

"ProvisionedThroughput": { "ReadCapacityUnits": 5, "WriteCapacityUnits": 5 },

"AttributeDefinitions": [
    { "AttributeName": "data", "AttributeType": "S" },
    { "AttributeName": "type", "AttributeType": "S" },
    { "AttributeName": "token", "AttributeType": "S" }
],

"KeySchema": [
    { "AttributeName": "data", "KeyType": "HASH" },
    { "AttributeName": "type", "KeyType": "RANGE" }
],

"GlobalSecondaryIndexes": [
    {
        "IndexName": "tokens-token",

        "KeySchema": [
            { "AttributeName": "token", "KeyType": "HASH" }
        ],

        "Projection": {
            "ProjectionType": "ALL"
        },

        "ProvisionedThroughput": { "ReadCapacityUnits": 2, "WriteCapacityUnits": 2 }
    }
],

"SSESpecification":  {"Enabled": true }

}

    public async getByToken(token: string): Promise<TokenResponse> {
    let tokenResponse: TokenResponse;
    let tries = 1;
    while (tries <= 2) { // Can't perform strongly consistent read on GSI so we have to do this to insure the token doesn't exist
        let item = await this.getItemByToken(token);
        if (item) return new TokenResponse(item);
        if (tries == 1) await this.sleep(1000);
        tries++;
    }
    return tokenResponse;
}

由于我们不关心发送不存在令牌的人的性能(无论如何都不应该发生),我们在不影响性能的情况下解决了这个问题(除了一次可能的 1 秒延迟创建令牌后)。如果您刚刚创建令牌,则不需要将其解析回您刚刚传入的数据。但如果您碰巧这样做,我们会透明地处理它。

当您尝试使用 putItem 时,您有一个 ConditionExpression 用于检查是否满足放置项目的条件,这意味着您可以检查 emailusername 存在。

ConditionExpression — (String)
A condition that must be satisfied in order for a conditional PutItem operation to succeed.

An expression can contain any of the following:

Functions: attribute_exists | attribute_not_exists | attribute_type | contains | begins_with | size
These function names are case-sensitive.

Comparison operators: = | <> | < | > | <= | >= | BETWEEN | IN
Logical operators: AND | OR | NOT
For more information on condition expressions, see Condition Expressions in the Amazon DynamoDB Developer Guide.

https://docs.aws.amazon.com/AWSJavaScriptSDK/latest/AWS/DynamoDB.html#putItem-property

我 运行 最近遇到了这个问题,想分享一个更新。 2018 年,DynamoDB 添加了 t运行sactions。如果您真的需要保持两个项目(在相同或不同的表中)100% 同步而不用担心最终的一致性,T运行sactWriteItems 和 T运行sactGetItems 就是你所需要的。

最好完全避免 t运行saction,如果可以的话,正如其他人所建议的那样。