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

模块 4:查看照片互动和用户的关注者

学习使用反向索引的方法,一种 DynamoDB 的常见设计模式

概述

在上一模块中,您设置了 DynamoDB 表中核心实体的访问模式。主键结构帮助我们实现了一些主要访问模式。这包括读取或写入单个实体的所有模式,以及获取多个相关实体的模式,比如属于特定用户的所有照片。

在本模块中,我们将学习使用反向索引的方法,这是 DynamoDB 的常见设计模式。

 时长

40 分钟

反向索引

二级索引是 DynamoDB 中至关重要的数据建模工具。您可以利用二级索引重新构造数据,以适应不同的查询模式。

反向索引是 DynamoDB 中常见的二级索引设计模式。通过反向索引,可以创建与表的主键逆向的二级索引。表的 HASH 键成为索引的 RANGE 键,而表的 RANGE 键成为索引的主键。

在两种情况下,反向索引很有用。首先,反向索引对于查询多对多关系的“另一方”很有用。例如,查询 Friendship 实体。您可以基于主键结构,通过查询表的主键来查询特定用户的所有关注者。添加反向索引后,您将能够通过使用反向索引查找某个用户正在关注的用户(“被关注者”)。

反向索引也可用于查询一对多关系的实体。这种情况下,需要查询的实体本身是一对多关系的主体。例如,您可以通过反向索引查询表中的 Reaction 实体。一张照片上可以有多个互动,每个互动信息都包括互动对象照片、互动用户的用户名和互动类型。但是,由于一个用户可以有多张照片,因此照片的主要标识符位于其 RANGEPHOTO#<USERNAME>#<TIMESTAMP> 中。因此,不能使用主键将互动与照片关联起来。

为了实现“查看照片和互动”访问模式,数据模型将 Reaction 实体的照片标识符放置在 RANGE 键中。现在,您可以在单个请求中,通过反向索引根据照片标识符查询照片及其所有互动,如下面的步骤 2 所示。

