使用 Amazon DynamoDB 为移动应用程序设计数据库

模块 5:部分规范化

学习如何实现部分规范化

概述

在上一个模块中,您在表中添加了一个反向索引。反向索引为关系型数据添加了额外的查询模式。但是,我们发现还是遇到了问题。虽然我们可以查询指定 User 实体的所有 Friendship 实体,但 Friendship 实体并不包含被关注用户的信息。

在本模块中,您将学习部分规范化。

 时长

20 分钟

部分规范化

在第二个模块中,我们了解了在使用 DynamoDB 建模时,不能伪造关系模式。关系模型的一个核心是数据规范化,规范化可以帮助您避免在多个位置复制数据。在关系型数据库中,规范化是一种不错的模式,但在查询时可能需要通过连接 (join) 来重新组合数据,而数据 join 的消耗比较高。

使用 DynamoDB 时,通常需要对数据进行非规范化处理。非规范化有助于避免数据 join,且能提高查询性能。为此,您可以将属性从一个数据项复制到引用该数据项的另一个数据项,以避免在查询期间需要同时查询这两个数据项。

但是,有时非规范化会使数据模型复杂化。例如,数据模型有一个 Friendship 实体,该实体既引用被关注的用户,也引用关注者用户。在创建 Friendship 实体时,您可以将每个 User 实体的所有属性复制到 Friendship 实体中。然后,当您检索 Friendship 实体时,也将获得关于这两个用户的所有详细信息。

每当用户更改个人资料中的信息时,这可能会造成问题。例如,如果用户更换了头像,则包含该用户的每个 Friendship 实体中的数据都将过时。每当有更新时,您都需要更新包含该用户的每个 Friendship 实体。

在下面的步骤中,您将了解如何使用部分规范化和调用 BatchGetItem API 来处理这种情况。

操作步骤

  • 在此步骤中,您将了解如何查找关注的用户。这些用户是某特定用户在应用程序中关注的用户。您还将了解如何检索被关注用户的所有数据。

    正如本模块简介中所述,您可能希望对 Friendship 和 User 数据使用部分规范化技术。您可以使用 BatchGetItem API 检索 Friendship 实体中用户的信息,而不必将每个用户的全部信息存储在 Friendship 实体中。

    在您下载的代码中,application/ 目录下有一个名为 find_and_enrich_following_for_user.py 文件。该脚本的内容如下所示。

    import boto3
    
    from entities import User
    
    dynamodb = boto3.client('dynamodb')
    
    USERNAME = "haroldwatkins"
    
    
    def find_and_enrich_following_for_user(username):
        friend_value = "#FRIEND#{}".format(username)
        resp = dynamodb.query(
            TableName='quick-photos',
            IndexName='InvertedIndex',
            KeyConditionExpression="SK = :sk",
            ExpressionAttributeValues={":sk": {"S": friend_value}},
            ScanIndexForward=True
        )
    
        keys = [
            {
                "PK": {"S": "USER#{}".format(item["followedUser"]["S"])},
                "SK": {"S": "#METADATA#{}".format(item["followedUser"]["S"])},
            }
            for item in resp["Items"]
        ]
    
        friends = dynamodb.batch_get_item(
            RequestItems={
                "quick-photos": {
                    "Keys": keys
                }
            }
        )
    
        enriched_friends =  [User(item) for item in friends['Responses']['quick-photos']]
    
        return enriched_friends
    
    
    
    follows = find_and_enrich_following_for_user(USERNAME)
    
    print("Users followed by {}:".format(USERNAME))
    for follow in follows:
        print(follow)

    find_and_enrich_following_for_user 函数类似于您在上一个模块中使用的 find_follower_for_user 函数。该函数接受用户名,以帮助您查询该用户关注的用户。该函数先使用反向索引发出 Query 请求,查找指定用户名关注的所有用户。然后,组合一个 BatchGetItem 来获取每个被关注用户的完整 User 实体,并返回这些实体。

    这会向 DynamoDB 发出两个请求,而不是理想下的一个请求。不过,可以实现相当复杂的访问模式,并避免每次更新用户个人资料时都要更新 Friendship 实体。这种部分规范化非常适合满足您的建模需求。

    在终端运行以下命令,执行该脚本。

    python application/find_and_enrich_following_for_user.py
    

    控制台应输出指定用户关注的用户列表。

    Users followed by haroldwatkins:
    User<ppierce -- Ernest Mccarty>
    User<vpadilla -- Jonathan Scott>
    User<david25 -- Abigail Alvarez>
    User<jacksonjason -- John Perry>
    User<chasevang -- Leah Miller>
    User<frankhall -- Stephanie Fisher>
    User<nmitchell -- Amanda Green>
    User<tmartinez -- Kristin Stevens>
    User<natasha87 -- Walter Carlson>
    User<geoffrey32 -- Mary Martin>

    请注意,您现在处理的是 User 实体,而不是 Friendship 实体。User 实体中包含用户的最完整、最新信息。虽然需要发出两个请求才能实现目的,但与完全非规范化以及由此产生的数据完整性问题相比,这仍然是一个更好的选择。

总结

在本模块中,我们了解了如何通过部分规范化和调用 BatchGetItem API 在保持低查询请求的同时,维护对象之间的数据完整性。

在下一个模块中,我们将使用 DynamoDB 事务来实现添加照片互动或关注用户。

添加互动,关注用户