자습서: 글로벌 테이블 만들기 - Amazon DynamoDB

자습서: 글로벌 테이블 만들기

이 섹션에서는 선호하는 일관성 모드에 맞게 구성된 DynamoDB 글로벌 테이블을 만들기 위한 단계별 지침을 제공합니다. 애플리케이션의 요구 사항에 따라 다중 리전 최종 일관성(MREC) 또는 다중 리전 강력한 일관성(MRSC) 모드를 선택합니다.

MREC 글로벌 테이블은 AWS 리전 간에 최종 일관성과 함께 더 짧은 쓰기 지연 시간을 제공합니다. MRSC 글로벌 테이블은 MREC보다 약간 긴 쓰기 지연 시간으로 리전 간에 강력하게 일관된 읽기를 제공합니다. 데이터 일관성, 지연 시간 및 가용성에 대한 애플리케이션의 요구 사항에 가장 적합한 일관성 모드를 선택합니다.

MREC용으로 구성된 글로벌 테이블 만들기

이 섹션에서는 다중 리전 최종 일관성(MREC) 모드를 사용하여 글로벌 테이블을 만드는 방법을 보여줍니다. MREC는 글로벌 테이블의 기본 일관성 모드이며 AWS 리전 간에 비동기식 복제를 통해 지연 시간이 짧은 쓰기를 제공합니다. 한 리전의 항목에 대한 변경 사항은 일반적으로 1초 이내에 다른 모든 리전에 복제됩니다. 따라서 MREC는 낮은 쓰기 지연 시간을 우선시하고 리전마다 약간 다른 버전의 데이터를 반환할 수 있는 짧은 기간을 허용할 수 있는 애플리케이션에 이상적입니다.

DynamoDB를 사용할 수 있는 모든 AWS 리전에서 복제본이 있는 MREC 글로벌 테이블을 만들고 언제든지 복제본을 추가하거나 제거할 수 있습니다. 다음 예제에서는 여러 리전에 복제본이 있는 MREC 글로벌 테이블을 만드는 방법을 보여줍니다.

다음 단계에 따라 AWS Management Console을 사용하여 전역 테이블을 생성합니다. 다음 예제에서는 미국 및 유럽의 복제본 테이블로 전역 테이블을 만듭니다.

  1. AWS Management Console에 로그인하고 https://console.aws.amazon.com/dynamodb/에서 DynamoDB 콘솔을 엽니다.

  2. 이 예제에서는 탐색 모음의 리전 선택기에서 미국 동부(오하이오)를 선택합니다.

  3. 콘솔 왼쪽의 탐색 창에서 테이블을 선택합니다.

  4. Create Table(테이블 생성)을 선택합니다.

  5. 테이블 생성 페이지에서 다음을 수행합니다.

    1. 테이블 이름Music을(를) 입력합니다.

    2. 파티션 키(Partition key)Artist를 입력합니다.

    3. 정렬 키에는 SongTitle을 입력합니다.

    4. 기타 기본 설정을 유지하고 테이블 생성을 선택합니다.

      이 최신 테이블은 새로운 전역 테이블에서 첫 번째 복제본 테이블 역할을 합니다. 이는 나중에 추가하는 다른 복제본 테이블의 프로토타입입니다.

  6. 테이블이 활성화되면 다음을 수행합니다.

    1. 테이블 목록에서 Music 테이블을 선택합니다.

    2. 전역 테이블 탭을 선택합니다.

    3. 복제본 생성을 선택합니다.

  7. 사용 가능한 복제 리전 드롭다운 목록에서 미국 서부(오리건) us-west-2를 선택합니다.

    콘솔은 선택한 리전에 동일한 이름의 테이블이 존재하지 않는지 확인합니다. 이름이 동일한 테이블이 있는 경우 해당 리전에서 새 복제본 테이블을 생성하려면 먼저 기존 테이블을 삭제해야 합니다.

  8. 복제본 생성을 선택합니다. 그러면 미국 서부(오레곤) us-west-2 리전에서 테이블 생성 프로세스가 시작됩니다.

    음악 테이블의 전역 테이블 탭 및 다른 복제본 테이블의 전역 테이블 탭은 테이블이 여러 리전에서 복제되었음을 나타냅니다.

  9. 이전 단계를 반복하여 다른 리전을 추가하되 유럽(프랑크푸르트) eu-central-1을 리전으로 선택합니다.

  10. 복제를 테스트하려면 다음을 수행합니다.

    1. 미국 동부(오하이오) 리전에서 AWS Management Console을 계속 사용해야 합니다.

    2. 테이블 항목 탐색을 선택합니다.

    3. 항목 생성을 선택합니다.

    4. Artistitem_1을 입력하고 SongTitle에는 Song Value 1을 입력합니다.

    5. 항목 생성을 선택합니다.

  11. 다른 리전으로 전환하여 복제를 확인합니다.

    1. 오른쪽 상단의 리전 선택기에서 유럽(프랑크푸르트)을 선택합니다.

    2. Music 테이블에 생성한 항목이 포함되어 있는지 확인합니다.

    3. 미국 서부(오리건)에 대해 확인을 반복합니다.

CLI

다음 코드 예제에서는 최종 일관성이 있는 다중 리전 복제(MREC)를 사용하여 DynamoDB 글로벌 테이블을 관리하는 방법을 보여줍니다.

  • 다중 리전 복제(MREC)를 사용하여 테이블을 만듭니다.

  • 복제본 테이블에서 항목을 넣고 가져옵니다.

  • 복제본을 하나씩 제거합니다.

  • 테이블을 삭제하여 정리합니다.

Bash 스크립트와 함께 AWS CLI사용

다중 리전 복제를 사용하여 테이블을 생성합니다.

# Step 1: Create a new table (MusicTable) in US East (Ohio), with DynamoDB Streams enabled (NEW_AND_OLD_IMAGES) aws dynamodb create-table \ --table-name MusicTable \ --attribute-definitions \ AttributeName=Artist,AttributeType=S \ AttributeName=SongTitle,AttributeType=S \ --key-schema \ AttributeName=Artist,KeyType=HASH \ AttributeName=SongTitle,KeyType=RANGE \ --billing-mode PAY_PER_REQUEST \ --stream-specification StreamEnabled=true,StreamViewType=NEW_AND_OLD_IMAGES \ --region us-east-2 # Step 2: Create an identical MusicTable table in US East (N. Virginia) aws dynamodb update-table --table-name MusicTable --cli-input-json \ '{ "ReplicaUpdates": [ { "Create": { "RegionName": "us-east-1" } } ] }' \ --region us-east-2 # Step 3: Create a table in Europe (Ireland) aws dynamodb update-table --table-name MusicTable --cli-input-json \ '{ "ReplicaUpdates": [ { "Create": { "RegionName": "eu-west-1" } } ] }' \ --region us-east-2

