DynamoDB에서 글로벌 보조 인덱스 사용 - Amazon DynamoDB

DynamoDB에서 글로벌 보조 인덱스 사용

일부 애플리케이션은 서로 다른 속성을 쿼리 기준으로 사용하여 다양한 유형의 쿼리를 수행해야 할 수 있습니다. 이러한 요구 사항을 지원하기 위해 하나 이상의 글로벌 보조 인덱스를 생성하고 Amazon DynamoDB에서 이 인덱스에 대해 Query 요청을 실행할 수 있습니다.

시나리오: 글로벌 보조 인덱스 사용

모바일 게임 애플리케이션에서 사용자와 점수를 추적하는 GameScores라는 테이블을 예로 들어 설명해 보겠습니다. GameScores의 각 항목은 파티션 키(UserId)와 정렬 키(GameTitle)로 식별됩니다. 다음 그림은 테이블에서 항목을 구성하는 방식을 보여 줍니다. (일부 속성만 표시)

사용자 ID, 타이틀, 점수, 날짜 및 승률의 목록이 포함되어 있는 GameScores 테이블입니다.

이제 각 게임의 최고 점수를 표시하는 순위표 애플리케이션을 작성하려는 경우를 가정하겠습니다. 키 속성(UserIdGameTitle)을 지정한 쿼리는 매우 효율적일 것입니다. 그러나 애플리케이션에서 GameTitle만을 토대로 GameScores에서 데이터를 검색해야 할 경우에는 Scan 작업을 사용해야 할 수 있습니다. 테이블에 항목을 추가할수록 전체 데이터 스캔이 느려지고 효율성이 저하되면서 다음과 같은 질문에 응답하기가 어려워집니다.

  • Meteor Blasters 게임의 최고 기록 점수

  • Galaxy Invaders의 최고 점수 기록 보유자

  • 최고 승률

키가 아닌 속성에 대한 쿼리 속도를 높이기 위해 글로벌 보조 인덱스를 만들 수 있습니다. 글로벌 보조 인덱스는 기본 테이블의 속성 중 일부를 포함하지만 테이블과 다른 기본 키를 기준으로 구성됩니다. 인덱스 키는 테이블의 키 속성을 가져야 할 필요가 없습니다. 테이블과 동일한 키 스키마를 가져야 할 필요도 없습니다.

예를 들어 파티션 키가 GameTitle이고 정렬 키가 TopScoreGameTitleIndex라는 글로벌 보조 인덱스를 생성할 수 있습니다. 기본 테이블의 기본 키 속성이 언제나 인덱스로 프로젝션되므로 UserId 속성이 함께 표시됩니다. 다음 그림은 GameTitleIndex 인덱스를 나타냅니다.

타이틀, 점수 및 사용자 ID의 목록이 포함되어 있는 GameTitleIndex 테이블입니다.

이제 GameTitleIndex를 쿼리하여 Meteor Blasters의 점수를 쉽게 가져올 수 있습니다. 결과는 정렬 키 값 TopScore로 정렬됩니다. ScanIndexForward 파라미터를 false로 설정하는 경우 결과가 내림차순으로 반환되어 가장 높은 점수가 가장 먼저 반환됩니다.

모든 글로벌 보조 인덱스에는 파티션 키가 있어야 하며 선택 사항으로 정렬 키가 있을 수 있습니다. 인덱스 키 스키마는 기본 테이블 스키마와 다를 수 있습니다. 단순 기본 키(파티션 키)를 사용하는 테이블이 있을 때 복합 기본 키(파티션 키 및 정렬 키)를 사용하여 글로벌 보조 인덱스를 만들거나 그 반대도 가능합니다. 인덱스 키 속성은 기본 테이블에서 나온 모든 최상위 수준 String, Number 또는 Binary 속성으로 구성될 수 있습니다. 다른 스칼라 유형, 문서 유형 및 집합 유형은 허용되지 않습니다.

