为什么在执行调用 cognito lambda 触发器 PostConfirmation 然后 PreTokenGeneration 的玩笑测试时会出现随机错误?

Why do I get random errors while executing jest tests that call cognito lambda triggers PostConfirmation and then PreTokenGeneration?

我有一个 PostConfirmation lambda 触发器,它通过 GraphQL(appsync https 调用)将数据添加到 DynamoDB,然后我在 PreTokenGeneration lambda 中查询该信息。 当我通过我的应用程序 UI 手动测试时,一切正常。 但是当通过 Jest 测试执行时,由于在 DynamoDB 中找不到假定的记录信息,超过 50% 的时间我会收到错误。 当我通过 UI 应用程序手动测试时,不会出现该问题。仅在通过 Jest 测试执行时。

我检查了 PostConfirmation DynamoDB 记录添加的 Cloudwatch 时间戳,PreTokenGeneration 并检查了 DynamoDB 中的 createDate。时间戳看起来不错。 例如:

有人可以帮助我或提示我为什么会发生这种情况 and/or 我该如何解决这个问题?提前谢谢你。

考虑到@noel-llevares 的回答,我修改了 VTL 模板以包含 ConsistentRead=true,但问题仍然存在。

这是为保存操作记录的 RequestMapping

{
    "logType": "RequestMapping",
    "path": [
        "createAccountMember"
    ],
    "fieldName": "createAccountMember",
    "resolverArn": "arn:aws:appsync:xx-xxxx-x:111111111:apis/<redacted>/types/Mutation/resolvers/createAccountMember",
    "requestId": "<redacted>",
    "context": {
        "arguments": {
            "input": {
                "id": "<redacted>",
                "userID": "<redacted>",
                "accountID": "<redacted>",
                "membershipStatus": "active",
                "groupsEnrolledIn": [
                    <redacted>
                ],
                "recordOwner": "<redacted>",
                "createdAt": "2020-08-25T05:11:10.917Z",
                "updatedAt": "2020-08-25T05:11:10.917Z",
                "__typename": "AccountMember"
            }
        },
        "stash": {},
        "outErrors": []
    },
    "fieldInError": false,
    "errors": [],
    "parentType": "Mutation",
    "graphQLAPIId": "<redacted>",
    "transformedTemplate": "\n\n\n\n\n\n\n\n{\n  \"version\": \"2018-05-29\",\n  \"operation\": \"PutItem\",\n  \"key\":  {\n  \"id\":   {\"S\":\"<redacted>\"}\n} ,\n  \"attributeValues\": {\"accountID\":{\"S\":\"<redacted>\"},\"createdAt\":{\"S\":\"2020-08-25T05:11:10.917Z\"},\"recordOwner\":{\"S\":\"<redacted>\"},\"__typename\":{\"S\":\"AccountMember\"},\"id\":{\"S\":\"<redacted>\"},\"membershipStatus\":{\"S\":\"active\"},\"userID\":{\"S\":\"<redacted>\"},\"groupsEnrolledIn\":{\"L\":[{\"S\":\"<redacted>\"},{\"S\":\"<redacted>\"},{\"S\":\"<redacted>\"}]},\"updatedAt\":{\"S\":\"2020-08-25T05:11:10.917Z\"}},\n  \"condition\": {\"expression\":\"attribute_not_exists(#id)\",\"expressionNames\":{\"#id\":\"id\"}}\n}\n"
}

为保存操作记录的 ResponseMapping

{
    "logType": "ResponseMapping",
    "path": [
        "createAccountMember"
    ],
    "fieldName": "createAccountMember",
    "resolverArn": "<redacted>",
    "requestId": "<redacted>",
    "context": {
        "arguments": {
            "input": {
                "id": "<redacted>",
                "userID": "<redacted>",
                "accountID": "<redacted>",
                "membershipStatus": "active",
                "groupsEnrolledIn": [
                    "<redacted>",
                    "<redacted>",
                    "<redacted>"
                ],
                "recordOwner": "<redacted>",
                "createdAt": "2020-08-25T05:11:10.917Z",
                "updatedAt": "2020-08-25T05:11:10.917Z",
                "__typename": "AccountMember"
            }
        },
        "result": {
            "accountID": "<redacted>",
            "createdAt": "2020-08-25T05:11:10.917Z",
            "recordOwner": "<redacted>",
            "__typename": "AccountMember",
            "id": "<redacted>",
            "membershipStatus": "active",
            "userID": "<redacted>",
            "groupsEnrolledIn": [
                "<redacted>",
                "<redacted>",
                "<redacted>"
            ],
            "updatedAt": "2020-08-25T05:11:10.917Z"
        },
        "stash": {},
        "outErrors": []
    },
    "fieldInError": false,
    "errors": [],
    "parentType": "Mutation",
    "graphQLAPIId": "<redacted>",
    "transformedTemplate": "{\"accountID\":\"<redacted>\",\"createdAt\":\"2020-08-25T05:11:10.917Z\",\"recordOwner\":\"<redacted>\",\"__typename\":\"AccountMember\",\"id\":\"<redacted>\",\"membershipStatus\":\"active\",\"userID\":\"<redacted>\",\"groupsEnrolledIn\":[\"<redacted>\",\"<redacted>\",\"<redacted>\"],\"updatedAt\":\"2020-08-25T05:11:10.917Z\"}\n"
}

