Build a multi-region, multi-master application with Serverless and DynamoDB Global Tables
Tags: serverless, dynamodb, aws, python
Published: 2022-05-05 14:43:31

Originally posted at [Serverless](https://www.serverless.com/blog/build-multiregion-multimaster-application-dynamodb-global-tables) on January 30th, 2018

*To see how DynamoDB compares to MongoDB, Cassandra, or BigTable, [see here](https://serverless.com/dynamodb/).*

AWS gives devs some *powerful* building blocks for making amazing applications. In this walkthrough, we’re going to create a multi-region, multi-master, geo-routed application-all in about 30 minutes.

We’ll have compute located on two different continents writing to databases in their own region, and the written data will be replicated to the database in the other region. Users will be directed to the closest region according to their location.

Prefer the video walkthrough? The webinar was recorded live, and [is available on Cloud Academy](https://cloudacademy.com/webinars/how-build-regionally-distributed-serverless-architecture-61/); give it a watch!

Now, on to the word-wise walkthrough.

## Setting up your app

Before we begin, you’ll need the [Serverless Framework installed](https://serverless.com/framework/docs/providers/aws/guide/quick-start/) with an AWS account set up. Your version of Serverless must be 1.25 or higher to take advantage of regional endpoints.

I’m going to write this in Python because I love Python. But you can do it in any language you want.

Create a new Serverless project from a template using the sls create command and change into that directory:
{% gist https://gist.github.com/richardgrantserverless/639019244f4255949848a76408dc3efb.js %}
## Basic functionality

Our application will be a web API over a key-value store, where users can submit and retrieve values for keys. The path in the URL will be the key name, and the payload for a POST request will be the value.

To handle the basic functionality with our application, we’ll need three things:

* a DynamoDB table to store our keys and values,

* a set_key function to allow users to store the value for a key

* a get_key function to allow users to retrieve the value for a key

Edit your serverless.yml so that it looks as follows:
{% gist https://gist.github.com/richardgrantserverless/68ecbb4276c2ccb9adcc3897ffbb0d9d.js %}
Let’s walk through this a bit.

In the resources section, we've used CloudFormation to define our DynamoDB table. We've see it has a HASH key named "key"-this is the primary access pattern for our table.

In the provider section, we've added iamRoleStatements that give our functions GetItem and PutItem permissions to our table. We've also injected the name of the table into our environment via the KEY_TABLE variable.

Also, note that we’re using a regional endpoint with endpointType: regional. This was added in the [1.25 release of the Serverless Framework](https://serverless.com/blog/serverless-v1.25.0/), and it allows us to have our endpoint in a particular region, rather than at all CloudFront edge locations.

Finally, in the functions section, we have our two functions: getKey and setKey. The function handlers are defined in the handlers directory in the get_key and set_key modules.

Let’s add our handlers. First, remove the templated handler.py and add a handlers directory:
{% gist https://gist.github.com/richardgrantserverless/258a0e277646d4cbff0da36671c2b1b5.js %}
Add the code for your setKey function in handlers/set_key.py:
{% gist https://gist.github.com/richardgrantserverless/f6f8b011979604e96ca5bf2b8f7f0d0c.js %}
We will parse the key name from the URL path parameter and the value from the JSON body in the POST request. Then we’ll save a DynamoDB Item with the key, value, and region it was written from.

Then, we’ll add the code for our getKey function in handlers/get_key.py:
{% gist https://gist.github.com/richardgrantserverless/3b58db1a300e8868495b8c70a84175ea.js %}
It takes the key from the path, retrieves it from DynamoDB, and returns it to the user. Note that it returns both the region it was last written from *and* the region that it’s currently being read from. This will be useful later on.

Great, let’s deploy it. I’m going to use the us-west-2 region:
{% gist https://gist.github.com/richardgrantserverless/32cb86a757030120b2a1ce977dba8397.js %}
If your deploy was successful, it will show your base url in the endpoints block of your Service Information.

Let’s test our endpoints. Export your url into a variable called BASE_URL, then run a curl command to set a key:
{% gist https://gist.github.com/richardgrantserverless/e009fafe85385dd2755b3d92335d1586.js %}
And now, let’s retrieve it:
{% gist https://gist.github.com/richardgrantserverless/ceea71e5934234460d5fef8cee70b59e.js %}
Awesome, we retrieved our key. Note that it says it was last written in the us-west-2 region and that we're currently reading from the us-west-2 region.

## Adding a custom domain

Now, let’s add a custom domain to our endpoint. This means: (a) we won’t have a funky endpoint like 6rbm4350zj.execute-api.us-west-2.amazonaws.com; (b) it'll be easier to geo-route later.

To follow along here, *you will need to own a domain registered in Route53*.

We’re going to use the [serverless-domain-manager](https://github.com/amplify-education/serverless-domain-manager) plugin to simplify this.

**Note:** If you want more details on this, read our post on [setting up your custom domain with Serverless & API Gateway](https://serverless.com/blog/serverless-api-gateway-domain/). This usage will be a little bit different since we’re using a new feature to [set up regional domains](https://github.com/amplify-education/serverless-domain-manager/pull/49).

My base domain is serverlessteam.com. I want my app to generally be accessible at keyvalues.serverlessteam.com.

First, let’s create an SSL certificate for our domain using Amazon Certificate Manager (ACM). Navigate to Amazon Certificate Manager *in the region you want to deploy your endpoint*. Click “Request a certificate” and enter your domain.

I’ll create a certificate at keyvalue.serverlessteam.com.

![](https://cdn-images-1.medium.com/max/4964/0*dxgRr7MhMy3g42Gi.png)

Hit ‘Next’, and AWS will send your domain manager an email to confirm the certificate.

While you’re here, you should create one for your other region as well. I created mine in eu-central-1 (Frankfurt) using the same process.

Next, install the serverless-domain-manager plugin using npm:
{% gist https://gist.github.com/richardgrantserverless/afe9bc450035f766e08fa5480ce52d20.js %}
Configure your serverless.yml by adding the plugin to plugins and adding config in the custom block:
{% gist https://gist.github.com/richardgrantserverless/a9e8c131ebff87c996e01fbbfce833b8.js %}
This will help us provision a custom domain in API Gateway. Note that we’re provisioning a regional endpointType, and the domain name will use the region. We won't have it create our Route53 record as we'll create that separately.

Go on and create your domain:
{% gist https://gist.github.com/richardgrantserverless/ab1354ec1f2763b938bee42ed2bde4ad.js %}
Because it can take up to 40 minutes to provision a domain, let’s provision our other region while we’re here:
{% gist https://gist.github.com/richardgrantserverless/e3faaba28148c6e43e5ac88d44e35d5a.js %}
And now, deploy our two services:
{% gist https://gist.github.com/richardgrantserverless/6a3a344e454a7bbb8d6fa62db1a5c0fd.js %}
Both our services are deployed. Let’s set up the Route53 Latency records to our endpoints.

[Latency-based routing](https://docs.aws.amazon.com/Route53/latest/DeveloperGuide/routing-policy.html#routing-policy-latency) is a really interesting feature of Route53. It allows you to create multiple DNS records for the same resource. Each DNS record points to IP addresses in different regions. When the user makes a DNS query, Route53 will return the record that offers the lowest latency based on the requesting user’s location. *Neat.*

We’ll create two records for keyvalue.<yourdomain>.com-one in us-west-2 and one in eu-central-1. Use the script below, modifying the value of DOMAIN in the first line to match your domain:
{% gist https://gist.github.com/richardgrantserverless/e191944d3a6439849a47ffe6e5048155.js %}
After this operation, you’ll have latency records pointing to your regional API Gateways using the same subdomain. Let’s try it out.

Set a key in your US region:
{% gist https://gist.github.com/richardgrantserverless/a08e0a1bca223a5617ccc51aa40723d0.js %}
Then retrieve it:
{% gist https://gist.github.com/richardgrantserverless/ffbecdca5978bf94d923b3d66186340d.js %}
Let’s try retrieving the key from our European region:
{% gist https://gist.github.com/richardgrantserverless/5fb134b31f2148847fe52cb0d9bf9642.js %}
Ah, the key doesn’t show up. This is because our two regions are completely separate from each other. Each has their own Lambda compute and their own DynamoDB data store.

While these separate regions allow for fast queries around the globe, it also results in a segmented experience for users. We want the *data* to be shared, so a user gets a fast, consistent experience at any point on earth.

## DynamoDB Global Tables

Enter: DynamoDB Global Tables.

This feature, [announced at AWS re:Invent 2017](https://serverless.com/blog/ultimate-list-serverless-announcements-reinvent/#dynamodb-global-tables), allows you to specify DynamoDB tables in separate regions to act as a single table. Writes in one region will be asynchronously replicated to the other regions.

This allows for some powerful applications without writing custom syncing logic.

To set up a Global Table, there are a few requirements. First, the tables must have the same name in different regions. Second, both tables must be *entirely empty*. This means we’ll need to clear out any existing items in our table.

If you have Items in your table, use the script below to clean them out. You’ll need to run it for each region that has a table with Items in it:
{% gist https://gist.github.com/richardgrantserverless/4ec35153ce7a316fb625ce8473837caa.js %}
Once your tables are cleared out, then we can run the command to create a global table using the AWS CLI:
{% gist https://gist.github.com/richardgrantserverless/7eec6d49132beeecf9c90ed0c8c14b5f.js %}
You will get a response indicating that the table is being created:
{% gist https://gist.github.com/richardgrantserverless/9b034d30f7f2d66a1f1cf025ab821513.js %}
Now our tables will be synced together, so writes from one region will be replicated to another region.

Let’s test it out. Save a key in the US region:
{% gist https://gist.github.com/richardgrantserverless/0c4784a2842e7eb4f7ff1a6f33a41f34.js %}
Then retrieve it in the EU region:
{% gist https://gist.github.com/richardgrantserverless/f78357da66a03f67a4c6136f0d923351.js %}
Nice, it worked. We can read the key in a different region. Note that the response from the getKey method includes the region in which the key was written ( us-west-2) as well as the region from which we're currently reading ( eu-central-1).

We can also use our custom domain to retrieve the key from whichever region is closest:
{% gist https://gist.github.com/richardgrantserverless/6734cd82ac2f9defbc5f9d5b1217a1ed.js %}
This read from the us-west-2 region as I'm located in the US, but it would read from the eu-central-1 region for those closer to that region.

## Frontend client

To this point, we’ve been using curl and the terminal for calling our API. If you want a more visual approach, [Alex Casalboni](https://twitter.com/alex_casalboni) created a [visual frontend](https://github.com/alexcasalboni/serverless-multi-region-client-demo) for interacting with the backend. It allows you to write to and read from specific regions, while also showing the latency of your requests:

![](https://cdn-images-1.medium.com/max/5148/0*BNOCU-afTNBEIDLO.png)

Check it out and run it yourself!

## Miss the DynamoDB global tables webinar?

Follow [@goserverless](https://twitter.com/goserverless/) on twitter or sign up for our bi-monthly newsletter (via the lefthand menu) to stay on top of serverless industry news.

![](https://cdn-images-1.medium.com/max/2000/1*NCHqjhEtW2FQQm5NO0ekAg.png)

*Originally published at [https://www.serverless.com](https://www.serverless.com/blog/build-multiregion-multimaster-application-dynamodb-global-tables).*