このモジュールでは、DynamoDB で単一の API 呼び出しを使用して複数の項目を取り出す簡単な例を見ていきます。セカンダリインデックスを使用して DynamoDB テーブルで別のクエリパターンを有効にする方法もご説明します。

モジュールの所要時間: 15 分


モジュール 2 では、GetItem API 呼び出しを使用して DynamoDB テーブルから一冊の本を取り出す方法をご紹介しました。このアクセスパターンは便利ですが、アプリケーションでは 1 回の呼び出しで複数の項目を取り出せることも必要です。例えば、ジョン・グリシャム作のすべての本を取り出してユーザーに表示できるようにします。このモジュールのステップ 1 では、Query API を使用して特定の著者のすべての本を取り出します。

1 冊の本を取り出す GetItem API 呼び出しと、1 人の著者のすべての本を取り出す Query API 呼び出しはどちらも、Books テーブルで特定のプライマリキーを使用します。ただし、歴史や伝記などの特定のカテゴリーですべての本を取り出すといった、別のアクセスパターンを有効にすることもできます。カテゴリーはテーブルのプライマリキーではありませんが、セカンダリインデックスを作成して別のアクセスパターンを有効にできます。このモジュールのステップ 2 ではセカンダリインデックスを作成し、ステップ 3 ではセカンダリインデックスのクエリを行います。


  • ステップ 1:クエリを使用して複数の項目を取り出す

    テーブルで複合プライマリキーを使用すると、Query API 呼び出しを使って同じハッシュキーですべての項目を取り出せます。このアプリケーションでは、同じ著者属性ですべての本を取り出せる、ということです。

    AWS Cloud9 ターミナルで次のコマンドを実行します。

    $ python query_items.py

    このコマンドでジョン・グリシャム作のすべての本を取り出す次のスクリプトを実行します。

    import boto3
    from boto3.dynamodb.conditions import Key
    
    # boto3 is the AWS SDK library for Python.
    # The "resources" interface allows for a higher-level abstraction than the low-level client interface.
    # For more details, go to http://boto3.readthedocs.io/en/latest/guide/resources.html
    dynamodb = boto3.resource('dynamodb', region_name='us-east-1')
    table = dynamodb.Table('Books')
    
    # When making a Query API call, you use the KeyConditionExpression parameter to specify the hash key on which you want to query.
    # You’re using the Key object from the Boto 3 library to specify that you want the attribute name ("Author")
    # to equal "John Grisham" by using the ".eq()" method.
    resp = table.query(KeyConditionExpression=Key('Author').eq('John Grisham'))
    
    print("The query returned the following items:")
    for item in resp['Items']:
        print(item)

    スクリプトを実行すると、ジョン・グリシャムの『法律事務所』と『レインメーカー』という 2 冊の本が表示されます。

    $ python query_items.py
    The query returned the following items:
    {'Title': 'The Firm', 'Formats': {'Hardcover': 'Q7QWE3U2', 'Paperback': 'ZVZAYY4F', 'Audiobook': 'DJ9KS9NM'}, 'Author': 'John Grisham', 'Category': 'Suspense'}
    {'Title': 'The Rainmaker', 'Formats': {'Hardcover': 'J4SUKVGU', 'Paperback': 'D7YF4FCX'}, 'Author': 'John Grisham', 'Category': 'Suspense'}

    DynamoDB で単一の呼び出しを使用して複数の項目を取り出すのは一般的なパターンで、Query API 呼び出しを使用すれば簡単にできます。

  • ステップ 2:セカンダリインデックスの作成

    DynamoDB では、アカウントにセカンダリインデックスを作成して、テーブルで別のデータアクセスパターンに対応できるようになります。セカンダリインデックスは、DynamoDB テーブルにクエリの柔軟性を加える強力な方法です。

    DynamoDB には 2 つのセカンダリインデックスがあります。グローバルセカンダリインデックスとローカルセカンダリインデックスです。このセクションでは、カテゴリー属性にグローバルセカンダリインデックスを追加して、特定のカテゴリーのすべての本を取り出せるようにします。

    次のサンプルスクリプトで、既存のテーブルにグローバルセカンダリインデックスを追加します。

    import boto3
    
    # Boto3 is the AWS SDK library for Python.
    # You can use the low-level client to make API calls to DynamoDB.
    client = boto3.client('dynamodb', region_name='us-east-1')
    
    try:
        resp = client.update_table(
            TableName="Books",
            # Any attributes used in your new global secondary index must be declared in AttributeDefinitions
            AttributeDefinitions=[
                {
                    "AttributeName": "Category",
                    "AttributeType": "S"
                },
            ],
            # This is where you add, update, or delete any global secondary indexes on your table.
            GlobalSecondaryIndexUpdates=[
                {
                    "Create": {
                        # You need to name your index and specifically refer to it when using it for queries.
                        "IndexName": "CategoryIndex",
                        # Like the table itself, you need to specify the key schema for an index.
                        # For a global secondary index, you can use a simple or composite key schema.
                        "KeySchema": [
                            {
                                "AttributeName": "Category",
                                "KeyType": "HASH"
                            }
                        ],
                        # You can choose to copy only specific attributes from the original item into the index.
                        # You might want to copy only a few attributes to save space.
                        "Projection": {
                            "ProjectionType": "ALL"
                        },
                        # Global secondary indexes have read and write capacity separate from the underlying table.
                        "ProvisionedThroughput": {
                            "ReadCapacityUnits": 1,
                            "WriteCapacityUnits": 1,
                        }
                    }
                }
            ],
        )
        print("Secondary index added!")
    except Exception as e:
        print("Error updating table:")
        print(e)

    グローバルセカンダリインデックスの作成は、テーブル作成と多くの共通点があります。インデックスの名前、インデックスに含まれる属性、インデックスのキースキーマ、プロビジョンドスループット (アプリケーションがテーブルまたはインデックスで使用できる性能の最大値) を指定します。各インデックスのプロビジョンドスループットはテーブルのプロビジョンドスループットとは異なります。これによりスループットをきめ細かく定義して、アプリケーションのニーズを満たすことができます。

    ターミナルで次のコマンドを実行し、グローバルセカンダリインデックスを追加します。

    $ python add_secondary_index.py

    このスクリプトで、Books テーブルにCategoryIndex というグローバルセカンダリインデックスを追加します。

  • ステップ 3:セカンダリインデックスのクエリ

    CategoryIndex ができましたので、これを使用して特定のカテゴリーのすべての本を取り出せます。セカンダリインデックスを使用してテーブルに対してクエリを実行するのは、Query API 呼び出しを使用するのと似ています。ここでは API 呼び出しにインデックス名を追加します。

    グローバルセカンダリインデックスを既存のテーブルに追加する場合、DynamoDB では同期せずに、そのテーブルの既存の項目をインデックスに対してバックフィルします。すべての項目がバックフィルされたら、インデックスをクエリに使用することができます。バックフィルの時間はテーブルサイズよって異なります。

    query_with_index.py スクリプトを使用して、新しいインデックスに対してクエリを実行できます。ターミナルで次のコマンドを使用してスクリプトを実行します。

    $ python query_with_index.py

    このコマンドでは次のスクリプトを実行して、サスペンスカテゴリーに含まれるストアのすべての本を取り出します。

    import time
    
    import boto3
    from boto3.dynamodb.conditions import Key
    
    # Boto3 is the AWS SDK library for Python.
    # The "resources" interface allows for a higher-level abstraction than the low-level client interface.
    # For more details, go to http://boto3.readthedocs.io/en/latest/guide/resources.html
    dynamodb = boto3.resource('dynamodb', region_name='us-east-1')
    table = dynamodb.Table('Books')
    
    # When adding a global secondary index to an existing table, you cannot query the index until it has been backfilled.
    # This portion of the script waits until the index is in the “ACTIVE” status, indicating it is ready to be queried.
    while True:
        if not table.global_secondary_indexes or table.global_secondary_indexes[0]['IndexStatus'] != 'ACTIVE':
            print('Waiting for index to backfill...')
            time.sleep(5)
            table.reload()
        else:
            break
    
    # When making a Query call, you use the KeyConditionExpression parameter to specify the hash key on which you want to query.
    # If you want to use a specific index, you also need to pass the IndexName in our API call.
    resp = table.query(
        # Add the name of the index you want to use in your query.
        IndexName="CategoryIndex",
        KeyConditionExpression=Key('Category').eq('Suspense'),
    )
    
    print("The query returned the following items:")
    for item in resp['Items']:
        print(item)

    一部のスクリプトはインデックスをクエリの実行に利用できるようになるまで待機することにご注意ください。

    ターミナルに次のような出力が表示されます。

    $ python query_with_index.py
    The query returned the following items:
    {'Title': 'The Firm', 'Formats': {'Hardcover': 'Q7QWE3U2', 'Paperback': 'ZVZAYY4F', 'Audiobook': 'DJ9KS9NM'}, 'Author': 'John Grisham', 'Category': 'Suspense'}
    {'Title': 'The Rainmaker', 'Formats': {'Hardcover': 'J4SUKVGU', 'Paperback': 'D7YF4FCX'}, 'Author': 'John Grisham', 'Category': 'Suspense'}
    {'Title': 'Along Came a Spider', 'Formats': {'Hardcover': 'C9NR6RJ7', 'Paperback': '37JVGDZG', 'Audiobook': '6348WX3U'}, 'Author': 'James Patterson', 'Category': 'Suspense'}

    このクエリは 2 人の異なる著者の 3 冊の本を返します。これはテーブルの主なキースキーマでは難しかったと思われるクエリパターンですが、セカンダリインデックスの機能を利用すれば簡単に実行できます。


    次のモジュールでは、UpdateItem API を使用してテーブルにすでにある項目の属性を更新する方法をご説明します。