AWS Database Blog

Use Region pinning to set a home Region for items in an Amazon DynamoDB global table

Amazon DynamoDB global tables provide a fully managed, scalable solution for deploying a multi-Region, multi-active database that’s replicated across multiple AWS Regions. Global tables allow applications to read and write data to a table, with the data automatically replicated across all Regions that are associated with that table. Global tables are ideal for applications that need to provide low-latency data access to users in various locations, as well as for applications that must be able to fail over to a different Region to meet business continuity requirements.

Region pinning for a DynamoDB global table item lets you specify a home Region for an item and specify where the item should be written or read. Assigning a home Region to an item supports use cases where you need to ensure that your application always writes the data to a specific Region or avoid conflicts if writers in two Regions update the same item within a replication interval (usually one second). For example, you can use Region pinning on a global table associated with multiple Regions to ensure that an item is written to a location close to the users of that table.

In this post, we show you how to implement global table item Region pinning for a sample table and how you can implement this for your use cases.

Solution overview

In this solution, we demonstrate how to create a global table and assign a home Region to each item. After a home Region is assigned, we show you how to make an update in the Region where a request originated. We use a new attribute named src_region and add it to items in the global table. This attribute is used to assign a home Region for each item. In the application code, the Region a request is coming from is checked for each update.

For this example, you’ll create the following table in us-east-1 Region and a replica in us-west-1 Region.

Primary key Attributes
Partition key: Year Sort key: Title Director rating src_region
1944 Lifeboat Alfred Hitchcock 7 us-east-1
1975 Jaws Steven Spielberg 7 us-west-1
1997 Titanic James Cameron 7 us-east-1

A condition expression allows you to specify the conditions under which a write or update operation should be performed on an item in a table. You use a condition expression on the src_region attribute to compare the Region the request is coming from to the home Region of the attribute. If the Regions match, then the item in the home Region is updated; if not, the item in the request Region is updated. The DynamoDB global table automatically replicates the changes to all other replicas. Refer to the multi-Region replication for DynamoDB for more details about replicating data across multiple Regions. The following sections walk you through an example of setting up Region pinning.

Prerequisites

For this solution, you must meet the following requirements:

  • Have Python 3.8 version or higher and Boto3 installed.
  • Be able to configure AWS credentials and Regions.
  • Have an AWS Identity and Access Management (IAM) principal that can be granted access to the DynamoDB table used in the example.

Create a DynamoDB global table and replica table

Start by creating a DynamoDB table and configuring a replica.

To create a global table and replica

  1. Use the following code to create your global table:
    aws dynamodb create-table \
        --table-name Movies \
        --attribute-definitions \
            AttributeName=Year,AttributeType=N \
            AttributeName=Title,AttributeType=S \
        --key-schema \
            AttributeName=Year,KeyType=HASH \
            AttributeName=Title,KeyType=RANGE \
    		--billing-mode PAY_PER_REQUEST\
        --stream-specification StreamEnabled=true,StreamViewType=NEW_AND_OLD_IMAGES \
        --region us-east-1
    
  2.  You can use the AWS Management Console for DynamoDB to confirm the settings. The following screenshot shows that the Movies table is created in us-east-1 with the requested attributes.Figure 1: Review created table and attributes
  3. Use the following command to create a replica of the table:
    aws dynamodb update-table --table-name Movies --cli-input-json --region us-east-1 \
        '{
            "ReplicaUpdates":
            [
                {
                "Create": {
                    "RegionName": "us-west-1"
                    }
                }
            ]
        }' 
    
  4. The following screenshot shows that a replica is created in the us-west-1 Region and has the same attributes as the original table in us-east-1.Replica in Other regionFigure 2: Review replica table

Note: Alternatively, you can use create-global-table in the AWS Command Line Interface (AWS CLI) or use the cloud shell from an existing table in the console. This command creates a replication relationship between two or more DynamoDB tables with the same table name in the provided Regions.

Load sample data

In this step, you populate the Movies table with sample data. This scenario uses a sample data file that has movie details. The data is in JSON format, as shown in example code. For each movie, there is a year, title, director, rating, and home Region (src_region).