다중 리전 테이블을 설명합니다.

# Step 4: View the list of replicas created using describe-table aws dynamodb describe-table \ --table-name MusicTable \ --region us-east-2 \ --query 'Table.{TableName:TableName,TableStatus:TableStatus,MultiRegionConsistency:MultiRegionConsistency,Replicas:Replicas[*].{Region:RegionName,Status:ReplicaStatus}}'

복제본 테이블에 항목을 넣습니다.

# Step 5: To verify that replication is working, add a new item to the Music table in US East (Ohio) aws dynamodb put-item \ --table-name MusicTable \ --item '{"Artist": {"S":"item_1"},"SongTitle": {"S":"Song Value 1"}}' \ --region us-east-2

복제본 테이블에서 항목을 가져옵니다.

# Step 6: Wait for a few seconds, and then check to see whether the item has been # successfully replicated to US East (N. Virginia) and Europe (Ireland) aws dynamodb get-item \ --table-name MusicTable \ --key '{"Artist": {"S":"item_1"},"SongTitle": {"S":"Song Value 1"}}' \ --region us-east-1 aws dynamodb get-item \ --table-name MusicTable \ --key '{"Artist": {"S":"item_1"},"SongTitle": {"S":"Song Value 1"}}' \ --region eu-west-1

복제본을 제거합니다.

# Step 7: Delete the replica table in Europe (Ireland) Region aws dynamodb update-table --table-name MusicTable --cli-input-json \ '{ "ReplicaUpdates": [ { "Delete": { "RegionName": "eu-west-1" } } ] }' \ --region us-east-2 # Delete the replica table in US East (N. Virginia) Region aws dynamodb update-table --table-name MusicTable --cli-input-json \ '{ "ReplicaUpdates": [ { "Delete": { "RegionName": "us-east-1" } } ] }' \ --region us-east-2

테이블을 삭제하여 정리합니다.

# Clean up: Delete the primary table aws dynamodb delete-table --table-name MusicTable --region us-east-2 echo "Global table demonstration complete."
Java

다음 코드 예제에서는 다중 리전 간 복제본을 사용하여 DynamoDB 글로벌 테이블을 만들고 관리하는 방법을 보여줍니다.

  • 글로벌 보조 인덱스와 DynamoDB Streams가 있는 테이블을 만듭니다.

  • 다른 리전에 복제본을 추가하여 글로벌 테이블을 만듭니다.

  • 글로벌 테이블에서 복제본을 제거합니다.

  • 테스트 항목을 추가하여 리전 간 복제를 확인합니다.

  • 글로벌 테이블 구성 및 복제본 상태를 설명합니다.

SDK for Java 2.x

AWS SDK for Java 2.x를 사용하여 글로벌 보조 인덱스와 DynamoDB Streams가 있는 테이블을 만듭니다.

public static CreateTableResponse createTableWithGSI( final DynamoDbClient dynamoDbClient, final String tableName, final String indexName) { if (dynamoDbClient == null) { throw new IllegalArgumentException("DynamoDB client cannot be null"); } if (tableName == null || tableName.trim().isEmpty()) { throw new IllegalArgumentException("Table name cannot be null or empty"); } if (indexName == null || indexName.trim().isEmpty()) { throw new IllegalArgumentException("Index name cannot be null or empty"); } try { LOGGER.info("Creating table: " + tableName + " with GSI: " + indexName); CreateTableRequest createTableRequest = CreateTableRequest.builder() .tableName(tableName) .attributeDefinitions( AttributeDefinition.builder() .attributeName("Artist") .attributeType(ScalarAttributeType.S) .build(), AttributeDefinition.builder() .attributeName("SongTitle") .attributeType(ScalarAttributeType.S) .build()) .keySchema( KeySchemaElement.builder() .attributeName("Artist") .keyType(KeyType.HASH) .build(), KeySchemaElement.builder() .attributeName("SongTitle") .keyType(KeyType.RANGE) .build()) .billingMode(BillingMode.PAY_PER_REQUEST) .globalSecondaryIndexes(GlobalSecondaryIndex.builder() .indexName(indexName) .keySchema(KeySchemaElement.builder() .attributeName("SongTitle") .keyType(KeyType.HASH) .build()) .projection( Projection.builder().projectionType(ProjectionType.ALL).build()) .build()) .streamSpecification(StreamSpecification.builder() .streamEnabled(true) .streamViewType(StreamViewType.NEW_AND_OLD_IMAGES) .build()) .build(); CreateTableResponse response = dynamoDbClient.createTable(createTableRequest); LOGGER.info("Table creation initiated. Status: " + response.tableDescription().tableStatus()); return response; } catch (DynamoDbException e) { LOGGER.severe("Failed to create table: " + tableName + " - " + e.getMessage()); throw e; } }

AWS SDK for Java 2.x를 사용하여 테이블이 활성화될 때까지 기다립니다.

public static void waitForTableActive(final DynamoDbClient dynamoDbClient, final String tableName) { if (dynamoDbClient == null) { throw new IllegalArgumentException("DynamoDB client cannot be null"); } if (tableName == null || tableName.trim().isEmpty()) { throw new IllegalArgumentException("Table name cannot be null or empty"); } try { LOGGER.info("Waiting for table to become active: " + tableName); try (DynamoDbWaiter waiter = DynamoDbWaiter.builder().client(dynamoDbClient).build()) { DescribeTableRequest request = DescribeTableRequest.builder().tableName(tableName).build(); waiter.waitUntilTableExists(request); LOGGER.info("Table is now active: " + tableName); } } catch (DynamoDbException e) { LOGGER.severe("Failed to wait for table to become active: " + tableName + " - " + e.getMessage()); throw e; } }

AWS SDK for Java 2.x를 사용하여 글로벌 테이블을 생성하거나 확장하기 위한 복제본을 추가합니다.

