Skip to main content

Multi-Region Design

Multi-Region Shopping Mall implements an Active-Active multi-region architecture based on the Write-Primary/Read-Local pattern. This document explains why this pattern was chosen, how it works, and the consistency model in detail.

Region Role Assignment

RegionRoleResponsibility
us-east-1PrimaryAll write operations, global data master
us-west-2SecondaryRead operations, write forwarding, promotable on failure

Why Active-Active?

Comparison: Active-Passive vs Active-Active

AspectActive-PassiveActive-Active
Resource UtilizationSecondary idleBoth regions utilized
Read LatencySingle region dependentProcessed in nearest region
Failover TimeWait for DNS propagation (minutes)Immediate (already serving traffic)
Cost EfficiencyLow (standby resources)High (load balanced during normal operation)
Implementation ComplexityLowHigh (data consistency management)

Reasons for Choosing Active-Active

  1. 99.99% Availability Goal: Service continuity even with single region failure
  2. Global User Experience: Responses from the region closest to users
  3. Cost Optimization: Utilizing resources in both regions during normal operation
  4. Gradual Failover: Smooth transition since traffic is already distributed

Write-Primary / Read-Local Pattern

Pattern Overview

Read Path (Local Read)

Read Path Characteristics:

  • Processed in the region closest to the user
  • ElastiCache checked first to minimize latency
  • Reads from Aurora/DocumentDB local replica
  • Average response time: 30-50ms

Write Path (Forward to Primary)

Write Path Characteristics:

  • Write requests received at Secondary region are forwarded to Primary
  • Transaction processed at Primary, then response returned
  • Events published to MSK Kafka
  • Data asynchronously replicated to Secondary
  • Average response time: 150-200ms

Write Forwarding Mechanism

Service-Level Implementation

// Go service example (Order Service)
func (h *OrderHandler) CreateOrder(c *gin.Context) {
region := os.Getenv("AWS_REGION")
primaryRegion := os.Getenv("PRIMARY_REGION") // "us-east-1"

if region != primaryRegion {
// Forward to Primary if in Secondary region
resp, err := h.forwardToPrimary(c.Request)
if err != nil {
c.JSON(500, gin.H{"error": "Primary region unavailable"})
return
}
c.Data(resp.StatusCode, "application/json", resp.Body)
return
}

// Process directly in Primary region
order, err := h.orderService.Create(c.Request.Context(), orderRequest)
// ...
}
// Java service example (Payment Service)
@Service
public class PaymentService {

@Value("${aws.region}")
private String currentRegion;

@Value("${primary.region}")
private String primaryRegion;

public PaymentResponse processPayment(PaymentRequest request) {
if (!currentRegion.equals(primaryRegion)) {
return forwardToPrimary(request);
}

// Process directly in Primary region
return executePayment(request);
}
}

Aurora Global Database Write Forwarding

Aurora Global Database natively supports Write Forwarding.

-- Executed in Secondary region
-- Aurora automatically forwards to Primary
INSERT INTO orders (user_id, total_amount, status)
VALUES ('user-123', 150000, 'PENDING');

-- Verify Write Forwarding is enabled
SELECT * FROM aurora_global_db_status();
# Terraform configuration
resource "aws_rds_cluster" "secondary" {
# ...
enable_global_write_forwarding = true
}

Consistency Model

Consistency Strategy by Data Type

Consistency LevelApplied ToReasonAllowed Replication Lag
StrongOrders, Payments, Inventory, AccountsFinancial transactions, duplicate prevention required0 (synchronous)
EventualProduct Catalog, Search, Recommendations, ReviewsSlight delay acceptable, read performance priority1-2 seconds
SessionCart, Wishlist, SessionsPer-user isolation, immediate reflection neededN/A (cache)

Strong Consistency Implementation

Financial transactions must be processed in the Primary region.

Eventual Consistency Implementation

Catalog and search data are processed with eventual consistency.

Read-After-Write Consistency

Ensuring consistency when a user reads their own data immediately after writing.

Implementation Strategy

# Python service example
class OrderService:
def get_order(self, order_id: str, user_id: str) -> Order:
# 1. Check local cache
cached = self.cache.get(f"order:{order_id}")
if cached:
return cached

# 2. Check for recent write (Session sticky)
recent_write = self.cache.get(f"recent_write:{user_id}:{order_id}")

if recent_write:
# Read from Primary (strong consistency)
return self.read_from_primary(order_id)
else:
# Read from local replica (performance priority)
return self.read_from_local(order_id)

Region Failover

Automatic Failover Conditions

Failover Scenarios

Failure TypeImpact ScopeAuto/ManualExpected Recovery Time
Single AZ failureServices in that AZAutomatic (EKS)30 seconds
EKS cluster failureRegional servicesAutomatic (Route 53)1 minute
Aurora Primary failureWrite operationsAutomatic (Aurora)1-2 minutes
Full region failureAll servicesManual (promotion required)5-10 minutes

Next Steps