다른 기본 테이블 속성을 인덱스로 프로젝션할 수도 있습니다. 인덱스를 쿼리할 경우 DynamoDB에서 이와 같이 프로젝션된 속성을 효율적으로 가져올 수 있습니다. 그러나 글로벌 보조 인덱스 쿼리는 기본 테이블에서 속성을 가져올 수 없습니다. 예를 들어 이전 다이어그램에 나와 있듯이 GameTitleIndex을 쿼리하는 경우, TopScore 이외의 어떤 키가 아닌 속성에도 액세스할 수 없습니다(키 속성 GameTitleUserId은 자동으로 프로젝션됨에도 불구하고).

DynamoDB 테이블에서 각 키 값은 고유해야 합니다. 하지만 글로벌 보조 인덱스의 키 값은 고유할 필요가 없습니다. 예를 들어 많은 새로운 사용자들이 도전하지만 0점을 넘지 못하는 특히 어려운 Comet Quest라는 게임이 있다고 가정해 보겠습니다. 아래에는 이를 표현할 수 있는 몇 가지 데이터가 나와 있습니다.

UserId GameTitle TopScore
123 Comet Quest 0
201 Comet Quest 0
301 Comet Quest 0

이 데이터가 GameScores 테이블에 추가되면 DynamoDB가 이를 GameTitleIndex에 전파합니다. GameTitle에 Comet Quest를 사용하고 TopScore에 0을 사용하여 인덱스를 쿼리하면 다음 데이터가 반환됩니다.

타이틀, 최고 점수 및 사용자 ID의 목록이 포함되어 있는 테이블입니다.

지정된 키 값을 가진 항목만 응답에 나타납니다. 이러한 데이터 집합 내에서 항목들은 특정한 순서를 따르지 않습니다.

글로벌 보조 인덱스는 해당 키 속성이 실제로 존재하는 데이터 항목만 추적합니다. 예를 들어 GameScores 테이블에 새 항목을 하나 더 추가하되 필요한 기본 키 속성만 제공한 경우를 가정하겠습니다.

UserId GameTitle
400 Comet Quest

TopScore 속성을 지정하지 않았으므로 DynamoDB가 이 항목을 GameTitleIndex에 전파하지 않습니다. 라서, 모든 Comet Quest 항목에 대해 GameScores를 쿼리하면 다음 4개 항목이 반환됩니다.

4개의 타이틀, 최고 점수 및 사용자 ID의 목록이 포함되어 있는 테이블입니다.

그러나 GameTitleIndex로 유사한 쿼리를 실행할 경우 4개가 아닌 3개 항목이 반환됩니다. 그 이유는 존재하지 않는 TopScore를 보유한 항목은 인덱스로 적용되지 않기 때문입니다.

3개의 타이틀, 최고 점수 및 사용자 ID의 목록이 포함되어 있는 테이블입니다.

속성 프로젝션

프로젝션은 테이블에서 보조 인덱스로 복사되는 속성 집합입니다. 테이블의 파티션 키와 정렬 키는 항상 인덱스로 프로젝션되지만, 다른 속성을 프로젝션하여 애플리케이션의 쿼리 요건을 지원하는 것도 가능합니다. 따라서 인덱스에 쿼리를 실행할 때는 마치 속성이 자체 테이블에 저장되어 있는 것처럼 Amazon DynamoDB가 프로젝션의 모든 속성에 액세스할 수 있습니다.

보조 인덱스를 생성할 때 인덱스에 프로젝션될 속성을 지정해야 합니다. DynamoDB는 이를 위해 다음과 같은 세 가지 옵션을 제공합니다.

  • KEYS_ONLY - 인덱스의 각 항목은 테이블 파티션 키 및 정렬 키 값, 그리고 인덱스 키 값으로만 구성됩니다. KEYS_ONLY 옵션은 보조 인덱스의 크기를 최소화합니다.

  • INCLUDEKEYS_ONLY에서 설명한 속성 외에도 지정하는 키가 아닌 다른 속성이 보조 인덱스에 포함됩니다.

  • ALL – 보조 인덱스에 소스 테이블의 모든 속성이 포함됩니다. 모든 테이블 데이터가 인덱스에 복제되므로 ALL 프로젝션은 보조 인덱스의 크기를 최대화합니다.

