Data Management Patterns for Multi-Region Deployments on AWS
This article explores data replication strategies for running services across multiple AWS regions. It covers how to balance consistency, availability, and latency when your application needs to serve users globally—from simple read replicas to complex multi-region write scenarios with conflict resolution.
A VMware/Vanson Bourne survey found that 49% of organizations cite data integration across clouds as a top challenge. The storage layer is where multi-region architectures either succeed or collapse.
This article presents four data replication patterns for multi-region microservices on AWS, each targeting a different point on the consistency-availability spectrum. Every pattern includes AWS implementation details and Terraform configuration.
The CAP Theorem in Practice

The CAP theorem states that a distributed data store can provide at most two of three guarantees during a network partition: Consistency, Availability, and Partition Tolerance. Since network partitions between AWS regions are inevitable, every multi-region storage system must choose between consistency and availability during those events.
A CP system (like Aurora single-writer) rejects writes from secondary regions during a partition, preserving correctness. An AP system (like DynamoDB Global Tables in MREC mode) accepts writes in both regions, preserving availability at the cost of temporarily inconsistent data.
The four patterns represent different positions on this spectrum. Patterns 1A and 1B centralize writes. Pattern 2 partitions write ownership. Patterns 3a and 3b allow writes everywhere — Pattern 3a accepting eventual consistency, Pattern 3b achieving strong consistency through quorum-based arbitration.
Pattern 1A: Read Local, Write Global (Asynchronous Replication)

Problem
An application needs low-latency reads from multiple regions. The workload is read-heavy and can tolerate slightly stale reads. All writes can go to a single region.
Solution
Deploy a primary read-write cluster in one region and read-only replicas elsewhere. The primary asynchronously replicates to secondaries. Applications read locally and send all writes to the primary.
AWS Implementation
Aurora Global Database replicates to up to ten secondary clusters using storage-level replication, which doesn't consume database engine resources. Typical cross-region lag is under one second (
AuroraGlobalDBReplicationLag metric). ElastiCache Global Datastore provides the same topology for caching.Failure mode: if the primary region fails, a secondary must be promoted (RTO typically under one minute). Writes are unavailable during promotion.
Terraform Configuration
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
# Global database linking both regions
resource "aws_rds_global_cluster" "main" {
global_cluster_identifier = "multi-region-global-db"
engine = "aurora-postgresql"
engine_version = "16"
database_name = "application"
storage_encrypted = true
}
# Primary cluster in us-east-1 — accepts reads and writes
resource "aws_rds_cluster" "primary" {
cluster_identifier = "primary-cluster"
global_cluster_identifier = aws_rds_global_cluster.main.id
engine = "aurora-postgresql"
engine_version = "16"
master_username = "app_admin"
manage_master_user_password = true
master_user_secret_kms_key_id = aws_kms_key.aurora.arn
database_name = "application"
}
# Secondary cluster in eu-west-1 — read-only, async-replicated
resource "aws_rds_cluster" "secondary" {
cluster_identifier = "secondary-cluster"
global_cluster_identifier = aws_rds_global_cluster.main.id
engine = "aurora-postgresql"
engine_version = "16"
}Tradeoffs
Zero write conflicts. Sub-second replication lag suits read-heavy workloads: product catalogs, content metadata, reference data. All writes pay cross-region latency from non-primary regions, and brief write outage occurs during failover.
Pattern 1B: Read Local, Write Global (Write Forwarding)

Problem
The application cannot tolerate stale reads. Every read must reflect the latest committed write, but writes can still go to a single primary region.
Solution
Use the same topology as Pattern 1A with write forwarding enabled. Secondary clusters transparently forward writes to the primary, and session-level consistency ensures subsequent reads see forwarded write results.
AWS Implementation
Aurora Global Database with write forwarding (PostgreSQL 14.9+, 15.4+, 16.x) intercepts DML on secondaries and forwards to the primary. The
apg_write_forward.consistency_mode parameter controls behavior:SESSION(default): Reads block until forwarded writes replicate back within the same sessionEVENTUAL: No waiting; reads may return stale dataGLOBAL: Reads block until fully caught up with primary — strongest guarantee, highest latency
DDL and
SERIALIZABLE isolation are unsupported. The primary reserves connections for forwarding via apg_write_forward.max_forwarding_connections_percent (default 25%).Terraform Configuration
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
# Secondary cluster — write forwarding sends DML to the primary transparently
resource "aws_rds_cluster" "secondary" {
cluster_identifier = "secondary-cluster"
global_cluster_identifier = aws_rds_global_cluster.main.id
engine = "aurora-postgresql"
engine_version = "16"
enable_global_write_forwarding = true
db_cluster_parameter_group_name = aws_rds_cluster_parameter_group.write_forwarding.name
}
# Session-level consistency controls for forwarded writes
resource "aws_rds_cluster_parameter_group" "write_forwarding" {
family = "aurora-postgresql16"
name = "write-forwarding-params"
parameter {
name = "apg_write_forward.consistency_mode"
value = "session" # options: session, eventual, global, off
}
}Tradeoffs
Eliminates application-level write routing. Every write from secondary crosses regions twice.
SESSION mode prevents write-then-read-miss within a session; GLOBAL adds latency to every read. Monitor AuroraForwardingReplicaReadWaitLatency.Pattern 2: Read Local, Write Partitioned (Entity-Based Ownership)

