이 모듈에서는 DynamoDB에서 단일 API 호출로 여러 항목을 검색하는 몇 가지 간단한 예제를 살펴봅니다. 또한 보조 인덱스를 사용하여 DynamoDB 테이블에서 추가 쿼리 패턴을 활성화하는 방법도 알아봅니다.

모듈 소요 시간: 15분


모듈 2에서는 GetItem API 호출을 사용하여 DynamoDB 테이블에서 단일 서적을 검색하는 방법을 알아보았습니다. 이 액세스 패턴도 유용하지만, 애플리케이션에서는 한 번의 호출로 여러 항목도 검색할 수 있어야 합니다. 예를 들어, 사용자에게 표시하기 위해 John Grisham이 쓴 모든 서적을 검색하려고 합니다. 이 모듈의 1단계에서는 Query API를 사용하여 특정 작가의 모든 서적을 검색합니다.

단일 서적을 가져오는 GetItem API 호출과 한 작가의 모든 서적을 검색하는 Query API 호출은 모두 Books 테이블에서 지정된 기본 키를 사용합니다. 하지만 역사 또는 전기와 같은 특정 카테고리의 모든 서적을 검색하는 등 추가 액세스 패턴을 사용할 수도 있습니다. Category는 테이블의 기본 키가 아니지만, 추가 액세스 패턴을 위해 보조 인덱스를 생성할 수 있습니다. 이 모듈의 2단계와 3단계에서는 보조 인덱스를 생성하고 보조 인덱스를 쿼리합니다.


  • 1단계. 하나의 쿼리로 여러 항목 검색

    테이블에서 복합 기본 키를 사용하는 경우 Query API 호출을 사용하여 동일한 해시 키의 모든 항목을 검색할 수 있습니다. 애플리케이션 측면에서는 동일한 Author 속성의 모든 서적을 검색하는 것과 같습니다.

    AWS Cloud9 터미널에서 다음 명령을 실행합니다.

    $ python query_items.py

    이 명령은 John Grisham이 쓴 모든 서적을 검색하는 다음 스크립트를 실행합니다.

    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)

    이 스크립트를 실행하면 John Grisham의 두 서적, The FirmThe Rainmaker가 표시됩니다.

    $ 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에는 두 종류의 보조 인덱스(글로벌 보조 인덱스 및 로컬 보조 인덱스)가 있습니다. 이 단원에서는 특정 카테고리의 모든 서적을 검색할 수 있는 Category 속성에 글로벌 보조 인덱스를 추가합니다.

    다음 예제 스크립트에서는 기존 테이블에 글로벌 보조 인덱스를 추가합니다.

    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

    이 스크립트에서는 CategoryIndex라고 하는 글로벌 보조 인덱스를 Books 테이블에 추가합니다.

  • 3단계. 보조 인덱스 쿼리

    CategoryIndex가 있으므로 이를 사용하여 특정 카테고리의 모든 서적을 검색할 수 있습니다. 보조 인덱스를 사용하여 테이블을 쿼리하는 방법은 Query API 호출을 사용하는 방법과 비슷합니다. 이제 API 호출에 인덱스 이름을 추가합니다.

    기존 테이블에 글로벌 보조 인덱스를 추가하는 경우 DynamoDB에서는 테이블의 기존 항목으로 인덱스를 비동기식으로 다시 채웁니다. 모든 항목을 다시 채운 후 인덱스를 쿼리할 수 있습니다. 다시 채우는 데 걸리는 시간은 테이블 크기에 따라 다릅니다.

    query_with_index.py 스크립트를 사용하여 새 인덱스를 쿼리할 수 있습니다. 터미널에서 다음 명령으로 스크립트를 실행합니다.

    $ python query_with_index.py

    이 명령은 다음 스크립트를 실행하여 매장에서 CategorySuspense인 모든 서적을 검색합니다.

    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를 사용하여 테이블에서 기존 항목의 속성을 업데이트하는 방법에 대해 알아봅니다.