이전 다이어그램에서 GameTitleIndex에는 프로젝션된 하나의 속성 UserId만 포함되어 있습니다. 애플리케이션이 쿼리의 GameTitleTopScore를 사용하여 각 게임의 최고 득점자의 UserId를 효과적으로 결정할 수 있지만 최고 득점자의 승패 격차의 최대 비율을 효율적으로 확인하지는 못합니다. 확인하려면 기본 테이블에서 추가 쿼리를 실행하여 각 최고 득점자의 승패를 가져와야 합니다. 이 데이터에서 쿼리를 지원하는 더욱 효율적인 방법은 다음 그림과 같은 속성을 기본 테이블에서 글로벌 보조 인덱스로 프로젝션하는 것입니다.

효율적인 쿼리를 지원하기 위해 키가 아닌 속성을 GSI로 프로젝션하는 방법을 설명합니다.

키가 아닌 속성 WinsLosses는 인덱스로 프로젝션되기 때문에 애플리케이션으로 모든 게임 또는 모든 게임과 사용자 ID 조합에 대해 승률을 확인할 수 있습니다.

속성을 글로벌 보조 인덱스로 프로젝션할 경우에는 프로비저닝된 처리량 비용과 스토리지 비용 간 균형을 고려해야 합니다.

  • 대기 시간이 가장 낮은 몇 개의 속성만 액세스해야 할 경우 해당 속성만 글로벌 보조 인덱스로 프로젝션하는 방법을 고려해 볼 수 있습니다. 인덱스가 작을수록 스토리지 비용과 쓰기 비용이 절감됩니다.

  • 애플리케이션이 키가 아닌 일부 속성에 빈번하게 액세스할 경우 해당 속성을 글로벌 보조 인덱스로 프로젝션하는 방법을 고려해야 합니다. 글로벌 보조 인덱스의 추가 스토리지 비용이 빈번한 테이블 스캔 수행으로 발생하는 비용보다 경제적입니다.

  • 키가 아닌 속성 대부분에 자주 액세스해야 하는 경우 해당 속성이나 심지어 전체 기본 테이블을 글로벌 보조 인덱스로 프로젝션할 수 있습니다. 이렇게 하면 유연성이 극대화합니다. 그러나 스토리지 비용이 심지어 2배로 증가하게 됩니다.

  • 애플리케이션이 테이블에 빈번하게 쿼리하지 않지만 테이블 데이터에 대해 많은 쓰기 또는 업데이트 작업을 수행해야 할 경우 KEYS_ONLY를 프로젝션할 수 있습니다. 이 경우 글로벌 보조 인덱스는 크기는 최소화되지만 쿼리 작업에 필요할 경우 계속 사용할 수 있습니다.

글로벌 보조 인덱스에서 데이터 읽기

QueryScan 작업을 사용하여 글로벌 보조 인덱스에서 항목을 가져올 수 있습니다. GetItemBatchGetItem 작업은 글로벌 보조 인덱스에 사용할 수 없습니다.

글로벌 보조 인덱스 쿼리

Query 작업을 사용하여 글로벌 보조 인덱스에서 하나 이상의 항목에 액세스할 수 있습니다. 쿼리에서는 사용하려는 인덱스의 이름과 기본 테이블의 이름, 쿼리 결과에 반환할 속성, 적용할 쿼리 조건을 지정해야 합니다. DynamoDB는 결과를 오름차순 또는 내림차순으로 반환할 수 있습니다.

순위표 애플리케이션에 대해 게임 데이터를 요청하는 Query에서 다음과 같은 데이터가 반환된 경우를 가정해 보세요.

{ "TableName": "GameScores", "IndexName": "GameTitleIndex", "KeyConditionExpression": "GameTitle = :v_title", "ExpressionAttributeValues": { ":v_title": {"S": "Meteor Blasters"} }, "ProjectionExpression": "UserId, TopScore", "ScanIndexForward": false }