Problem
The application needs low-latency reads and writes, but bidirectional replication conflicts are unacceptable. Data has a natural partitioning dimension (geography, tenant ID).
Solution
Each entity has a "home" region for writes. All regions hold read replicas of everything. Applications read locally but route writes to the entity's home region.
AWS Implementation
Consider US users owned by us-east-1 and EU users by eu-west-1. Each region runs an independent Aurora PostgreSQL cluster. AWS DMS CDC tasks replicate bidirectionally. Entity ownership is determined by a
home_region field or DynamoDB routing table.Critical: configure DMS loopback prevention to avoid infinite replication cycles.
Terraform Configuration
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
# Each region has its own independent primary Aurora cluster
resource "aws_rds_cluster" "us_primary" {
cluster_identifier = "users-us-primary"
engine = "aurora-postgresql"
engine_version = "16.4"
database_name = "users"
}
resource "aws_rds_cluster" "eu_primary" {
cluster_identifier = "users-eu-primary"
engine = "aurora-postgresql"
engine_version = "16.4"
database_name = "users"
}
# DMS CDC task replicates US-owned entities to EU (one direction shown)
resource "aws_dms_replication_task" "us_to_eu" {
replication_task_id = "us-to-eu-cdc"
migration_type = "cdc"
replication_instance_arn = aws_dms_replication_instance.us_east.replication_instance_arn
source_endpoint_arn = aws_dms_endpoint.us_source.endpoint_arn
target_endpoint_arn = aws_dms_endpoint.eu_target.endpoint_arn
table_mappings = jsonencode({
rules = [
{
rule-type = "selection"
rule-id = "1"
rule-name = "select-us-owned-tables"
rule-action = "include"
object-locator = {
schema-name = "public"
table-name = "users_us_%"
}
}
]
})
# Loopback prevention stops replicated rows from cycling back
replication_task_settings = jsonencode({
LoopbackPreventionSettings = {
EnableLoopbackPrevention = true
SourceSchema = "public"
TargetSchema = "public"
}
})
}
# Entity-to-region routing table — replicated for local reads in every region
resource "aws_dynamodb_table" "entity_routing" {
name = "entity-region-routing"
billing_mode = "PAY_PER_REQUEST"
hash_key = "entity_id"
attribute {
name = "entity_id"
type = "S"
}
replica {
region_name = "eu-west-1"
}
}Tradeoffs
No conflicts — each entity has one writer. Reads always local. Application must track entity ownership. Foreign entity writes add cross-region latency. Re-partitioning requires careful migration. DMS adds operational overhead versus Aurora Global Database.
Pattern 3a: Read Local, Write Local (Bidirectional Asynchronous Replication)

Problem
The application requires low-latency writes everywhere. The data model tolerates eventual consistency and has a conflict resolution strategy.
Solution
Independent read-write instances in each region. All writes go locally. Bidirectional async replication with last-writer-wins or application-level merge for conflicts.
AWS Implementation with DynamoDB Global Tables
DynamoDB Global Tables replicates items automatically across regions. Each replica accepts reads and writes. Replication typically completes under one second (
ReplicationLatency metric). Conflicts use last-writer-wins at item level — the later timestamp survives silently.Implications: non-idempotent operations (
counter = counter + 1) lose updates in concurrent writes. Use idempotent writes or accept silent conflict resolution.Conflict minimization strategies:
- Partition writes by region at application level (becomes Pattern 2)
- Use IAM to restrict writes to single region (becomes Pattern 1A)
- Design for architectural impossibility of concurrent writes (append-only logs, unique keys)
Terraform Configuration
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
# DynamoDB Global Table — each region gets a read-write replica
resource "aws_dynamodb_table" "global_orders" {
name = "orders"
billing_mode = "PAY_PER_REQUEST"
hash_key = "order_id"
range_key = "created_at"
attribute {
name = "order_id"
type = "S"
}
attribute {
name = "created_at"
type = "S"
}
# Streams required for cross-region replication
stream_enabled = true
stream_view_type = "NEW_AND_OLD_IMAGES"
# Adding a replica block creates a Global Table in that region
replica {
region_name = "eu-west-1"
}
}AWS Implementation with Aurora PostgreSQL and DMS
For relational workloads, use two Aurora PostgreSQL clusters with DMS CDC tasks bidirectionally. Requires DMS instance management and loopback prevention. PostgreSQL lacks built-in conflict resolution — use table-level write partitioning, application versioning, or accept last-writer-wins at DMS level.
Tradeoffs
Highest availability, lowest latency. Fundamental tradeoff: data correctness during concurrent writes. Good for: session data, shopping carts, preferences, append-only logs. Not suitable for: financial balances, inventory counts, counters — unless wrapped in conflict detection.
Pattern 3b: Read Local, Write Local with Strong Consistency (Arbitration Region)

