A Highly Available Three-Tier Application on AWS: Multi-Region Design with Aurora DSQL
Listen to this article
AI-narrated listening version - 10 min 06 sec. Code is explained; complete examples remain below.
Designing a three-tier AWS application that survives an Availability Zone or Region failure. Explore the architecture diagram, service choices, database consistency, identity recovery, and the trade-offs behind each decision.
TL;DR
A highly available three-tier AWS application can combine CloudFront and private S3 buckets for presentation, Global Accelerator and ECS Fargate for regional application services, and Aurora DSQL for strongly consistent writes across two Regions. Each application Region spans three Availability Zones and maintains failover capacity. A third Region acts as the DSQL witness, rather than another application deployment. Cognito multi-Region replication improves identity recovery but has feature limitations. This design explains regional routing, transaction retries, deployment safety, backup recovery, and the distinction between database availability and end-to-end application reliability.
Start with the user journey, then choose the services
A three-tier AWS application separates presentation, business logic, and persistent data. To keep that application useful during a Region failure, every dependency in the user journey needs a recovery strategy: serving the page, signing in, executing a request, and finding the committed result afterward.
Consider a greenfield order-management application. Users browse orders, submit new ones, and check their status. The front end is a static single-page application; the API runs in containers; the database needs SQL transactions and consistent reads across Regions. Payment processing and large file uploads are separate integrations with their own recovery contracts.
The proposed architecture uses two application Regions: Ireland (eu-west-1) and London (eu-west-2). Paris (eu-west-3) is the Aurora DSQL witness. This is a supported European DSQL Region combination. London is in the UK: this placement is not an EU-only data-residency design. Confirm jurisdiction requirements for application data, identity data, logs, and witness transaction logs before choosing locations. DSQL Region availability
This reference design targets continued order access for already-authorized users and existing users whose authentication method supports identity failover. It does not promise that every account-administration operation survives every failure.
Service behavior was checked against AWS documentation on 4 October 2026. “Latest” here means relevant, generally available capabilities, including DSQL's newer relational features and Cognito multi-Region replication. It does not mean adding every recently launched service. Start by checking the DSQL compatibility guide against your application before committing to this design.
The AWS architecture diagram
Solid arrows show request paths. Dashed connections show replication or coordination. The witness does not serve application requests. Aurora DSQL sits outside the customer VPC; PrivateLink provides the private connection into the managed service. Identity is a separate dependency with its own failover path.
View the full-size architecture diagram.
The three logical tiers remain distinct even though they use several managed services:
| Tier | Components | Availability strategy |
|---|---|---|
| Presentation | Route 53, CloudFront, WAF, two private S3 buckets | Edge caching and S3 origin failover; deploy the same assets to both Regions |
| Application | Global Accelerator, regional internal ALBs, regional WAF, ECS Fargate | Both Regions serve traffic; each spans three AZs and retains failover capacity |
| Data | Two peered Aurora DSQL clusters and a third-Region witness | Strongly consistent regional read/write endpoints; managed recovery |
Cognito, IAM, KMS, ECR, CloudWatch, and AWS Backup support these tiers. They are not additional business tiers.
Tier 1: deliver the front end without depending on the API Region
Point app.example.com through a Route 53 alias to CloudFront. Use S3 REST origins, keep both buckets private, and configure Origin Access Control for each origin. Block public access. The bucket policies permit the intended CloudFront distribution rather than anonymous website access. OAC is incompatible with S3 website endpoints, which have different security behavior. CloudFront origin access
Configure an origin group with Ireland primary and London secondary. Select appropriate connection-failure and server-error conditions, and tune timeout behavior through tests. CloudFront origin failover handles GET, HEAD, and OPTIONS; it does not provide generic failover for POST or PUT. That makes it suitable for immutable JavaScript, CSS, and HTML delivery, while the API uses its own routing path. CloudFront origin failover
The deployment pipeline copies the same content-hashed assets to both buckets before updating either Region's entry HTML. For example, in a CI job with regional deployment permissions:
# dist/assets contains content-hashed filenames. Retain older release assets.
aws s3 sync dist/assets/ "s3://${WEB_BUCKET_IRELAND}/assets/" \
--region eu-west-1 --cache-control 'public,max-age=31536000,immutable'
aws s3 sync dist/assets/ "s3://${WEB_BUCKET_LONDON}/assets/" \
--region eu-west-2 --cache-control 'public,max-age=31536000,immutable'
# Run after verifying both Regions contain every asset referenced by index.html.
aws s3 cp dist/index.html "s3://${WEB_BUCKET_IRELAND}/index.html" \
--region eu-west-1 --cache-control 'no-cache'
aws s3 cp dist/index.html "s3://${WEB_BUCKET_LONDON}/index.html" \
--region eu-west-2 --cache-control 'no-cache'
These commands are a release fragment, not a complete deployment pipeline. Add manifest verification, rollback, security headers, and an SPA route-rewrite strategy. Deploying assets directly into both Regions avoids making a new release's availability depend on asynchronous bucket replication catching up.
Why these components? S3 removes web-server maintenance for static content. CloudFront keeps repeated reads near users and reduces origin load. Route 53 supplies durable names without becoming the per-request application router. WAF filters malicious HTTP requests at the presentation boundary. Server-side rendering would require an additional resilient rendering service; it is outside this static-front-end example.
Tier 2: route API requests to two independent application deployments
Point api.example.com through Route 53 to a standard AWS Global Accelerator. Its regional endpoint groups contain the Ireland and London ALBs. Both serve traffic; Global Accelerator selects endpoints using health, client location, and configured routing controls. Traffic dials are controls for regional participation, not a promise of an exact global 50/50 split. New connections can move after failure detection; existing connections and interrupted requests are not replayed. Global Accelerator routing
Use an internal ALB in each VPC, with subnets across three AZs. Global Accelerator can reach internal ALBs without giving them public IPs. Its VPC still needs an attached internet gateway, although the private ALB subnets do not need an internet-gateway route. Attach a regional WAF web ACL to each ALB; Global Accelerator itself is not an HTTP firewall or cache. Private Global Accelerator endpoints
Configure HTTPS listeners, regional ACM certificates, IP target groups, security groups, and a bounded readiness endpoint. Keep Fargate tasks in private subnets with no public IP assignment. Permit application ingress only from the ALB security group. Separate database access from application ingress.
ECS Fargate suits this workload because it runs ordinary containers without EC2 host patching or a Kubernetes control plane. EKS is reasonable when Kubernetes APIs, operators, or platform standardization justify its additional operations. Lambda is another option for short event-driven requests, but this example deliberately keeps a container service with controlled connection pooling and request concurrency.
Enable ECS Availability Zone rebalancing explicitly and maintain tasks in all three AZs. It is a recovery mechanism, not a substitute for spare capacity. A service fragment using rolling deployments might look like this:
# Fragment: referenced VPC, task definition, listener and target group already exist.
ApiService:
Type: AWS::ECS::Service
DependsOn: ApiHttpsListener
Properties:
Cluster: !Ref ApiCluster
TaskDefinition: !Ref ApiTaskDefinition
LaunchType: FARGATE
DesiredCount: 9 # Illustrative; derive your minimum from a load test.
AvailabilityZoneRebalancing: ENABLED
DeploymentConfiguration:
MinimumHealthyPercent: 100
MaximumPercent: 200
DeploymentCircuitBreaker:
Enable: true
Rollback: true
NetworkConfiguration:
AwsvpcConfiguration:
AssignPublicIp: DISABLED
SecurityGroups: [!Ref ApiTaskSecurityGroup]
Subnets: [!Ref PrivateSubnetA, !Ref PrivateSubnetB, !Ref PrivateSubnetC]
LoadBalancers:
- ContainerName: api
ContainerPort: 8080
TargetGroupArn: !Ref ApiTargetGroup
Create this regional service twice through infrastructure as code. Add target-tracking scaling, request timeouts, graceful shutdown, connection draining, and quotas for surge tasks. The fragment follows current ECS CloudFormation properties and AZ rebalancing behavior. Validate the finished stack rather than treating the fragment as a deployable template.
The browser calls a separate API hostname, so allow only the intended front-end origin through CORS. Validate access-token signature, issuer, expiration, client binding and required scopes in both Regions. Store durable state in the database, not task memory. Removing a task must not remove a session, order, or idempotency record.
Reserve capacity before the outage
Assume a hypothetical global peak of 400 requests/second. Suppose a representative load test establishes 100 requests/second per task, and you choose a safe operating budget of 70 per task. To handle the entire global peak after losing one of three evenly populated AZs:
Required tasks per Region >= ceil(global_peak / (safe_task_rate × 2/3))
>= ceil(400 / (70 × 2/3))
>= 9
After one AZ is lost: 6 tasks × 70 requests/second = 420 requests/second.
These numbers are a sizing example, not an AWS benchmark. Validate the same scenario against database contention, connection rates, downstream limits, memory, and tail latency. Autoscaling cannot manufacture instantaneous capacity during a regional evacuation.
Tier 3: choose Aurora DSQL for the required write semantics
Aurora DSQL gives this greenfield application two regional endpoints that accept reads and writes against strongly consistent logical data. DSQL manages its infrastructure and AZ resilience. The Paris witness stores a limited window of encrypted transaction logs for coordination and durability; it has no application endpoint and is not a third full database copy. Pair the two clusters using the documented multi-Region configuration. DSQL resilience
Why DSQL? The requirement is to accept orders from either healthy application Region without promoting a new database writer. This makes active-active database behavior useful. Cross-Region coordination still costs latency: a nearby endpoint does not guarantee local-only commit latency. Benchmark the actual transaction shape across the selected geography.
AWS documents 99.999% multi-Region DSQL availability. That database property is not a five-nines promise for this whole application. Authentication, request routing, capacity, bad releases, client behavior, and external systems also influence the user journey. DSQL overview
PostgreSQL compatibility is a design gate
DSQL uses PostgreSQL-compatible interfaces, but it is not interchangeable with every PostgreSQL deployment. Current limits include one built-in postgres database, fixed Repeatable Read isolation, and restrictions on temporary tables, triggers, and procedural PL/pgSQL functionality. DDL and DML require separate transactions. Migration considerations
Conversely, older advice that DSQL lacks foreign keys is outdated: AWS added foreign-key constraints in August 2026. Check current syntax and workload costs rather than repeating preview-era restrictions. Foreign-key release
A minimal order record can use the client's operation identifier as its primary key:
-- Apply schema changes separately from application transactions.
CREATE TABLE orders (
request_id UUID PRIMARY KEY,
customer_id UUID NOT NULL,
payload_hash VARCHAR(64) NOT NULL,
total_cents BIGINT NOT NULL,
status VARCHAR(32) NOT NULL
);
Validate amounts and authorization in the application. If the same operation identifier appears again, read the committed record, check ownership and payload hash, and return its original result. Reject reuse with a different payload. A client timeout does not prove that its transaction failed.
Connect privately and authenticate with the task role
Provision the DSQL connection interface endpoint in multiple AZs, enable private DNS, and restrict its security group to the task security group on port 5432. The management API endpoint alone does not provide SQL connectivity. Discover the connection service identifier using DSQL's get-vpc-endpoint-service-name operation and use its documented private hostname. DSQL PrivateLink
For a container with a least-privilege ECS task role, a connection factory can use IAM authentication:
python -m pip install boto3 'psycopg[binary]'
import os
import boto3
import psycopg
region = os.environ["AWS_REGION"]
cluster_id = os.environ["DSQL_CLUSTER_ID"]
service_id = os.environ["DSQL_CONNECTION_SERVICE_ID"]
host = f"{cluster_id}.{service_id}.{region}.on.aws"
dsql = boto3.client("dsql", region_name=region)
def open_connection():
token = dsql.generate_db_connect_auth_token(
Hostname=host, Region=region, ExpiresIn=900
)
return psycopg.connect(
host=host, port=5432, dbname="postgres",
user=os.environ["DSQL_DATABASE_ROLE"], password=token,
sslmode="verify-full",
sslrootcert=os.environ["DSQL_CA_BUNDLE"],
connect_timeout=5,
)
The CA bundle must exist in the image and trust the server certificate. The role needs dsql:DbConnect and the corresponding database-role authorization; reserve admin privileges for provisioning. Use this factory when establishing pooled connections, not on every HTTP request. In the private-DNS configuration, sign and connect to the same documented hostname. Token generation
Tokens authenticate new connections; expiry does not terminate existing sessions. DSQL nevertheless has connection-lifetime and creation-rate limits. Recycle pools with jitter before the documented 60-minute connection limit, and avoid synchronized reconnection storms against the 100-new-connections-per-second limit. Keep transactions within the current five-minute, 3,000-mutated-row and 10 MiB change limits. Authentication, DSQL quotas
Retry the transaction, preserve the business operation
DSQL uses optimistic concurrency control. A conflicting transaction can fail at commit with SQLSTATE 40001; retry the whole transaction with a bounded budget and jitter. Strongly consistent reads do not make snapshot-isolated transactions serializable: explicitly protect business invariants, such as inventory limits, and test concurrent updates. This example assumes a configured psycopg connection pool:
import random
import time
from psycopg.errors import SerializationFailure
def run_transaction(pool, work):
for attempt in range(5):
try:
with pool.connection() as connection:
with connection.transaction():
return work(connection)
except SerializationFailure:
if attempt == 4:
raise
time.sleep(random.uniform(0, min(0.05 * 2**attempt, 0.5)))
work must contain database operations only. It must reuse the same business operation identifier across retries. Add an overall request deadline and cancellation handling in the real API. Do not retry all exceptions indiscriminately, and do not charge a payment card inside this block. Network errors around commit require reconciling the original operation before attempting it again. DSQL concurrency control
For asynchronous work, write an outbox row in the same transaction, then let workers deliver it with deduplication. Regional queues, event buses, and payment providers have separate failure semantics; adding SQS does not create a globally replicated queue or exactly-once external side effects.
Identity also needs regional recovery
Cognito multi-Region replication, launched in June 2026, provides a primary user pool and a standby replica. It requires an eligible pool on Essentials or Plus and a KMS multi-Region customer-managed key. This design puts the primary in Ireland and its standby in London. Directory replication is eventually consistent; user administration is not active-active. Cognito release
Configure and activate the replica, its regional dependencies, and managed-login domain failover. The domain's routing configuration specifies the secondary Region and a Route 53 health check. Applications using regional SDK authentication endpoints must select the healthy endpoint themselves. The API's Global Accelerator does not automatically fail over Cognito. Cognito domain routing
Use Cognito's updated issuer configuration before launching this greenfield app. Both API deployments validate the same issuer and appropriate keys using an OIDC/JWT library. Updated issuers currently do not work with ALB's native authenticate-cognito action or API Gateway's native Cognito authorizers; therefore the ECS application performs token validation. Cache discovery and signing keys safely and handle rotation. Issuer compatibility
During identity failover, disable unsupported account operations in the UI. Secondary pools cannot register new users, reset passwords, or update profiles. TOTP MFA is unsupported there, and previously unseen federated users cannot sign in. Recently changed identity data may also lag. If those journeys must remain available during a Region outage, this identity choice needs revision before approving the architecture. Existing valid tokens can support authorized API requests independently of a new login, subject to token validity and policy. Cognito replication limitations
Why each supporting component was chosen
| Component | Rationale | Trade-off or operating obligation |
|---|---|---|
| IAM task roles and SQL roles | Short-lived credentials and explicit application privileges | Test least-privilege grants in both Regions; distinguish task and execution roles |
| AWS KMS | Managed encryption keys; multi-Region CMK where Cognito requires it | Regional key policies and grants still need configuration; a key is not a replicated application |
| ECR regional repositories | Each Region can start tasks from a local artifact | Configure replication and verify the exact image digest exists before release |
| VPC endpoints | Private access to supported dependencies without mandatory public internet traversal | Endpoint ENIs, security groups, DNS, and costs need regional design |
| Regional NAT Gateway, when needed | Managed multi-AZ outbound internet access without maintaining separate zonal NAT resources | Verify expansion into every workload AZ before relying on it; multi-AZ billing still applies |
| CloudWatch and distributed tracing | Observe actual orders, errors, saturation, and dependency latency | Telemetry must work in both Regions; dashboards do not fix failures |
| AWS Backup | Recover from deletion or corruption that live replication would reproduce | Full recovery points, restore time, new cluster endpoints, and restore drills |
| Infrastructure as code | Repeat regional networks, services, policies and configuration | Validate service differences and quotas; avoid making a failover require a new deployment |
ECR replication helps distribute artifacts, but it is not a release-readiness guarantee. Promote an immutable digest only after checking both regional repositories. ECR replication
For private tasks, plan endpoints for ECR APIs and image pulls, S3 artifact access, CloudWatch Logs, and any chosen secret service. Fargate image pulls need the relevant ECR endpoints and S3 access, not just the ECR API endpoint. Private artifact access
If the API calls an internet-hosted payment or identity service, use Regional NAT Gateway in each application VPC. This newer availability mode expands across AZs based on workload presence, simplifying egress management. Expansion into a new AZ can take up to 60 minutes: establish and verify coverage across all three AZs before accepting production traffic. If you choose conventional zonal NAT gateways instead, use one per workload AZ with local routes. A single shared zonal gateway would introduce an avoidable AZ dependency. Test egress failure and include NAT's multi-AZ costs in the budget. Regional NAT behavior
Failure behavior: what happens when something breaks?
| Failure | Expected response | What to verify |
|---|---|---|
| One Fargate task | ALB removes the unhealthy target; ECS replaces it | Draining, retry behavior, idempotency and replacement capacity |
| One application AZ | Remaining targets serve traffic; ECS rebalances when capacity permits | Reserved two-AZ capacity, endpoints and egress in surviving AZs |
| One application Region | Global Accelerator steers new connections to healthy endpoints | Health detection, full regional capacity, interrupted requests and client backoff |
| One DSQL peer Region | Surviving endpoint continues according to managed DSQL recovery behavior | Commit outcomes, contention, pool reconnect and regional app dependencies |
| Witness Region alone | DSQL documents continued cluster availability | Do not mistake the witness for a third serving database |
| Primary S3 origin | CloudFront can use the secondary for eligible requests | Both copies of all referenced assets, correct origin permissions and timeouts |
| Primary Cognito Region | Supported authentication shifts through configured identity failover | Existing users, issuer/JWKS access, method limits and degraded account operations |
| Bad release or accidental delete | Stop rollout; roll back code or restore an appropriate backup | Replication also propagates bad data; restore is a separate recovery workflow |
An important routing caveat: Global Accelerator fails open when no healthy eligible endpoints remain. Health checks are not a security boundary or a guarantee that invalid requests stop arriving. Return explicit application errors and preserve authorization even during failures. Avoid coupling both Regions' readiness to one briefly unhealthy shared dependency. Unhealthy endpoint routing
A successful process-health probe only proves a process answered. Use bounded regional readiness and independent synthetic transactions to measure whether users can actually authenticate, create an order, and read it. Run canaries against each Region as well as the public hostname; otherwise global routing can conceal an unhealthy regional deployment.
Backups, recovery objectives, and safe deployment
Define an initial 99.95% successful-order SLO as a business target, not an observed result. Over a 30-day window that permits 21.6 minutes of unavailable time. Define separate latency and authentication SLOs and specify excluded journeys explicitly.
For one Region's infrastructure loss, design for preserving committed DSQL transactions. An acknowledged database commit and an HTTP success response are different boundaries; reconcile ambiguous outcomes with the operation identifier. Measure recovery time from user-visible interruption through routing, retries, identity recovery and successful completion. Do not infer an end-to-end RTO from database availability alone.
For accidental deletion, choose a distinct backup RPO: for example, a proposed hourly recovery-point policy has different loss exposure from live regional resilience. AWS Backup currently takes full DSQL backups. A paired-cluster restore uses the original recovery point and an identical copied recovery point in a supported second Region, creating new clusters. Reconfigure endpoints and validate permissions and connectivity before switching traffic. Do not describe this as DSQL point-in-time recovery. Backup behavior, Paired restore
Deploy one application Region at a time. Verify the new revision under synthetic and real traffic before rolling out the other. Use backward-compatible schema changes because both Regions share database state; a regional code rollback cannot undo a destructive shared migration. Confirm local artifacts, permissions, certificates and quotas before permitting traffic. Track transaction-conflict rates and tail latency alongside HTTP errors.
Before production approval, run these drills:
- Drain one AZ and load the remaining two at the intended failover rate.
- Withdraw one API Region and measure complete user journeys, including retries.
- Submit the same operation through both Regions and verify one committed outcome.
- Interrupt a connection near commit and prove reconciliation prevents duplicate work.
- Exercise Cognito failover for every authentication method you actually offer.
- Restore a backup into isolated resources and validate data and application behavior.
- Roll back one Region while the other runs the newer schema-compatible revision.
For related operational depth, see the DevOpsDreams guides on Terraform module design, Terraform state and backups, and SRE error budgets. Use the AWS restore procedure as an exercised runbook rather than a document left until an incident.
When a different database or routing choice makes more sense
Choose Aurora PostgreSQL Global Database when an existing application's SQL features, extensions or operational tooling outweigh active-active writes. It has a primary writer and asynchronous cross-Region replication. Unplanned failover can lose changes corresponding to replication lag; planned switchover can synchronize first. That is a different recovery contract from this DSQL design. Global Database recovery
Consider DynamoDB when access patterns naturally fit key-based operations and its selected multi-Region consistency mode meets the business contract. Do not migrate relational joins and transaction logic just to add another managed service. Compare databases against access patterns, consistency, compatibility, geography and cost.
Route 53 health-aware DNS routing can replace Global Accelerator for a simpler API entry point when resolver caching and client failover behavior are acceptable. This design pays for Global Accelerator to obtain an independent global network entry point and regional routing controls. It keeps CloudFront for cacheable content, where its behavior fits.
Cost review should include failover headroom, Fargate tasks, two ALBs, Global Accelerator, WAF, data transfer, DSQL usage, Cognito replication, endpoint ENIs, NAT where needed, observability retention and backup copies. Regional resilience increases the minimum operating footprint. If a slower recovery is acceptable, warm standby may fit the business better than running both application Regions at full recovery capacity.
Frequently asked questions
What are the three tiers in a highly available AWS application?
The presentation tier serves the interface, the application tier executes business logic, and the data tier stores authoritative state. Here they use CloudFront and S3, ECS Fargate behind ALBs, and Aurora DSQL. Cross-tier dependencies still need explicit recovery behavior.
Can both Regions write to the database at the same time?
Yes. The paired Aurora DSQL endpoints support strongly consistent reads and writes against shared logical data. That does not eliminate transaction conflicts, cross-Region latency, or the need to reconcile unknown commit outcomes.
Does this architecture guarantee zero downtime?
No. It removes several regional failure dependencies but cannot eliminate health-detection delays, interrupted requests, bad deployments or every identity limitation. Establish SLOs and measure complete user journeys in failure drills.
When should I choose Aurora Global Database instead?
Choose it when conventional PostgreSQL or MySQL compatibility and migration requirements matter more than independent regional write endpoints. Design for its primary writer, replication lag, and explicit regional failover rather than assuming DSQL semantics.
Will every Cognito operation work during regional failover?
No. Secondary authentication has limits, including unavailable sign-up, password reset and profile updates, and unsupported TOTP authentication. Verify the current feature matrix and revise the identity design if those journeys require the same availability target as order access.
Comments
Post a Comment