Multi-region active-active — análisis y trade-offs¶
Qué cambia cuando el sistema cruza regiones. Tres modelos (active-passive, active-active per-tenant, active-active global) con sus trade-offs en consistencia, latencia, complejidad. Recomendación Phase 5+.
El problema: ley de la física¶
| Round-trip típico | Latency |
|---|---|
| Same rack | 0.1ms |
| Same AZ | 0.5ms |
| Same region cross-AZ | 1-2ms |
| Cross-region (us-east-1 ↔ us-west-2) | 60-80ms |
| Cross-continent (US ↔ EU) | 80-100ms |
| Cross-continent (US ↔ Asia) | 150-200ms |
Cada sync write cross-region agrega round-trip. Si tu engine quiere CP (linearizability), agregás 60-200ms por comando.
Tres modelos de deployment¶
Modelo A: Active-Passive (DR)¶
flowchart TD
subgraph East[Region us-east-1 - primary, all traffic]
EE[Engine cluster]
EP[(Postgres primary)]
end
subgraph West[Region us-west-2 - warm standby]
WE[Engine cluster - idle]
WP[(Postgres replica)]
end
EP -->|async replication WAL| WP
- Lectura/escritura: solo en primary.
- Failover: manual o auto sobre RTO 5-10min.
- RPO: 5s-30s (async replication lag).
- Pros: simple, no consenso cross-region.
- Cons: tráfico de west usuarios cruza el continente.
Esta es la opción default M4. Cubre DR sin complejidad de active-active.
Modelo B: Active-Active per-tenant (sharded)¶
flowchart LR
subgraph East[Region us-east-1]
EE[Engine cluster]
EP[(Postgres primary<br/>E tenants)]
end
subgraph West[Region us-west-2]
WE[Engine cluster]
WP[(Postgres primary<br/>W tenants)]
end
EP <-->|async cross-region replication para DR| WP
- Cada tenant tiene una región home asignada.
- Tenant E_1 escribe solo en east-1. Tenant W_1 solo en west-2.
- Replicación cross-region es solo para DR, no para latency.
- Pros: cada tenant baja latency (cerca de su home region); cada region failover independiente.
- Cons: tenant lock-in a una region; migración entre regions = downtime.
Ideal para casos donde clientes tienen ownership de región (e.g., EU tenant en eu-west-1 por GDPR).
Modelo C: Active-Active global (verdaderamente distribuido)¶
flowchart TD
E1[Engine<br/>us-east-1]
E2[Engine<br/>us-west-2]
E3[Engine<br/>eu-west-1]
GS[(Globally replicated state store<br/>Spanner, CockroachDB, YugabyteDB, ...)]
E1 <--> GS
E2 <--> GS
E3 <--> GS
- Cualquier región puede aceptar cualquier comando.
- State store distribuido garantiza consistencia.
- Pros: ultimate availability, no failover concept.
- Cons:
- Spanner-style requires Google infrastructure o TrueTime.
- CockroachDB / YugabyteDB en Postgres-wire pero con limitaciones.
- Cross-region writes 60-200ms latency.
- Costo 3-5× más.
- Postgres vanilla no soporta esto.
Nuestra decisión: NO ir por C en el roadmap planificado. La complejidad excede el beneficio para la mayoría de casos. Si un cliente lo pide, evaluar caso-por-caso.
Análisis detallado Modelo B (recomendado para multi-region)¶
Sharding por tenant¶
Routing en API gateway:
1. Request arrives with header X-Tenant-ID: acme.
2. Lookup tenant.home_region from cache/DB.
3. Forward to engine cluster en home_region.
4. Si home_region down: 5xx, retry, eventual failover manual.
Esquema:
-- En cada region, tabla replicada
CREATE TABLE tenant_home_region (
tenant_id TEXT PRIMARY KEY,
home_region TEXT NOT NULL,
failover_to TEXT, -- region target en caso de DR
last_updated TIMESTAMPTZ NOT NULL
);
Cache TTL 5min en gateway. Si tenant migra de region, gateway converge en 5min.
Cross-region replication para DR¶
flowchart LR
P[(Postgres primary<br/>us-east-1)]
S[(Postgres standby<br/>us-west-2<br/>only for DR; no reads)]
P -->|async logical replication| S
Tools: pglogical, streaming replication con synchronous_commit=off.
RPO: ~5s. RTO: 5-10min con manual promotion.
Tenant migration (rare event)¶
# Plan: migrar tenant "acme" de us-east-1 a us-west-2
# Procedure (con downtime de 10-30 min para ese tenant):
1. Set tenant.home_region = "us-west-2" (route nuevos requests west)
2. Stop tenant en us-east-1 (workers reject)
3. Snapshot tenant data en east-1 (pg_dump con tenant filter)
4. Restore en us-west-2
5. Reconnect workers a us-west-2
6. Smoke test
7. Cleanup datos en us-east-1 (after retention period)
Automation con wf tenant migrate acme --to us-west-2.
Failures y consistencia¶
Failure scenarios¶
| Escenario | Modelo A (active-passive) | Modelo B (sharded) |
|---|---|---|
| Engine pod crash | Auto-recover (HA en region) | Idem |
| Postgres primary crash | Patroni failover en region | Idem |
| AZ outage | Auto (multi-AZ en region) | Idem |
| Region outage | Manual failover to standby region; RTO 10min | Tenants de esa region migran a backup region; RTO 10min |
| Network partition cross-region | Standby region observes lag; manual decision | Cada region sigue sirviendo sus tenants (sin coord) |
Consistency guarantees¶
Modelo A: - Linearizable dentro de la primary region. - Cross-region replicas: eventual consistency (lag 5-30s).
Modelo B: - Linearizable dentro de la home region del tenant. - No cross-tenant cross-region operations (intencional). - Si un proceso de tenant E llama API en region W (vía REST): rejected (404 with explanation).
What can go wrong en Modelo B?¶
Split-brain por tenant migration race¶
- A=east, B=west. Tenant "x" en transición east → west.
- Cliente envía request al east, en flight la migración termina, request escribe en east.
- west no lo verá (no replicación bidireccional).
Mitigación: período de "fencing" durante migration. Stop east, wait quiescence, then promote west.
DNS / routing layer bug¶
- Gateway cache de tenant→region desactualizada.
- Cliente recibe success en region "wrong"; data escrita ahí.
Mitigación: doble-check en el engine (compara header X-Tenant-Region con su own region). Si mismatch: 421 Misdirected Request, client re-routes.
Tooling necesario para Modelo B¶
- Tenant routing service (gateway-level).
- Cross-region replication monitoring.
- Tenant migration tooling.
- Drills periódicos: simular region outage.
- Cross-region observability dashboard.
¿Por qué no Citus para multi-region?¶
Citus shards across nodes en misma región (low latency).
Citus cross-region: - Latency inflada (5-50ms per query a remote shard). - Replication cross-region es per-shard, complejo. - Distribución no aware de tenancy → puede shardear un tenant entre regions.
Citus = horizontal scaling intra-region. Multi-region = sharding inter-region per tenant. Diferentes problemas.
Ver adrs/adr-021-citus-horizontal-scaling.
Cost analysis multi-region¶
| Modelo | Costo extra vs single-region |
|---|---|
| A (active-passive) | +50% (idle standby) |
| B (sharded per-tenant) | +100% (dos clusters activos full size) |
| C (truly distributed) | +200-400% (Spanner, etc.) + complejidad |
Justificación de Modelo B: si los SLAs requieren <30ms latency global, A no funciona (cross-region request es 60-80ms one-way).
Read replicas cross-region (opt-in)¶
Para use cases read-heavy (e.g., reportes / Operate UI):
Operate consume del replica local (low latency reads). Lag 1-5s, aceptable para UI.
# operate-config.yaml
data_source:
reads:
use_local_replica: true
max_lag_seconds: 30 # if lag > 30s, fail to primary
writes:
always_primary: true # writes (incident resolve, etc.) van al primary
Network topology¶
Gateway hierarchy¶
flowchart TD
GLB[Global LB<br/>DNS-based, e.g., Route 53 geo-routing]
LB1[us-east-1 LB]
LB2[us-west-2 LB]
LB3[eu-west-1 LB]
EC1[Engine cluster]
EC2[Engine cluster]
EC3[Engine cluster]
GLB --> LB1 --> EC1
GLB --> LB2 --> EC2
GLB --> LB3 --> EC3
Cross-region traffic¶
Solo: - Postgres logical replication (async, batched). - Observability aggregation (metrics, traces) → central platform. - Audit log shipping → central platform.
NO traffic: - Engine-to-engine queries cross-region. - Worker-to-engine cross-region (workers en home region del tenant).
VPC peering vs Transit Gateway¶
- VPC peering: barato, simple, no transitive.
- Transit Gateway: scalable, transitive, mas caro.
Para 2-3 regions: VPC peering. Para 5+: Transit Gateway.
Observability cross-region¶
Aggregation¶
Cada region tiene Prometheus / OTel Collector local. Push agregados a: - Thanos / Cortex (Prometheus federation). - Otel Collector central (traces).
Permite dashboards globales.
Métricas clave¶
wf_region_command_rate{region}
wf_region_active_instances{region}
wf_region_replication_lag_seconds{from_region, to_region}
wf_region_tenant_count{region}
wf_cross_region_requests_total{from, to, status} # debería ser ~0 idealmente
Alertas¶
wf_region_replication_lag_seconds > 60→ DR copy lagging.wf_cross_region_requests_total > 0→ routing bug.wf_region_active_instances{region} == 0 for 5m→ region down or no traffic.
Operate / Tasklist cross-region¶
Cada region tiene su own Operate. Usuario admin necesita acceder a múltiples regions: - Opción A: UI por region (multiple bookmarks). - Opción B: Global UI con region selector.
Recomendado: Opción B con backend que routea al region correcto.
flowchart LR
UI[Global Operate UI]
BE[Backend]
API[Operate API en us-east-1]
UI -->|Header X-Selected-Region: us-east-1| BE
BE -->|forwards| API
Decision summary¶
| Need | Recommended model |
|---|---|
| Single region, HA only | M2 (Patroni HA in-region) |
| Multi-region DR only | Modelo A (active-passive) |
| Multi-region latency-sensitive (per-tenant region) | Modelo B (sharded per-tenant) |
| Truly global low-latency | Modelo C (Spanner-class) — not in roadmap |
| Regulatory (data residency) | Modelo B (tenant home = jurisdiction) |
Migration path¶
flowchart TD
M1[M1 - single region, single node]
M2[M2 - single region HA Patroni]
M3[M3 - single region multi-AZ]
M4[M4 - Modelo A: cross-region DR]
M5[M5 - Modelo B: per-tenant sharding - opt-in]
M6[M6 - Modelo C - no planeado, evaluación caso-por-caso]
M1 --> M2 --> M3 --> M4 --> M5 --> M6
Trade-offs explícitos para M5¶
Para tomar la decisión de ir a Modelo B:
Si: - Tenes clientes en regions con latency requirements <100ms. - Compliance forzando data residency (GDPR EU tenants en eu-region). - Engine load justifica scale-out cross-region. - Equipo tiene bandwidth para mantenerlo.
Entonces sí.
Si no, Modelo A es suficiente y mucho más simple.
Anti-patterns¶
❌ Active-active sin coord (no consensus)¶
"Pongo dos engines, ambos escriben a Postgres replicado eventual". Resultado: split-brain, duplicate process instance keys, inconsistencia data. No funciona.
❌ Synchronous replication cross-region¶
Latencia por comando 60-200ms. Throughput cae 100×. Solo viable para datos críticos de bajo volumen (e.g., billing).
❌ "El cliente decide la region"¶
Sin home region clara, cualquier escritura va al servidor más cercano. Sin replicación bidireccional sincronizada, perdés data o duplicás.
❌ Multi-region sin chaos drills¶
Asumir que el failover funcionará "cuando lo necesitemos". Sin drill periódico, casi seguro que NO va a funcionar.
Roadmap¶
- M3: Single-region multi-AZ (no cross-region todavía).
- M4: Modelo A optional (cross-region DR).
- M5: Modelo B optional (per-tenant sharding) — design phase.
- M6+: Re-evaluación de C basado en customer demand.
Referencias¶
- analysis/scaling-strategy-postgres — phases
- adrs/adr-002-postgresql-as-state-store — Postgres como base
- adrs/adr-021-citus-horizontal-scaling — Citus vs multi-region
- analysis/disaster-recovery-runbook — DR procedures
- analysis/compliance-roadmap — GDPR data residency
- Spanner: Google's globally-distributed database
- CockroachDB multi-region