Problem
Multi-region writes needed with strong consistency. Eventual consistency and last-writer-wins are unacceptable.
Solution
Three regions: two active (reads/writes) plus a witness for consensus. Ensures every committed write is visible to subsequent reads anywhere.
AWS Implementation with Aurora DSQL
Aurora DSQL (GA May 2025) is a serverless distributed SQL database (PostgreSQL 16 subset). Multi-region clusters span three regions: two "linked" with endpoints, one "witness" storing encrypted logs only.
DSQL uses Amazon Time Sync Service and optimistic concurrency control (OCC). Transactions execute optimistically; conflicts at commit time result in errors requiring retry. Multi-region clusters target 99.999% availability.
Limitations:
- Clusters must stay within one geographic group: US, Europe, or Asia Pacific
- PostgreSQL subset only — many extensions/features unavailable
- High-contention workloads see elevated abort rates
- Higher cost than traditional Aurora
Terraform Configuration
1
2
3
4
5
6
7
8
9
10
11
12
13
14
# DSQL cluster in us-east-1 — active region, serves reads and writes
resource "aws_dsql_cluster" "us_east_1" {
deletion_protection_enabled = true
}
# DSQL cluster in us-east-2 — linked to us-east-1, us-west-2 as witness
resource "aws_dsql_cluster" "us_east_2" {
deletion_protection_enabled = true
multi_region_properties {
witness_region = "us-west-2"
clusters = [aws_dsql_cluster.us_east_1.arn]
}
}AWS Implementation with DynamoDB Global Tables (MRSC Mode)
DynamoDB Multi-Region Strong Consistency (MRSC) synchronously replicates to at least one other region before returning success. Requires exactly three regions (full replicas or two plus witness). Limited to specific regional groupings.
MRSC restrictions: no TTL, no LSIs, no transactions. Concurrent modifications raise
ReplicatedWriteConflictException.1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
# DynamoDB Global Table with MRSC — two replicas + one witness
resource "aws_dynamodb_table" "global_ledger" {
name = "financial-ledger"
billing_mode = "PAY_PER_REQUEST"
hash_key = "account_id"
range_key = "transaction_id"
stream_enabled = true
stream_view_type = "NEW_AND_OLD_IMAGES"
attribute {
name = "account_id"
type = "S"
}
attribute {
name = "transaction_id"
type = "S"
}
replica {
region_name = "us-east-2"
consistency_mode = "STRONG"
}
replica {
region_name = "us-west-2"
consistency_mode = "STRONG"
}
}Tradeoffs
Strongest multi-region write guarantees. Every write requires quorum (added latency). Three-region requirement increases cost. Aurora DSQL needs retry logic for OCC conflicts and has PostgreSQL subset compatibility. DynamoDB MRSC sacrifices TTL, LSIs, and transactions. Both represent fundamental architectural commitments.
Decision Framework
Can all writes go to a single region? Pattern 1A is simplest. Need session consistency? Use Pattern 1B.
Does data have a natural partition key mapping to regions? Pattern 2 gives local writes without conflict complexity.
Must every region accept low-latency writes? Eventual consistency OK? Pattern 3a (DynamoDB MREC). Strong consistency required? Pattern 3b (Aurora DSQL or DynamoDB MRSC).
Default: start with simplest pattern (1A or 3a) and evolve when workload demands.
References
- Amazon Aurora Global Database Documentation
- Aurora PostgreSQL Write Forwarding
- Introducing Amazon Aurora DSQL
- Aurora DSQL Multi-Region Cluster Setup
- DynamoDB Global Tables: How It Works
- DynamoDB Global Tables Multi-Region Strong Consistency
- ElastiCache Global Datastore
- AWS DMS Bidirectional Replication
- Deploy Aurora Global Database with Terraform
- Terraform AWS RDS Aurora Module
- Implementing Write Synchronization in Aurora Global Database
Enjoyed reading this content? Let the author know!
Your likes, comments, shares, and saves help creators reach more builders.
Loading recommendations
Loading article