操作步骤

  • 要创建二级索引,您需要指定索引的主键,就像之前创建表一样。请注意,全局二级索引的主键不需要具有唯一性。然后,DynamoDB 会根据指定的属性将数据项复制到索引中,您可以像查询表一样查询索引。

    反向索引是 DynamoDB 中的常见模式,在此模式中,可以创建与表主键逆向的二级索引。表的 HASH 键在二级索引中指定为 RANGE 键,而表的 RANGE 键在二级索引中指定为 HASH 键。

    创建二级索引与创建表类似。在您下载的代码中,scripts/ 目录下有一个名为 add_inverted_index.py 的文件。该文件的内容如下所示。 

    import boto3
    
    dynamodb = boto3.client('dynamodb')
    
    try:
        dynamodb.update_table(
            TableName='quick-photos',
            AttributeDefinitions=[
                {
                    "AttributeName": "PK",
                    "AttributeType": "S"
                },
                {
                    "AttributeName": "SK",
                    "AttributeType": "S"
                }
            ],
            GlobalSecondaryIndexUpdates=[
                {
                    "Create": {
                        "IndexName": "InvertedIndex",
                        "KeySchema": [
                            {
                                "AttributeName": "SK",
                                "KeyType": "HASH"
                            },
                            {
                                "AttributeName": "PK",
                                "KeyType": "RANGE"
                            }
                        ],
                        "Projection": {
                            "ProjectionType": "ALL"
                        },
                        "ProvisionedThroughput": {
                            "ReadCapacityUnits": 5,
                            "WriteCapacityUnits": 5
                        }
                    }
                }
            ],
        )
        print("Table updated successfully.")
    except Exception as e:
        print("Could not update table. Error:")
        print(e)

    要在表或二级索引的主键中使用属性,必须在 AttributeDefinitions 中定义这些属性。然后,我们在 GlobalSecondaryIndexUpdates 属性中创建一个新的二级索引。对于该二级索引,我们指定了索引名称、主键模式、预配吞吐量以及需要映射的属性

    请注意,一个反向索引是某个设计模式的名称,而不是 DynamoDB 中的一个正式属性。反向索引的创建与其他二级索引类似。

    运行以下命令,创建反向索引。

    python scripts/add_inverted_index.py
    

    您应该能在控制台中看到以下提示信息:“Table updated successfully.”(表更新成功。)

    在下一步中,我们将展示如何使用反向索引来查看照片。

  • 配置好二级索引后,我们现在可以用它来实现特定的访问模式了。

    使用二级索引时,只能选择两种 API 调用方式:QueryScan。使用 Query 时,必须指定 HASH 键,并返回目标结果。使用 Scan 时,无需指定 HASH 键,该操作将遍历整个表。在 DynamoDB 中,除特定情况外,通常不建议进行 Scan 操作,因为它会访问数据库中的每个数据项。如果表中含有大量数据,进行扫描可能会花费很长时间

    我们可以使用 Query API 根据二级索引查看特定照片的所有互动。正如您在上一个模块中看到的,您可以使用此查询方式通过一个命令检索两种类型的实体。在此查询中,您可以检索照片及其对应的互动。

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

    import boto3
    
    from entities import Photo, Reaction
    
    dynamodb = boto3.client('dynamodb')
    
    USER = "david25"
    TIMESTAMP = '2019-03-02T09:11:30'
    
    
    def fetch_photo_and_reactions(username, timestamp):
        try:
            resp = dynamodb.query(
                TableName='quick-photos',
                IndexName='InvertedIndex',
                KeyConditionExpression="SK = :sk AND PK BETWEEN :reactions AND :user",
                ExpressionAttributeValues={
                    ":sk": { "S": "PHOTO#{}#{}".format(username, timestamp) },
                    ":user": { "S": "USER$" },
                    ":reactions": { "S": "REACTION#" },
                },
                ScanIndexForward=True
            )
        except Exception as e:
            print("Index is still backfilling. Please try again in a moment.")
            return False
    
        items = resp['Items']
        items.reverse()
    
        photo = Photo(items[0])
        photo.reactions = [Reaction(item) for item in items[1:]]
    
        return photo
    
    
    photo = fetch_photo_and_reactions(USER, TIMESTAMP)
    
    if photo:
        print(photo)
        for reaction in photo.reactions:
            print(reaction)

    fetch_photo_and_reactions 函数与应用程序中需要实现的函数类似。该函数接受用户名和时间戳,并根据 InvertedIndex 查看照片和照片的互动。然后,将返回的数据项组合成一个 Photo 实体和多个 Reaction 实体,供您在应用程序中使用。

    python application/fetch_photo_and_reactions.py
    

    您应该会看到输出一张照片以及五个互动。

    Photo<david25 -- 2019-03-02T09:11:30>
    Reaction<ylee -- PHOTO#david25#2019-03-02T09:11:30 -- smiley>
    Reaction<kennedyheather -- PHOTO#david25#2019-03-02T09:11:30 -- smiley>
    Reaction<jenniferharris -- PHOTO#david25#2019-03-02T09:11:30 -- +1>
    Reaction<geoffrey32 -- PHOTO#david25#2019-03-02T09:11:30 -- +1>
    Reaction<chasevang -- PHOTO#david25#2019-03-02T09:11:30 -- +1>

    请注意,二级索引回填需要一些时间才能完成。您可能会收到一条错误消息,表示正在进行回填。如果是这样,请在几分钟后重试。

    在下一步中,我们将了解如何使用反向索引来获取指定用户关注的所有用户列表。

  • 在上一步中,您了解了如何使用反向索引来获取具有一对多关系实体的一对多关系。在这一步中,使用反向索引来获取多对多关系的“另一方”。

    根据表的主键,您可以查询某个用户的所有关注者,但不能查到某个用户关注的所有用户。利用反向索引,该情况就迎刃而解了,您可以查询某个用户关注的所有用户。

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

    import boto3
    
    from entities import Friendship
    
    dynamodb = boto3.client('dynamodb')
    
    USERNAME = "haroldwatkins"
    
    
    def find_following_for_user(username):
        resp = dynamodb.query(
            TableName='quick-photos',
            IndexName='InvertedIndex',
            KeyConditionExpression="SK = :sk",
            ExpressionAttributeValues={
                ":sk": { "S": "#FRIEND#{}".format(username) }
            },
            ScanIndexForward=True
        )
    
        return [Friendship(item) for item in resp['Items']]
    
    
    
    follows = find_following_for_user(USERNAME)
    
    print("Users followed by {}:".format(USERNAME))
    for follow in follows:
        print(follow)

    find_following_for_user 函数与应用程序中需要实现的函数类似。该函数接受用户名,可以帮助您查询该用户关注的用户。然后,该函数查询反向索引,查询指定用户名对应的关注者用户相关的所有 Friendship 实体。

    在终端使用以下命令运行该脚本。

    python application/find_following_for_user.py
    

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

    Users followed by haroldwatkins:
    Friendship<chasevang -- haroldwatkins>
    Friendship<david25 -- haroldwatkins>
    Friendship<frankhall -- haroldwatkins>
    Friendship<geoffrey32 -- haroldwatkins>
    Friendship<jacksonjason -- haroldwatkins>
    Friendship<natasha87 -- haroldwatkins>
    Friendship<nmitchell -- haroldwatkins>
    Friendship<ppierce -- haroldwatkins>
    Friendship<tmartinez -- haroldwatkins>
    Friendship<vpadilla -- haroldwatkins>

    请注意,虽然这将返回用户的所有 Friendship 实体,但 Friendship 实体中的信息比较少。仅包含被关注用户的用户名,而不包含完整的用户个人资料。在下一个模块中,我们将讨论如何使用部分规范化来有效处理类似情况。

总结

在本模块中,我们使用了反向索引模式,向表中添加了一个二级索引,从而实现了两种额外的访问模式:

  • 查看照片和互动(
  • 查看用户的被关注者(

在检索某个用户关注的所有用户时,我们发现了一个问题:每个 Friendship 实体都缺少有关被关注用户的信息。在下一个模块中,我们将了解如何使用部分规范化来帮助处理这种访问模式。

部分规范化