rces data residency rules before the request reaches your compute layer.
// edge-router.ts (Cloudflare Workers / Deno compatible)
import { Router } from 'itty-router';
import { geoip } from '@cloudflare/geoip';
const router = Router();
const REGION_MAP: Record<string, string> = {
'US': 'https://api.us-east-1.myapp.com',
'EU': 'https://api.eu-central-1.myapp.com',
'APAC': 'https://api.ap-southeast-1.myapp.com',
'LATAM': 'https://api.sa-east-1.myapp.com',
};
router.all('*', async (request: Request) => {
const country = geoip.country(request) ?? 'US';
const region = Object.keys(REGION_MAP).find(r =>
['US', 'CA', 'MX'].includes(country) ? r === 'US' :
['DE', 'FR', 'GB', 'NL'].includes(country) ? r === 'EU' :
['JP', 'KR', 'IN', 'ID', 'SG', 'AU'].includes(country) ? r === 'APAC' :
['BR', 'AR', 'CL', 'CO'].includes(country) ? r === 'LATAM' : r === 'US'
) ?? 'US';
const targetUrl = new URL(request.url);
targetUrl.hostname = new URL(REGION_MAP[region]).hostname;
const headers = new Headers(request.headers);
headers.set('X-Target-Region', region);
headers.set('X-Forwarded-For', request.headers.get('CF-Connecting-IP') ?? '');
return fetch(targetUrl.toString(), {
method: request.method,
headers,
body: request.method !== 'GET' ? request.body : undefined,
});
});
export default router;
Rationale: Routing at the edge eliminates cross-region API calls for 85% of traffic. It also creates a single enforcement point for data residency, rate limiting, and regional feature flags before requests hit your backend.
Step 2: Multi-Region Data Partitioning & Read Replication
Global scaling fails when a single primary database becomes the bottleneck. Implement a write-shard/read-replica pattern where each region owns its write database. Use a global read layer for cross-region queries that tolerate eventual consistency. Conflict resolution should follow a “last-write-wins” or vector-clock strategy for user-generated content, with strict partitioning for PII.
// db-router.ts
import { Pool } from 'pg';
const regionPools: Record<string, Pool> = {
'US': new Pool({ connectionString: process.env.DB_US }),
'EU': new Pool({ connectionString: process.env.DB_EU }),
'APAC': new Pool({ connectionString: process.env.DB_APAC }),
'LATAM': new Pool({ connectionString: process.env.DB_LATAM }),
};
export async function getRegionPool(region: string): Promise<Pool> {
const pool = regionPools[region];
if (!pool) throw new Error(`Unsupported region: ${region}`);
return pool;
}
export async function queryWithRegion(region: string, text: string, params?: any[]) {
const pool = await getRegionPool(region);
return pool.query(text, params);
}
Rationale: Write sharding eliminates cross-region write latency and reduces database connection contention. Read replicas serve localized queries with minimal replication lag. PII never crosses regional boundaries, satisfying GDPR, CCPA, and APAC data localization laws by design.
Step 3: Dynamic Asset Delivery & Cache Invalidation
Mobile apps download heavy assets (images, videos, config bundles). Serve these through a multi-tier CDN with edge-computed image optimization and stale-while-revalidate caching. Invalidate caches using content hashes, not time-based TTLs, to prevent stale UI states during regional rollouts.
Rationale: Static assets account for 60-70% of mobile payload size. Edge optimization reduces bandwidth costs by 40% and improves Time to Interactive (TTI). Content-hash invalidation guarantees that regional feature updates propagate without cache stampedes.
Step 4: Feature Rollout & OTA Update Pipeline
App store reviews introduce a 24-72 hour deployment lag. Decouple feature availability from binary distribution using a feature flag service and Over-The-Air (OTA) update mechanisms like CodePush or Expo Updates. Roll out regions sequentially, monitor crash rates, and implement automatic kill switches.
// feature-flag-client.ts
import { launchDarklyClient } from 'launchdarkly-node-server-sdk';
export async function isFeatureEnabled(region: string, featureKey: string, userId: string): Promise<boolean> {
const context = {
kind: 'user',
key: userId,
custom: { region },
};
return launchDarklyClient.variation(featureKey, context, false);
}
Rationale: Feature flags decouple deployment from release. They enable safe, incremental global rollouts, instant rollback capability, and A/B testing across regions without requiring app store resubmission.
Step 5: Observability & Synthetic Monitoring
Real User Monitoring (RUM) is reactive. Deploy synthetic monitors that simulate mobile network conditions (3G, high packet loss, high latency) from target regions. Track P95 latency, error budgets, and cache hit ratios per region. Alert on SLO breaches before user impact compounds.
Rationale: Global scaling requires proactive validation. Synthetic monitoring detects routing misconfigurations, CDN cache misses, and database replication lag before they affect production users.
Pitfall Guide
-
Treating a single-region database as the global source of truth
Cross-region writes introduce 150-300ms latency per transaction. Database connection pools exhaust quickly under global load. Solution: Partition by region, replicate reads, and accept eventual consistency for non-critical data.
-
Ignoring data residency at the API layer
Routing PII to a non-compliant region triggers legal exposure and fines. Solution: Enforce residency at the edge router. Never log or cache cross-border user identifiers.
-
Applying static CDN caching to personalized content
User-specific responses cached at the edge cause data leakage and stale UI states. Solution: Use Vary: Authorization, X-User-Region headers. Cache only public or anonymized payloads.
-
Hardcoding timezone, locale, or currency logic
Mobile clients in APAC/LATAM use non-standard formats. Hardcoded conversions break checkout flows and date pickers. Solution: Resolve locale at the edge, pass standardized ISO codes to the backend, and use server-side i18n libraries.
-
Relying solely on Real User Monitoring (RUM)
RUM data is noisy and delayed. It cannot isolate regional routing failures from client-side bugs. Solution: Pair RUM with synthetic probes from target regions and distributed tracing (OpenTelemetry).
-
Treating app store releases as deployment pipelines
App store review cycles destroy deployment frequency and prevent rapid rollback. Solution: Implement OTA updates for JS/native bundles and feature flags for logic toggles. Reserve app store releases for binary updates and compliance requirements.
-
Overcomplicating cross-region synchronization
Attempting real-time bidirectional sync across regions introduces conflict resolution nightmares and high egress costs. Solution: Use event-driven replication (Kafka/PubSub) for analytics and non-critical data. Keep user state regional.
Production Best Practice: Implement a “regional blast radius” model. Each region should be independently deployable, independently monitorable, and independently rollbackable. Cross-region dependencies should be read-only and eventually consistent.
Production Bundle
Action Checklist
Decision Matrix
| Scenario | Recommended Approach | Why | Cost Impact |
|---|
| Read-heavy social/feed app | Edge CDN + regional read replicas + global read layer | Minimizes write contention, serves localized feeds with low latency | +15% infra, -40% egress |
| Write-heavy fintech/banking | Strict regional write partitioning + async cross-region sync | Ensures compliance, eliminates cross-region write latency | +25% infra, -90% compliance risk |
| Start-up (<100k MAU) | Single region + edge routing + feature flags | Delays partitioning complexity until traffic justifies it | Baseline cost, fast iteration |
| Enterprise (>5M MAU) | Multi-region active-active + event-driven replication | Meets SLOs, enables zero-downtime deployments, satisfies sovereignty | +60% infra, -80% outage cost |
Configuration Template
# docker-compose.global.yml (simplified multi-region stack)
version: '3.9'
services:
edge-router:
image: myapp/edge-router:latest
ports: ["80:80", "443:443"]
environment:
- REGION_MAP={"US":"api-us","EU":"api-eu","APAC":"api-apac","LATAM":"api-latam"}
- ENFORCE_RESIDENCY=true
depends_on: [api-us, api-eu, api-apac, api-latam]
api-us:
image: myapp/api:latest
environment:
- DB_REGION=US
- DB_HOST=db-us
- FEATURE_FLAG_ENDPOINT=https://flags.myapp.com
depends_on: [db-us]
api-eu:
image: myapp/api:latest
environment:
- DB_REGION=EU
- DB_HOST=db-eu
- FEATURE_FLAG_ENDPOINT=https://flags.myapp.com
depends_on: [db-eu]
db-us:
image: postgres:15
environment:
- POSTGRES_DB=myapp_us
volumes: ["pg_us:/var/lib/postgresql/data"]
db-eu:
image: postgres:15
environment:
- POSTGRES_DB=myapp_eu
volumes: ["pg_eu:/var/lib/postgresql/data"]
volumes:
pg_us:
pg_eu:
Quick Start Guide
- Initialize edge routing: Deploy the edge router container or serverless function. Configure
REGION_MAP with your target regions and enable residency enforcement.
- Spin up regional databases: Create separate write databases per region. Configure connection pools and set
DB_REGION environment variables on each API instance.
- Connect feature flags: Register your LaunchDarkly/Unleash endpoint. Implement
isFeatureEnabled in your API middleware to gate regional features.
- Deploy synthetic probes: Run Pingdom/Checkly or custom Playwright scripts from US, EU, APAC, and LATAM locations. Set SLO alerts for P95 latency >120ms and error rate >0.5%.
- Validate with staged rollout: Enable a dummy feature for 1% of APAC traffic via feature flags. Monitor edge router logs, database query times, and CDN cache hit ratios. Expand to 100% once metrics stabilize.