이 쿼리에서

  • DynamoDB는 GameTitle 파티션 키로 Meteor Blassts의 인덱스 항목을 찾아 GameTitleIndex에 액세스합니다. 이 키가 있는 모든 인덱스 항목은 빠른 검색을 위해 인접한 상태로 저장됩니다.

  • 이 게임에서 DynamoDB는 인덱스를 사용하여 이 게임의 모든 사용자 ID와 최고 점수에 액세스합니다.

  • 결과가 반환되고, ScanIndexForward 파라미터가 false로 설정되어 있으므로 내림차순으로 정렬됩니다.

글로벌 보조 인덱스 검색

Scan 작업을 사용하여 글로벌 보조 인덱스에서 모든 데이터를 검색할 수 있습니다. 요청에 기본 테이블 이름과 인덱스 이름을 제공해야 합니다. DynamoDB는 Scan을 사용하여 인덱스에 있는 모든 데이터를 읽고 애플리케이션에 반환합니다. 또한 일부 데이터만 반환하고 나머지 데이터를 무시하도록 요청할 수 있습니다. 그러려면 Scan 작업의 FilterExpression 파라미터를 사용합니다. 자세한 내용은 스캔에 대한 필터 표현식 단원을 참조하십시오.

테이블과 글로벌 보조 인덱스 간 데이터 동기화

DynamoDB는 각 글로벌 보조 인덱스를 기본 테이블과 자동으로 동기화합니다. 애플리케이션이 테이블에서 항목을 쓰거나 삭제하면 해당 테이블의 글로벌 보조 인덱스가 최종적으로 일관된 모델을 사용하여 비동기적으로 업데이트됩니다. 애플리케이션이 인덱스에 직접 쓰기를 수행하는 경우는 없습니다. 하지만, DynamoDB가 이러한 인덱스를 유지하는 방식이 어떤 영향을 미치는지 이해하는 것이 중요합니다.

글로벌 보조 인덱스는 기본 테이블에서 읽기/쓰기 용량 모드를 상속합니다. 자세한 내용은 용량 모드 전환 시 고려 사항 단원을 참조하십시오.

글로벌 보조 인덱스를 생성할 때 하나 이상의 인덱스 키 속성과 해당 데이터 형식을 지정합니다. 다시 말해, 기본 테이블에 항목을 쓸 때마다 해당 속성의 데이터 형식이 인덱스 키 스키마의 데이터 형식과 일치해야 합니다. GameTitleIndex의 경우, 인덱스의 GameTitle 파티션 키가 String 데이터 유형으로 정의됩니다. 인덱스의 TopScore 정렬 키는 유형이 Number입니다. GameScores 테이블에 항목을 추가하고 GameTitle 또는 TopScore에 다른 데이터 형식을 지정할 경우 DynamoDB에서 데이터 형식 불일치로 인해 ValidationException을 반환합니다.

테이블에 항목을 추가하거나 삭제하면 해당 테이블의 글로벌 보조 인덱스가 최종 일관성 방식으로 업데이트됩니다. 정상적인 조건에서 해당 테이블 데이터의 변경 사항은 거의 밀리초 수준으로 글로벌 보조 인덱스에 적용됩니다. 하지만, 간혹 장애가 발생할 경우 긴 적용 지연이 발생할 수 있습니다. 따라서, 애플리케이션은 글로벌 보조 인덱스의 쿼리가 최신이 아닌 결과를 반환할 수 있음을 예상하고 그러한 상황을 처리할 수 있어야 합니다.

테이블에 항목을 쓸 경우 모든 글로벌 보조 인덱스 정렬 키의 속성을 지정하지 않아도 됩니다. 예를 들어 GameTitleIndex의 경우, GameScores 테이블에 새 항목을 쓰기 위해 TopScore 속성의 값을 지정할 필요가 없습니다. 이 경우 DynamoDB는 이 특정 항목의 인덱스에 데이터를 쓰지 않습니다.

글로벌 보조 인덱스가 많은 테이블은 인덱스가 적은 테이블보다 쓰기 작업에 더 높은 비용이 발생합니다. 자세한 내용은 글로벌 보조 인덱스에 대한 프로비저닝된 처리량 고려 사항 단원을 참조하십시오.

글로벌 보조 인덱스를 사용하는 테이블 클래스