To load sample data

    1. Create a file named “moviedata.json” with the following code:
      [
          {
              "year": 1944,
              "title": "Lifeboat",
              "director": "Alfred Hitchcock",
              "rating": 7,
              "src_region" : "us-east-1"
          },
          {
              "year": 1975,
              "title": "Jaws",
              "director": "Steven Spielberg",
              "rating": 7,
              "src_region" : "us-west-1"
          },
          {
              "year": 1997,
              "title": "Titanic",
              "director": "James Cameron",
              "rating" : 7,
              "src_region": "us-east-1"
          }
      ]
      
    2. After you create the sample data file, run the following program to populate the Movies table:
      import boto3
      import json
      
      dynamodb = boto3.resource('dynamodb', region_name='us-east-1')
      
      table = dynamodb.Table('Movies')
      
      with open("moviedata.json") as json_file:
          movies = json.load(json_file)
          for movie in movies:
              year = int(movie['year'])
              title = movie['title']
              director = movie['director']
              rating = int(movie['rating'])
              src_region = movie['src_region']
      
              print("Adding movie:" , year, title)
      
              table.put_item(
                      Item={
                          'year': year,
                          'title': title,
                          'director': director,
                          'rating' : rating,
                          'src_region': src_region
                      }
              )
    3. You can use the console to confirm that data for the Movies table has been added in both the us-east-1 and us-west-1 Regions.
      Items in Movies TableFigure 3: Use the console to check the Movies table

Region pinning

After populating the data, you can observe that each item is now associated with a home Region through the attribute src_region. When your application tries to update an item, the code compares the Region the request is coming from with the src_region and updates the item in the request Region but not in the src_region.

The following code is an example of how to update items conditionally based on the Region. Update_item and ConditionExpression are used in this code.

import boto3
from botocore.exceptions import ClientError

request_origin_region = boto3.session.Session().region_name

dynamodb = boto3.resource('dynamodb')
table = dynamodb.Table('Movies')

updateItem={}
updateItem['updateExpression'] = "set rating = :r"
updateItem['conditionExpression'] = "src_region = request_origin_region"
updateItem['ExpressionAttributeValues'] = dict(
    {
      ':r':9,
    }
   )

updateItemKey = {}
updateItemKey['year'] = 1975
updateItemKey['title'] = 'Jaws'

try:
    response = table.update_item(
        Key = updateItemKey,
        UpdateExpression = updateItem['updateExpression'],
        ConditionExpression = updateItem['conditionExpression'],
        ExpressionAttributeValues = updateItem['ExpressionAttributeValues'],
        ReturnValues="UPDATED_NEW"
    )
except ClientError as error:
    ecode = error.response['Error']['Code']
    if ecode == 'ConditionalCheckFailedException':
        ddb_region = boto3.resource('dynamodb', region_name=request_origin_region)
        ddb_table = dynamodb.Table('Movies')
        response = ddb_table.update_item(
            Key = updateItemKey,
            UpdateExpression = updateItem['updateExpression'],
            ExpressionAttributeValues = updateItem['ExpressionAttributeValues'],
            ReturnValues="UPDATED_NEW"
        )
        
    elif ecode == 'Internal server error':
        print(f"Internal Server Error: {error.response}")
        
    else:
        print(f"All other exceptions: {error.response}")

The preceding sample code doesn’t include code for resiliency in case of an event that degrades the workload in the source Region. For an overview of how to build resiliency to account for potential disruptions, refer to Build resilient applications with Amazon DynamoDB global tables: Part 3.

Also, when you work with DynamoDB global table items, you might get an HTTP 5xx error similar to the following:

Internal server error (Service: AmazonDynamoDBv2; Status Code: 500; Error Code: InternalServerError.

This error indicates a transient issue, such as a network outage or backend hardware failure. To mitigate these errors, you can do the following:

  • Implement a retry strategy for requests that fail with a 5xx error code. All AWS SDKs have a built-in retry mechanism with an algorithm that uses exponential backoff. You can modify the retry parameters to suit your needs. For more information, see Error retries and exponential backoff.
  • Avoid strongly consistent reads. When there’s a network delay or outage, strongly consistent reads are more likely to fail with a 5xx error. For more information, see Read consistency.

Clean up

Delete the table and the replicas created for this example to avoid incurring future costs. You can do this from the console or by using the following command

aws dynamodb delete-table --table-name Movies 

Conclusion

In this post, we showed you an example of Region pinning of an item in a DynamoDB global table. With this approach, you can restrict updates of an item from a specific Region. You can use the code samples in this post as a starting point to implement your use case.

Try this solution and share your feedback in the comments section.


About the Authors

Randy DeFauw is a Senior Principal Solutions Architect at AWS. He holds an MSEE from the University of Michigan, where he worked on computer vision for autonomous vehicles. He also holds an MBA from Colorado State University. Randy has held a variety of positions in the technology space, ranging from software engineering to product management. He entered the Big Data space in 2013 and continues to explore that area. He’s actively working on projects in the ML space and has presented at numerous conferences including Strata and GlueCon.

Ranjith Rayaprolu is a Senior Solutions Architect at AWS working with customers in the Pacific Northwest. He helps customers design and operate Well-Architected solutions in AWS that address their business problems and accelerate the adoption of AWS services. He focuses on AWS security and networking technologies to develop solutions in the cloud across different industry verticals. Ranjith lives in the Seattle area and loves outdoor activities.