public static UpdateTableResponse addReplica( final DynamoDbClient dynamoDbClient, final String tableName, final Region replicaRegion, final String indexName, final Long readCapacity) { if (dynamoDbClient == null) { throw new IllegalArgumentException("DynamoDB client cannot be null"); } if (tableName == null || tableName.trim().isEmpty()) { throw new IllegalArgumentException("Table name cannot be null or empty"); } if (replicaRegion == null) { throw new IllegalArgumentException("Replica region cannot be null"); } if (indexName == null || indexName.trim().isEmpty()) { throw new IllegalArgumentException("Index name cannot be null or empty"); } if (readCapacity == null || readCapacity <= 0) { throw new IllegalArgumentException("Read capacity must be a positive number"); } try { LOGGER.info("Adding replica in region: " + replicaRegion.id() + " for table: " + tableName); // Create a ReplicationGroupUpdate for adding a replica ReplicationGroupUpdate replicationGroupUpdate = ReplicationGroupUpdate.builder() .create(builder -> builder.regionName(replicaRegion.id()) .globalSecondaryIndexes(ReplicaGlobalSecondaryIndex.builder() .indexName(indexName) .provisionedThroughputOverride(ProvisionedThroughputOverride.builder() .readCapacityUnits(readCapacity) .build()) .build()) .build()) .build(); UpdateTableRequest updateTableRequest = UpdateTableRequest.builder() .tableName(tableName) .replicaUpdates(replicationGroupUpdate) .build(); UpdateTableResponse response = dynamoDbClient.updateTable(updateTableRequest); LOGGER.info("Replica addition initiated in region: " + replicaRegion.id()); return response; } catch (DynamoDbException e) { LOGGER.severe("Failed to add replica in region: " + replicaRegion.id() + " - " + e.getMessage()); throw e; } }

AWS SDK for Java 2.x를 사용하여 글로벌 테이블에서 복제본을 제거합니다.

public static UpdateTableResponse removeReplica( final DynamoDbClient dynamoDbClient, final String tableName, final Region replicaRegion) { if (dynamoDbClient == null) { throw new IllegalArgumentException("DynamoDB client cannot be null"); } if (tableName == null || tableName.trim().isEmpty()) { throw new IllegalArgumentException("Table name cannot be null or empty"); } if (replicaRegion == null) { throw new IllegalArgumentException("Replica region cannot be null"); } try { LOGGER.info("Removing replica in region: " + replicaRegion.id() + " for table: " + tableName); // Create a ReplicationGroupUpdate for removing a replica ReplicationGroupUpdate replicationGroupUpdate = ReplicationGroupUpdate.builder() .delete(builder -> builder.regionName(replicaRegion.id()).build()) .build(); UpdateTableRequest updateTableRequest = UpdateTableRequest.builder() .tableName(tableName) .replicaUpdates(replicationGroupUpdate) .build(); UpdateTableResponse response = dynamoDbClient.updateTable(updateTableRequest); LOGGER.info("Replica removal initiated in region: " + replicaRegion.id()); return response; } catch (DynamoDbException e) { LOGGER.severe("Failed to remove replica in region: " + replicaRegion.id() + " - " + e.getMessage()); throw e; } }

AWS SDK for Java 2.x를 사용하여 테스트 항목을 추가함으로써 복제를 확인합니다.

public static PutItemResponse putTestItem( final DynamoDbClient dynamoDbClient, final String tableName, final String artist, final String songTitle) { if (dynamoDbClient == null) { throw new IllegalArgumentException("DynamoDB client cannot be null"); } if (tableName == null || tableName.trim().isEmpty()) { throw new IllegalArgumentException("Table name cannot be null or empty"); } if (artist == null || artist.trim().isEmpty()) { throw new IllegalArgumentException("Artist cannot be null or empty"); } if (songTitle == null || songTitle.trim().isEmpty()) { throw new IllegalArgumentException("Song title cannot be null or empty"); } try { LOGGER.info("Adding test item to table: " + tableName); Map<String, software.amazon.awssdk.services.dynamodb.model.AttributeValue> item = new HashMap<>(); item.put( "Artist", software.amazon.awssdk.services.dynamodb.model.AttributeValue.builder() .s(artist) .build()); item.put( "SongTitle", software.amazon.awssdk.services.dynamodb.model.AttributeValue.builder() .s(songTitle) .build()); PutItemRequest putItemRequest = PutItemRequest.builder().tableName(tableName).item(item).build(); PutItemResponse response = dynamoDbClient.putItem(putItemRequest); LOGGER.info("Test item added successfully"); return response; } catch (DynamoDbException e) { LOGGER.severe("Failed to add test item to table: " + tableName + " - " + e.getMessage()); throw e; } }

AWS SDK for Java 2.x를 사용하여 글로벌 테이블 구성 및 복제본을 설명합니다.

public static DescribeTableResponse describeTable(final DynamoDbClient dynamoDbClient, final String tableName) { if (dynamoDbClient == null) { throw new IllegalArgumentException("DynamoDB client cannot be null"); } if (tableName == null || tableName.trim().isEmpty()) { throw new IllegalArgumentException("Table name cannot be null or empty"); } try { LOGGER.info("Describing table: " + tableName); DescribeTableRequest request = DescribeTableRequest.builder().tableName(tableName).build(); DescribeTableResponse response = dynamoDbClient.describeTable(request); LOGGER.info("Table status: " + response.table().tableStatus()); if (response.table().replicas() != null && !response.table().replicas().isEmpty()) { LOGGER.info("Number of replicas: " + response.table().replicas().size()); response.table() .replicas() .forEach(replica -> LOGGER.info( "Replica region: " + replica.regionName() + ", Status: " + replica.replicaStatus())); } return response; } catch (ResourceNotFoundException e) { LOGGER.severe("Table not found: " + tableName + " - " + e.getMessage()); throw e; } catch (DynamoDbException e) { LOGGER.severe("Failed to describe table: " + tableName + " - " + e.getMessage()); throw e; } }

AWS SDK for Java 2.x를 사용한 글로벌 테이블 작업의 전체 예제입니다.

public static void exampleUsage(final Region sourceRegion, final Region replicaRegion) { String tableName = "Music"; String indexName = "SongTitleIndex"; Long readCapacity = 15L; // Create DynamoDB client for the source region try (DynamoDbClient dynamoDbClient = DynamoDbClient.builder().region(sourceRegion).build()) { try { // Step 1: Create the initial table with GSI and streams LOGGER.info("Step 1: Creating table in source region: " + sourceRegion.id()); createTableWithGSI(dynamoDbClient, tableName, indexName); // Step 2: Wait for table to become active LOGGER.info("Step 2: Waiting for table to become active"); waitForTableActive(dynamoDbClient, tableName); // Step 3: Add replica in destination region LOGGER.info("Step 3: Adding replica in region: " + replicaRegion.id()); addReplica(dynamoDbClient, tableName, replicaRegion, indexName, readCapacity); // Step 4: Wait a moment for replica creation to start Thread.sleep(5000); // Step 5: Describe table to view replica information LOGGER.info("Step 5: Describing table to view replicas"); describeTable(dynamoDbClient, tableName); // Step 6: Add a test item to verify replication LOGGER.info("Step 6: Adding test item to verify replication"); putTestItem(dynamoDbClient, tableName, "TestArtist", "TestSong"); LOGGER.info("Global table setup completed successfully!"); LOGGER.info("You can verify replication by checking the item in region: " + replicaRegion.id()); // Step 7: Remove replica and clean up table LOGGER.info("Step 7: Removing replica from region: " + replicaRegion.id()); removeReplica(dynamoDbClient, tableName, replicaRegion); DeleteTableResponse deleteTableResponse = dynamoDbClient.deleteTable( DeleteTableRequest.builder().tableName(tableName).build()); LOGGER.info("MREC global table demonstration completed successfully!"); } catch (InterruptedException e) { Thread.currentThread().interrupt(); throw new RuntimeException("Thread was interrupted", e); } catch (DynamoDbException e) { LOGGER.severe("DynamoDB operation failed: " + e.getMessage()); throw e; } } }

MRSC용으로 구성된 글로벌 테이블 만들기

이 섹션에서는 다중 리전 강력한 일관성(MRSC) 글로벌 테이블을 만드는 방법을 보여줍니다. MRSC 글로벌 테이블은 리전 간에 항목 변경 사항을 동기식으로 복제하므로 모든 복제본에서 강력하게 일관된 읽기 작업을 통해 항상 항목의 최신 버전을 반환합니다. 단일 리전 테이블을 MRSC 글로벌 테이블로 변환할 때는 테이블이 비어 있어야 합니다. 단일 리전 테이블을 기존 항목이 있는 MRSC 글로벌 테이블로 변환하는 것은 지원되지 않습니다. 변환 프로세스 중에 테이블에 데이터가 작성되는 일이 없도록 합니다.

복제본 세 개 또는 복제본 두 개와 감시자 하나로 MRSC 글로벌 테이블을 구성할 수 있습니다. MRSC 글로벌 테이블을 만들 때 복제본과 선택적 감시자가 배포되는 리전을 선택합니다. 다음 예제에서는 미국 동부(버지니아 북부) 및 미국 동부(오하이오) 리전에 복제본이 있고 미국 서부(오리건) 리전에 감시자가 있는 MRSC 글로벌 테이블을 만듭니다.

다음 단계에 따라 AWS Management Console을 사용하여 MRSC 글로벌 테이블을 만듭니다.

  1. AWS Management Console에 로그인하고 https://console.aws.amazon.com/dynamodb/에서 DynamoDB 콘솔을 엽니다.

  2. 탐색 모음의 리전 선택기에서 MRSC를 갖춘 글로벌 테이블이 지원되는 리전(예: us-east-2)을 선택합니다.

  3. 탐색 창에서 테이블을 선택합니다.

  4. 테이블 생성을 선택합니다.

  5. 테이블 생성 페이지에서 다음을 수행합니다.

    1. 테이블 이름Music을(를) 입력합니다.

    2. 파티션 키Artist를 입력하고 기본 문자열 유형을 유지합니다.

    3. 정렬 키SongTitle을 입력하고 기본 문자열 유형을 유지합니다.

    4. 기타 기본 설정을 유지하고 테이블 생성을 선택합니다.

      이 최신 테이블은 새로운 전역 테이블에서 첫 번째 복제본 테이블 역할을 합니다. 이는 나중에 추가하는 다른 복제본 테이블의 프로토타입입니다.

  6. 테이블이 활성화될 때까지 기다린 다음 테이블 목록에서 선택합니다.

  7. 글로벌 테이블 탭을 선택한 다음 복제본 생성을 선택합니다.

  8. 복제본 생성 페이지에서 다음을 수행합니다.

    1. 다중 리전 일관성에서 강력한 일관성을 선택합니다.

    2. 복제 리전 1에서 US East (N. Virginia) us-east-1을 선택합니다.

    3. 복제 리전 2에서 US West (Oregon) us-west-2를 선택합니다.

    4. 미국 서부(오리건) 리전에서 감시자로 구성을 선택합니다.

    5. 복제본 생성을 선택합니다.

  9. 복제본 및 감시자 만들기 프로세스가 완료될 때까지 기다립니다. 테이블을 사용할 준비가 되면 복제본 상태가 활성으로 표시됩니다.

시작하기 전에 IAM 위탁자에게 감시자 리전이 있는 MRSC 글로벌 테이블을 만드는 데 필요한 권한이 있는지 확인합니다. IAM 위탁자는 CreateGlobalTableWitness, CreateTableCreateTableReplica를 간접적으로 호출할 수 있는 권한이 있어야 합니다.

다음은 미국 동부(버지니아 북부)의 복제본과 미국 서부(오리건)의 감시자 리전을 사용하여 DynamoDB 테이블(MusicMRSC)을 성공적으로 만들기 위한 샘플 IAM 정책입니다.

JSON
{ "Version": "2012-10-17", "Statement": [ { "Effect": "Allow", "Action": [ "dynamodb:UpdateTable", "dynamodb:CreateTable", "dynamodb:CreateTableReplica", "dynamodb:CreateGlobalTableWitness" ], "Resource": [ "arn:aws:dynamodb:us-west-2:123456789123:table/MusicMRSC", "arn:aws:dynamodb:us-east-1:123456789123:table/MusicMRSC" ] } ] }

다음 코드 예제에서는 다중 리전 강력한 일관성(MRSC)을 갖춘 DynamoDB 글로벌 테이블을 만들고 관리하는 방법을 보여줍니다.

  • 다중 리전 강력한 일관성이 있는 테이블을 만듭니다.

  • MRSC 구성 및 복제본 상태를 확인합니다.

  • 즉각적인 읽기를 통해 리전 간 강력한 일관성을 테스트합니다.

  • MRSC 보장을 사용하여 조건부 쓰기를 수행합니다.

  • MRSC 글로벌 테이블 리소스를 정리합니다.

Bash
Bash 스크립트와 함께 AWS CLI사용

다중 리전 강력한 일관성이 있는 테이블을 만듭니다.

# Step 1: Create a new table in us-east-2 (primary region for MRSC) # Note: Table must be empty when enabling MRSC aws dynamodb create-table \ --table-name MusicTable \ --attribute-definitions \ AttributeName=Artist,AttributeType=S \ AttributeName=SongTitle,AttributeType=S \ --key-schema \ AttributeName=Artist,KeyType=HASH \ AttributeName=SongTitle,KeyType=RANGE \ --billing-mode PAY_PER_REQUEST \ --region us-east-2 # Wait for table to become active aws dynamodb wait table-exists --table-name MusicTable --region us-east-2 # Step 2: Add replica and witness with Multi-Region Strong Consistency # MRSC requires exactly three replicas in supported regions aws dynamodb update-table \ --table-name MusicTable \ --replica-updates '[{"Create": {"RegionName": "us-east-1"}}]' \ --global-table-witness-updates '[{"Create": {"RegionName": "us-west-2"}}]' \ --multi-region-consistency STRONG \ --region us-east-2

MRSC 구성 및 복제본 상태를 확인합니다.

# Verify the global table configuration and MRSC setting aws dynamodb describe-table \ --table-name MusicTable \ --region us-east-2 \ --query 'Table.{TableName:TableName,TableStatus:TableStatus,MultiRegionConsistency:MultiRegionConsistency,Replicas:Replicas[*],GlobalTableWitnesses:GlobalTableWitnesses[*].{Region:RegionName,Status:ReplicaStatus}}'

리전 간 즉각적인 읽기를 통해 강력한 일관성을 테스트합니다.

# Write an item to the primary region aws dynamodb put-item \ --table-name MusicTable \ --item '{"Artist": {"S":"The Beatles"},"SongTitle": {"S":"Hey Jude"},"Album": {"S":"The Beatles 1967-1970"},"Year": {"N":"1968"}}' \ --region us-east-2 # Read the item from replica region to verify strong consistency (cannot read or write to witness) # No wait time needed - MRSC provides immediate consistency echo "Reading from us-east-1 (immediate consistency):" aws dynamodb get-item \ --table-name MusicTable \ --key '{"Artist": {"S":"The Beatles"},"SongTitle": {"S":"Hey Jude"}}' \ --consistent-read \ --region us-east-1

MRSC 보장을 사용하여 조건부 쓰기를 수행합니다.

# Perform a conditional update from a different region # This demonstrates that conditions work consistently across all regions aws dynamodb update-item \ --table-name MusicTable \ --key '{"Artist": {"S":"The Beatles"},"SongTitle": {"S":"Hey Jude"}}' \ --update-expression "SET #rating = :rating" \ --condition-expression "attribute_exists(Artist)" \ --expression-attribute-names '{"#rating": "Rating"}' \ --expression-attribute-values '{":rating": {"N":"5"}}' \ --region us-east-1

MRSC 글로벌 테이블 리소스를 정리합니다.

# Remove replica tables (must be done before deleting the primary table) aws dynamodb update-table \ --table-name MusicTable \ --replica-updates '[{"Delete": {"RegionName": "us-east-1"}}]' \ --global-table-witness-updates '[{"Delete": {"RegionName": "us-west-2"}}]' \ --region us-east-2 # Wait for replicas to be deleted echo "Waiting for replicas to be deleted..." sleep 30 # Delete the primary table aws dynamodb delete-table \ --table-name MusicTable \ --region us-east-2
Java
SDK for Java 2.x

AWS SDK for Java 2.x를 사용하여 MRSC 변환이 준비된 리전 테이블을 만듭니다.

public static CreateTableResponse createRegionalTable(final DynamoDbClient dynamoDbClient, final String tableName) { if (dynamoDbClient == null) { throw new IllegalArgumentException("DynamoDB client cannot be null"); } if (tableName == null || tableName.trim().isEmpty()) { throw new IllegalArgumentException("Table name cannot be null or empty"); } try { LOGGER.info("Creating regional table: " + tableName + " (must be empty for MRSC)"); CreateTableRequest createTableRequest = CreateTableRequest.builder() .tableName(tableName) .attributeDefinitions( AttributeDefinition.builder() .attributeName("Artist") .attributeType(ScalarAttributeType.S) .build(), AttributeDefinition.builder() .attributeName("SongTitle") .attributeType(ScalarAttributeType.S) .build()) .keySchema( KeySchemaElement.builder() .attributeName("Artist") .keyType(KeyType.HASH) .build(), KeySchemaElement.builder() .attributeName("SongTitle") .keyType(KeyType.RANGE) .build()) .billingMode(BillingMode.PAY_PER_REQUEST) .build(); CreateTableResponse response = dynamoDbClient.createTable(createTableRequest); LOGGER.info("Regional table creation initiated. Status: " + response.tableDescription().tableStatus()); return response; } catch (DynamoDbException e) { LOGGER.severe("Failed to create regional table: " + tableName + " - " + e.getMessage()); throw DynamoDbException.builder() .message("Failed to create regional table: " + tableName) .cause(e) .build(); } }

AWS SDK for Java 2.x를 사용하여 리전 테이블을 복제본 및 감시자를 갖춘 MRSC로 변환합니다.

public static UpdateTableResponse convertToMRSCWithWitness( final DynamoDbClient dynamoDbClient, final String tableName, final Region replicaRegion, final Region witnessRegion) { if (dynamoDbClient == null) { throw new IllegalArgumentException("DynamoDB client cannot be null"); } if (tableName == null || tableName.trim().isEmpty()) { throw new IllegalArgumentException("Table name cannot be null or empty"); } if (replicaRegion == null) { throw new IllegalArgumentException("Replica region cannot be null"); } if (witnessRegion == null) { throw new IllegalArgumentException("Witness region cannot be null"); } try { LOGGER.info("Converting table to MRSC with replica in " + replicaRegion.id() + " and witness in " + witnessRegion.id()); // Create replica update using ReplicationGroupUpdate ReplicationGroupUpdate replicaUpdate = ReplicationGroupUpdate.builder() .create(CreateReplicationGroupMemberAction.builder() .regionName(replicaRegion.id()) .build()) .build(); // Create witness update GlobalTableWitnessGroupUpdate witnessUpdate = GlobalTableWitnessGroupUpdate.builder() .create(CreateGlobalTableWitnessGroupMemberAction.builder() .regionName(witnessRegion.id()) .build()) .build(); UpdateTableRequest updateTableRequest = UpdateTableRequest.builder() .tableName(tableName) .replicaUpdates(List.of(replicaUpdate)) .globalTableWitnessUpdates(List.of(witnessUpdate)) .multiRegionConsistency(MultiRegionConsistency.STRONG) .build(); UpdateTableResponse response = dynamoDbClient.updateTable(updateTableRequest); LOGGER.info("MRSC conversion initiated. Status: " + response.tableDescription().tableStatus()); LOGGER.info("UpdateTableResponse full object: " + response); return response; } catch (DynamoDbException e) { LOGGER.severe("Failed to convert table to MRSC: " + tableName + " - " + e.getMessage()); throw DynamoDbException.builder() .message("Failed to convert table to MRSC: " + tableName) .cause(e) .build(); } }

AWS SDK for Java 2.x를 사용하여 MRSC 글로벌 테이블 구성을 설명합니다.

public static DescribeTableResponse describeMRSCTable(final DynamoDbClient dynamoDbClient, final String tableName) { if (dynamoDbClient == null) { throw new IllegalArgumentException("DynamoDB client cannot be null"); } if (tableName == null || tableName.trim().isEmpty()) { throw new IllegalArgumentException("Table name cannot be null or empty"); } try { LOGGER.info("Describing MRSC global table: " + tableName); DescribeTableRequest request = DescribeTableRequest.builder().tableName(tableName).build(); DescribeTableResponse response = dynamoDbClient.describeTable(request); LOGGER.info("Table status: " + response.table().tableStatus()); LOGGER.info("Multi-region consistency: " + response.table().multiRegionConsistency()); if (response.table().replicas() != null && !response.table().replicas().isEmpty()) { LOGGER.info("Number of replicas: " + response.table().replicas().size()); response.table() .replicas() .forEach(replica -> LOGGER.info( "Replica region: " + replica.regionName() + ", Status: " + replica.replicaStatus())); } if (response.table().globalTableWitnesses() != null && !response.table().globalTableWitnesses().isEmpty()) { LOGGER.info("Number of witnesses: " + response.table().globalTableWitnesses().size()); response.table() .globalTableWitnesses() .forEach(witness -> LOGGER.info( "Witness region: " + witness.regionName() + ", Status: " + witness.witnessStatus())); } return response; } catch (ResourceNotFoundException e) { LOGGER.severe("Table not found: " + tableName + " - " + e.getMessage()); throw DynamoDbException.builder() .message("Table not found: " + tableName) .cause(e) .build(); } catch (DynamoDbException e) { LOGGER.severe("Failed to describe table: " + tableName + " - " + e.getMessage()); throw DynamoDbException.builder() .message("Failed to describe table: " + tableName) .cause(e) .build(); } }

AWS SDK for Java 2.x를 사용하여 MRSC 강력한 일관성을 확인하기 위한 테스트 항목을 추가합니다.

public static PutItemResponse putTestItem( final DynamoDbClient dynamoDbClient, final String tableName, final String artist, final String songTitle, final String album, final String year) { if (dynamoDbClient == null) { throw new IllegalArgumentException("DynamoDB client cannot be null"); } if (tableName == null || tableName.trim().isEmpty()) { throw new IllegalArgumentException("Table name cannot be null or empty"); } if (artist == null || artist.trim().isEmpty()) { throw new IllegalArgumentException("Artist cannot be null or empty"); } if (songTitle == null || songTitle.trim().isEmpty()) { throw new IllegalArgumentException("Song title cannot be null or empty"); } try { LOGGER.info("Adding test item to MRSC global table: " + tableName); Map<String, AttributeValue> item = new HashMap<>(); item.put("Artist", AttributeValue.builder().s(artist).build()); item.put("SongTitle", AttributeValue.builder().s(songTitle).build()); if (album != null && !album.trim().isEmpty()) { item.put("Album", AttributeValue.builder().s(album).build()); } if (year != null && !year.trim().isEmpty()) { item.put("Year", AttributeValue.builder().n(year).build()); } PutItemRequest putItemRequest = PutItemRequest.builder().tableName(tableName).item(item).build(); PutItemResponse response = dynamoDbClient.putItem(putItemRequest); LOGGER.info("Test item added successfully with strong consistency"); return response; } catch (DynamoDbException e) { LOGGER.severe("Failed to add test item to table: " + tableName + " - " + e.getMessage()); throw DynamoDbException.builder() .message("Failed to add test item to table: " + tableName) .cause(e) .build(); } }

AWS SDK for Java 2.x를 사용하여 MRSC 복제본에서 일관된 읽기로 항목을 읽습니다.

public static GetItemResponse getItemWithConsistentRead( final DynamoDbClient dynamoDbClient, final String tableName, final String artist, final String songTitle) { if (dynamoDbClient == null) { throw new IllegalArgumentException("DynamoDB client cannot be null"); } if (tableName == null || tableName.trim().isEmpty()) { throw new IllegalArgumentException("Table name cannot be null or empty"); } if (artist == null || artist.trim().isEmpty()) { throw new IllegalArgumentException("Artist cannot be null or empty"); } if (songTitle == null || songTitle.trim().isEmpty()) { throw new IllegalArgumentException("Song title cannot be null or empty"); } try { LOGGER.info("Reading item from MRSC global table with consistent read: " + tableName); Map<String, AttributeValue> key = new HashMap<>(); key.put("Artist", AttributeValue.builder().s(artist).build()); key.put("SongTitle", AttributeValue.builder().s(songTitle).build()); GetItemRequest getItemRequest = GetItemRequest.builder() .tableName(tableName) .key(key) .consistentRead(true) .build(); GetItemResponse response = dynamoDbClient.getItem(getItemRequest); if (response.hasItem()) { LOGGER.info("Item found with strong consistency - no wait time needed"); } else { LOGGER.info("Item not found"); } return response; } catch (DynamoDbException e) { LOGGER.severe("Failed to read item from table: " + tableName + " - " + e.getMessage()); throw DynamoDbException.builder() .message("Failed to read item from table: " + tableName) .cause(e) .build(); } }

AWS SDK for Java 2.x를 사용하여 MRSC 보장이 있는 조건부 업데이트를 수행합니다.

public static UpdateItemResponse performConditionalUpdate( final DynamoDbClient dynamoDbClient, final String tableName, final String artist, final String songTitle, final String rating) { if (dynamoDbClient == null) { throw new IllegalArgumentException("DynamoDB client cannot be null"); } if (tableName == null || tableName.trim().isEmpty()) { throw new IllegalArgumentException("Table name cannot be null or empty"); } if (artist == null || artist.trim().isEmpty()) { throw new IllegalArgumentException("Artist cannot be null or empty"); } if (songTitle == null || songTitle.trim().isEmpty()) { throw new IllegalArgumentException("Song title cannot be null or empty"); } if (rating == null || rating.trim().isEmpty()) { throw new IllegalArgumentException("Rating cannot be null or empty"); } try { LOGGER.info("Performing conditional update on MRSC global table: " + tableName); Map<String, AttributeValue> key = new HashMap<>(); key.put("Artist", AttributeValue.builder().s(artist).build()); key.put("SongTitle", AttributeValue.builder().s(songTitle).build()); Map<String, String> expressionAttributeNames = new HashMap<>(); expressionAttributeNames.put("#rating", "Rating"); Map<String, AttributeValue> expressionAttributeValues = new HashMap<>(); expressionAttributeValues.put( ":rating", AttributeValue.builder().n(rating).build()); UpdateItemRequest updateItemRequest = UpdateItemRequest.builder() .tableName(tableName) .key(key) .updateExpression("SET #rating = :rating") .conditionExpression("attribute_exists(Artist)") .expressionAttributeNames(expressionAttributeNames) .expressionAttributeValues(expressionAttributeValues) .build(); UpdateItemResponse response = dynamoDbClient.updateItem(updateItemRequest); LOGGER.info("Conditional update successful - demonstrates strong consistency"); return response; } catch (ConditionalCheckFailedException e) { LOGGER.warning("Conditional check failed: " + e.getMessage()); throw e; } catch (DynamoDbException e) { LOGGER.severe("Failed to perform conditional update: " + tableName + " - " + e.getMessage()); throw DynamoDbException.builder() .message("Failed to perform conditional update: " + tableName) .cause(e) .build(); } }

AWS SDK for Java 2.x를 사용하여 MRSC 복제본과 감시자가 활성화될 때까지 기다립니다.

public static void waitForMRSCReplicasActive( final DynamoDbClient dynamoDbClient, final String tableName, final int maxWaitTimeSeconds) throws InterruptedException { if (dynamoDbClient == null) { throw new IllegalArgumentException("DynamoDB client cannot be null"); } if (tableName == null || tableName.trim().isEmpty()) { throw new IllegalArgumentException("Table name cannot be null or empty"); } if (maxWaitTimeSeconds <= 0) { throw new IllegalArgumentException("Max wait time must be positive"); } try { LOGGER.info("Waiting for MRSC replicas and witnesses to become active: " + tableName); final long startTime = System.currentTimeMillis(); final long maxWaitTimeMillis = maxWaitTimeSeconds * 1000L; int backoffSeconds = 5; // Start with 5 second intervals final int maxBackoffSeconds = 30; // Cap at 30 seconds while (System.currentTimeMillis() - startTime < maxWaitTimeMillis) { DescribeTableResponse response = describeMRSCTable(dynamoDbClient, tableName); boolean allActive = true; StringBuilder statusReport = new StringBuilder(); if (response.table().multiRegionConsistency() == null || !MultiRegionConsistency.STRONG .toString() .equals(response.table().multiRegionConsistency().toString())) { allActive = false; statusReport .append("MultiRegionConsistency: ") .append(response.table().multiRegionConsistency()) .append(" "); } if (response.table().replicas() == null || response.table().replicas().isEmpty()) { allActive = false; statusReport.append("No replicas found. "); } if (response.table().globalTableWitnesses() == null || response.table().globalTableWitnesses().isEmpty()) { allActive = false; statusReport.append("No witnesses found. "); } // Check table status if (!"ACTIVE".equals(response.table().tableStatus().toString())) { allActive = false; statusReport .append("Table: ") .append(response.table().tableStatus()) .append(" "); } // Check replica status if (response.table().replicas() != null) { for (var replica : response.table().replicas()) { if (!"ACTIVE".equals(replica.replicaStatus().toString())) { allActive = false; statusReport .append("Replica(") .append(replica.regionName()) .append("): ") .append(replica.replicaStatus()) .append(" "); } } } // Check witness status if (response.table().globalTableWitnesses() != null) { for (var witness : response.table().globalTableWitnesses()) { if (!"ACTIVE".equals(witness.witnessStatus().toString())) { allActive = false; statusReport .append("Witness(") .append(witness.regionName()) .append("): ") .append(witness.witnessStatus()) .append(" "); } } } if (allActive) { LOGGER.info("All MRSC replicas and witnesses are now active: " + tableName); return; } LOGGER.info("Waiting for MRSC components to become active. Status: " + statusReport.toString()); LOGGER.info("Next check in " + backoffSeconds + " seconds..."); tempWait(backoffSeconds); // Exponential backoff with cap backoffSeconds = Math.min(backoffSeconds * 2, maxBackoffSeconds); } throw DynamoDbException.builder() .message("Timeout waiting for MRSC replicas to become active after " + maxWaitTimeSeconds + " seconds") .build(); } catch (DynamoDbException | InterruptedException e) { LOGGER.severe("Failed to wait for MRSC replicas to become active: " + tableName + " - " + e.getMessage()); throw e; } }

AWS SDK for Java 2.x를 사용하여 MRSC 복제본 및 감시자를 정리합니다.

public static UpdateTableResponse cleanupMRSCReplicas( final DynamoDbClient dynamoDbClient, final String tableName, final Region replicaRegion, final Region witnessRegion) { if (dynamoDbClient == null) { throw new IllegalArgumentException("DynamoDB client cannot be null"); } if (tableName == null || tableName.trim().isEmpty()) { throw new IllegalArgumentException("Table name cannot be null or empty"); } if (replicaRegion == null) { throw new IllegalArgumentException("Replica region cannot be null"); } if (witnessRegion == null) { throw new IllegalArgumentException("Witness region cannot be null"); } try { LOGGER.info("Cleaning up MRSC replicas and witnesses for table: " + tableName); // Remove replica using ReplicationGroupUpdate ReplicationGroupUpdate replicaUpdate = ReplicationGroupUpdate.builder() .delete(DeleteReplicationGroupMemberAction.builder() .regionName(replicaRegion.id()) .build()) .build(); // Remove witness GlobalTableWitnessGroupUpdate witnessUpdate = GlobalTableWitnessGroupUpdate.builder() .delete(DeleteGlobalTableWitnessGroupMemberAction.builder() .regionName(witnessRegion.id()) .build()) .build(); UpdateTableRequest updateTableRequest = UpdateTableRequest.builder() .tableName(tableName) .replicaUpdates(List.of(replicaUpdate)) .globalTableWitnessUpdates(List.of(witnessUpdate)) .build(); UpdateTableResponse response = dynamoDbClient.updateTable(updateTableRequest); LOGGER.info("MRSC cleanup initiated - removing replica and witness. Response: " + response); return response; } catch (DynamoDbException e) { LOGGER.severe("Failed to cleanup MRSC replicas: " + tableName + " - " + e.getMessage()); throw DynamoDbException.builder() .message("Failed to cleanup MRSC replicas: " + tableName) .cause(e) .build(); } }

AWS SDK for Java 2.x를 사용하여 MRSC 워크플로 데모를 완료합니다.

public static void demonstrateCompleteMRSCWorkflow( final DynamoDbClient primaryClient, final DynamoDbClient replicaClient, final String tableName, final Region replicaRegion, final Region witnessRegion) throws InterruptedException { if (primaryClient == null) { throw new IllegalArgumentException("Primary DynamoDB client cannot be null"); } if (replicaClient == null) { throw new IllegalArgumentException("Replica DynamoDB client cannot be null"); } if (tableName == null || tableName.trim().isEmpty()) { throw new IllegalArgumentException("Table name cannot be null or empty"); } if (replicaRegion == null) { throw new IllegalArgumentException("Replica region cannot be null"); } if (witnessRegion == null) { throw new IllegalArgumentException("Witness region cannot be null"); } try { LOGGER.info("=== Starting Complete MRSC Workflow Demonstration ==="); // Step 1: Create an empty single-Region table LOGGER.info("Step 1: Creating empty single-Region table"); createRegionalTable(primaryClient, tableName); // Use the existing GlobalTableOperations method for basic table waiting LOGGER.info("Intermediate step: Waiting for table [" + tableName + "] to become active before continuing"); GlobalTableOperations.waitForTableActive(primaryClient, tableName); // Step 2: Convert to MRSC with replica and witness LOGGER.info("Step 2: Converting to MRSC with replica and witness"); convertToMRSCWithWitness(primaryClient, tableName, replicaRegion, witnessRegion); // Wait for MRSC conversion to complete using MRSC-specific waiter LOGGER.info("Waiting for MRSC conversion to complete..."); waitForMRSCReplicasActive(primaryClient, tableName); LOGGER.info("Intermediate step: Waiting for table [" + tableName + "] to become active before continuing"); GlobalTableOperations.waitForTableActive(primaryClient, tableName); // Step 3: Verify MRSC configuration LOGGER.info("Step 3: Verifying MRSC configuration"); describeMRSCTable(primaryClient, tableName); // Step 4: Test strong consistency with data operations LOGGER.info("Step 4: Testing strong consistency with data operations"); // Add test item to primary region putTestItem(primaryClient, tableName, "The Beatles", "Hey Jude", "The Beatles 1967-1970", "1968"); // Immediately read from replica region (no wait needed with MRSC) LOGGER.info("Reading from replica region immediately (strong consistency):"); GetItemResponse getResponse = getItemWithConsistentRead(replicaClient, tableName, "The Beatles", "Hey Jude"); if (getResponse.hasItem()) { LOGGER.info("✓ Strong consistency verified - item immediately available in replica region"); } else { LOGGER.warning("✗ Item not found in replica region"); } // Test conditional update from replica region LOGGER.info("Testing conditional update from replica region:"); performConditionalUpdate(replicaClient, tableName, "The Beatles", "Hey Jude", "5"); LOGGER.info("✓ Conditional update successful - demonstrates strong consistency"); // Step 5: Cleanup LOGGER.info("Step 5: Cleaning up resources"); cleanupMRSCReplicas(primaryClient, tableName, replicaRegion, witnessRegion); // Wait for cleanup to complete using basic table waiter LOGGER.info("Waiting for replica cleanup to complete..."); GlobalTableOperations.waitForTableActive(primaryClient, tableName); // "Halt" until replica/witness cleanup is complete DescribeTableResponse cleanupVerification = describeMRSCTable(primaryClient, tableName); int backoffSeconds = 5; // Start with 5 second intervals while (cleanupVerification.table().multiRegionConsistency() != null) { LOGGER.info("Waiting additional time (" + backoffSeconds + " seconds) for MRSC cleanup to complete..."); tempWait(backoffSeconds); // Exponential backoff with cap backoffSeconds = Math.min(backoffSeconds * 2, 30); cleanupVerification = describeMRSCTable(primaryClient, tableName); } // Delete the primary table deleteTable(primaryClient, tableName); LOGGER.info("=== MRSC Workflow Demonstration Complete ==="); LOGGER.info(""); LOGGER.info("Key benefits of Multi-Region Strong Consistency (MRSC):"); LOGGER.info("- Immediate consistency across all regions (no eventual consistency delays)"); LOGGER.info("- Simplified application logic (no need to handle eventual consistency)"); LOGGER.info("- Support for conditional writes and transactions across regions"); LOGGER.info("- Consistent read operations from any region without waiting"); } catch (DynamoDbException | InterruptedException e) { LOGGER.severe("MRSC workflow failed: " + e.getMessage()); throw e; } }