글로벌 보조 인덱스는 항상 기본 테이블과 동일한 테이블 클래스를 사용합니다. 테이블에 대해 새 글로벌 보조 인덱스가 추가될 때마다 새 인덱스는 기본 테이블과 동일한 테이블 클래스를 사용합니다. 테이블의 테이블 클래스가 업데이트되면 연결된 모든 글로벌 보조 인덱스도 업데이트됩니다.

글로벌 보조 인덱스에 대한 프로비저닝된 처리량 고려 사항

프로비저닝된 모드 테이블에서 글로벌 보조 인덱스를 생성할 때는 해당 인덱스에 예상되는 워크로드에 대한 읽기 및 쓰기 용량 단위를 지정해야 합니다. 글로벌 보조 인덱스의 프로비저닝된 처리량 설정 값은 기본 테이블의 설정 값과 별개입니다. 글로벌 보조 인덱스의 Query 작업은 기본 테이블이 아닌 인덱스에서 읽기 용량 단위를 소비합니다. 테이블에 항목을 추가, 업데이트 또는 삭제하면 해당 테이블의 글로벌 보조 인덱스도 업데이트됩니다. 이러한 인덱스 업데이트에서는 기본 테이블이 아닌 인덱스의 용량 단위가 사용됩니다.

예를 들어 글로벌 보조 인덱스를 Query하고 프로비저닝된 읽기 용량을 초과할 경우 요청이 제한됩니다. 테이블에 많은 쓰기 작업을 수행하지만 해당 테이블의 글로벌 보조 인덱스의 쓰기 용량이 부족할 경우 테이블의 쓰기 작업이 제한됩니다.

중요

잠재적 조절을 피하기 위해서 글로벌 보조 인덱스용 프로비저닝된 쓰기 용량은 새로운 업데이트로 기본 테이블과 글로벌 보조 인덱스가 모두 쓰여지기 때문에 기본 테이블의 쓰기 용량 이상이어야 합니다.

글로벌 보조 인덱스의 프로비저닝된 처리량 설정을 확인하려면 DescribeTable 작업을 사용합니다. 테이블의 모든 글로벌 보조 인덱스에 대한 세부 정보가 반환됩니다.

읽기 용량 단위

글로벌 보조 인덱스는 최종적으로 일관된 읽기를 지원하며, 각각 읽기 용량 단위의 절반을 소비합니다. 즉, 단일 글로벌 보조 인덱스 쿼리는 읽기 용량 단위당 최대 2 × 4KB = 8KB까지 검색할 수 있습니다.

글로벌 보조 인덱스 쿼리의 경우 DynamoDB는 테이블에 대한 쿼리와 같은 방식으로 프로비저닝된 읽기 작업을 계산합니다. 유일한 차이는 기본 테이블 항목의 크기가 아닌 인덱스 항목의 크기를 기준으로 계산이 이루어진다는 점입니다. 읽기 용량 단위의 수는 반환된 모든 항목에서 프로젝션된 모든 속성 크기의 합계입니다. 그런 다음 결과가 다음 4KB 경계로 반올림됩니다. DynamoDB에서 프로비저닝된 처리량을 계산하는 방법에 대한 자세한 내용은 프로비저닝된 용량 모드 단원을 참조하세요.

Query 작업에서 반환된 결과의 최대 크기는 1MB입니다. 여기에는 반환된 모든 항목의 속성 이름 및 값의 크기가 포함됩니다.

예를 들어 각 항목에 2,000바이트의 데이터가 포함된 글로벌 보조 인덱스를 가정해 보겠습니다. 이제 이 인덱스를 Query했을 때 쿼리의 KeyConditionExpression이 8개 항목과 일치한다고 가정해 봅니다. 일치하는 항목의 총 크기는 2,000바이트 × 8개 항목 = 16,000바이트입니다. 그런 다음 결과가 가장 가까운 4KB 경계로 반올림됩니다. 글로벌 보조 인덱스 쿼리는 최종적으로 일관되므로 총 비용은 0.5 × (16KB/4KB), 즉 2 읽기 용량 단위입니다.

쓰기 용량 단위