这是为列表操作记录的请求映射。你可以看到 consistentRead=true

{
    "logType": "RequestMapping",
    "path": [
        "listAccountMembers"
    ],
    "fieldName": "listAccountMembers",
    "resolverArn": "<redacted>",
    "requestId": "<redacted>",
    "context": {
        "arguments": {
            "filter": {
                "userID": {
                    "eq": "<redacted>"
                }
            }
        },
        "stash": {},
        "outErrors": []
    },
    "fieldInError": false,
    "errors": [],
    "parentType": "Query",
    "graphQLAPIId": "<redacted>,
    "transformedTemplate": "  \n{\"version\":\"2018-05-29\",\"limit\":100,\"consistentRead\":true,\"filter\":{\"expression\":\"(#userID = :userID_eq)\",\"expressionNames\":{\"#userID\":\"userID\"},\"expressionValues\":{\":userID_eq\":{\"S\":\"<redacted>\"}}},\"operation\":\"Scan\"}"
}

这是记录的 responseMapping。您可以看到结果是一个空数组 (items:[]),即使之前已经添加了记录并且我们为查询指定了 consistentRead=true

{
    "logType": "ResponseMapping",
    "path": [
        "listAccountMembers"
    ],
    "fieldName": "listAccountMembers",
    "resolverArn": "<redacted>",
    "requestId": "<redacted>",
    "context": {
        "arguments": {
            "filter": {
                "userID": {
                    "eq": "<redacted>"
                }
            }
        },
        "result": {
            "items": [],
            "nextToken": "<redacted>",
            "scannedCount": 100
        },
        "stash": {},
        "outErrors": []
    },
    "fieldInError": false,
    "errors": [],
    "parentType": "Query",
    "graphQLAPIId": "<redacted>",
    "transformedTemplate": "\n{\"items\":[],\"nextToken\":\"<redacted>",\"scannedCount\":100,\"startedAt\":null}\n"
}

我还能缺少什么?

UPDATE02

我找到了可能的原因。这是因为我不熟悉 DynamoDB 的工作原理。查询或扫描操作通过键获取结果。在这种情况下,不涉及密钥,因此它会在考虑限制的情况下获取所有记录。在我的例子中它是 100,然后它应用过滤器。因此,如果添加的记录不在前 100 个结果中,除非我通过分页,否则无法找到它(不适合很多特定需求)。

TL;DR:我将查询更改为使用以 userID 作为关键字段的 @key 指令,问题已经解决,因为 que 字段是 GSI 并且我希望使用此类分区检索的记录远少于 100 条限制。我会在撤消之前所做的调整后立即将其添加为答案的一部分。

DynamoDB 默认是最终一致

根据 documentation,

When you read data from a DynamoDB table, the response might not reflect the results of a recently completed write operation. The response might include some stale data. If you repeat your read request after a short time, the response should return the latest data.

如果您需要立即读取刚刚写入的内容,您可以选择使用强一致性读取。这通常通过在 DynamoDB 调用中指定 ConsistentReadtrue 来完成。

我找到了根本原因。查询或扫描操作通过键获取结果。在这种情况下,不涉及密钥,因此它会获取所有考虑到限制的记录,在我的情况下是 100,然后应用过滤器。因此,如果添加的记录不在前 100 个结果中,除非我通过分页(不适合我的特定需要),否则无法找到它。我没有注意到这一点,因为我不熟悉 DynamoDB 的工作原理。但是感谢@noel-llevares 我进行了更多 in-depth 研究以找到解决方案。

解决方案是将查询更改为使用名称为“byUsername”的 @key 指令,该指令存在于 AccountMember 类型中,以 userID 作为关键字段,问题是消失了,因为 que 字段是一个 GSI,我希望使用此类分区检索的记录数远少于 100 条限制。