Saltar a contenido

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

  1. Tenant routing service (gateway-level).
  2. Cross-region replication monitoring.
  3. Tenant migration tooling.
  4. Drills periódicos: simular region outage.
  5. 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):

Region us-east-1: Postgres primary
Region us-west-2: Postgres async replica + Operate API local

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