테이블의 항목을 추가, 업데이트 또는 삭제하고 이로 인해 글로벌 보조 인덱스가 영향을 받을 경우 글로벌 보조 인덱스에서 작업에 프로비저닝된 쓰기 용량 단위를 소비합니다. 쓰기의 총 할당된 처리량 비용은 기본 테이블에 쓰기 작업으로 소비된 쓰기 용량 단위와 글로벌 보조 인덱스를 업데이트하여 소비된 쓰기 용량 단위의 합계로 구성됩니다. 글로벌 보조 인덱스 업데이트가 필요하지 않은 테이블 쓰기의 경우 인덱스에서 쓰기 용량이 소비되지 않습니다.

테이블 쓰기가 성공하기 위해서는 테이블 및 모든 글로벌 보조 인덱스의 프로비저닝 처리량 설정에 쓰기를 수용할 수 있을 만큼 충분한 쓰기 용량이 있어야 합니다. 그렇지 않을 경우 테이블에 대한 쓰기가 제한됩니다.

글로벌 보조 인덱스에 항목을 쓰는 비용은 몇 가지 요인에 따라 달라집니다.

  • 인덱싱된 속성을 정의하는 테이블에 새 항목을 쓰거나 이전에 정의되지 않은 인덱싱된 속성을 정의하도록 기존 항목을 업데이트하는 경우 항목을 인덱스에 넣으려면 1번의 쓰기 작업이 필요합니다.

  • 테이블을 업데이트하여 인덱스 키 속성 값이 A에서 B로 변경될 경우 두 번의 쓰기, 즉, 인덱스에서 이전 항목을 삭제하는 쓰기와 새 항목을 인덱스에 추가하는 쓰기가 필요합니다. 

  • 항목이 인덱스에 있지만 테이블 쓰기로 인해 인덱스 속성이 삭제된 경우 인덱스에서 기존 항목 프로젝션을 삭제하는 한 번의 쓰기가 필요합니다.

  • 항목이 업데이트되기 전후에 인덱스에 항목이 없을 경우 인덱스에 추가 쓰기 비용이 발생하지 않습니다.

  • 테이블을 업데이트하여 인덱스 키 스키마에 프로젝션된 속성 값이 변경되었지만 인덱스 키 속성 값이 변경되지 않은 경우 인덱스에 프로젝션된 속성 값을 업데이트하는 한 번의 쓰기가 필요합니다.

이러한 모든 요인은 인덱스에 있는 각 항목의 크기가 쓰기 용량 단위를 계산하기 위한 1KB 항목 크기보다 작거나 같은 경우를 가정합니다. 이보다 큰 인덱스 항목에서는 추가 쓰기 용량 단위가 필요합니다. 쿼리에서 어떤 속성을 반환해야 하는지 고려하고 해당 속성만 인덱스에 프로젝션함으로써 쓰기 비용을 최소화할 수 있습니다.

글로벌 보조 인덱스에 대한 스토리지 고려 사항

애플리케이션에서 테이블에 항목을 쓰는 경우 DynamoDB는 해당 속성이 표시되어야 하는 모든 글로벌 보조 인덱스에 올바른 속성 하위 집합을 자동으로 복사합니다. AWS 계정에는 기본 테이블의 항목 스토리지 및 해당 테이블에 있는 글로벌 보조 인덱스의 속성 스토리지 비용이 청구됩니다.

인덱스 항목에서 사용하는 공간의 양은 다음의 합계입니다.

  • 기본 테이블 기본 키(파티션 및 정렬 키)의 크기(바이트)

  • 인덱스 키 속성의 크기(바이트)

  • 프로젝션된 속성(있는 경우)의 크기(바이트)

  • 인덱스 항목당 오버헤드의 100바이트

글로벌 보조 인덱스의 스토리지 요구 사항을 추정하려면 인덱스의 평균 항목 크기를 추정한 다음 기본 테이블에서 글로벌 보조 인덱스 키 속성이 있는 항목의 수를 곱합니다.

테이블에 특정 속성이 정의되지 않은 항목이 포함되어 있지만 해당 속성이 인덱스 파티션 키 또는 정렬 키로 정의된 경우 DynamoDB에서 해당 항목의 데이터를 인덱스에 쓰지 않습니다.