Multi-region active-active
A partir da v4.2, o CipherVault suporta deploy active-active em múltiplas regiões com Postgres logical replication, region-aware routing e CRDTs para metadata cross-region.
Topologia
┌──────────────────────────────────────┐
│ DNS / GeoDNS │
│ cv.acme.com.br → região mais próxima│
└─────────────┬────────────────────────┘
│
┌─────────────────┼─────────────────┐
▼ ▼ ▼
┌──────────┐ ┌──────────┐ ┌──────────┐
│ sa-east-1│ │ us-east-1│ │ eu-west-1│
│ CV+PG │ │ CV+PG │ │ CV+PG │
└────┬─────┘ └────┬─────┘ └────┬─────┘
└───────── PG logical replication ──┘
└────────── CRDT sync (counters) ───┘
Cada região tem seu próprio backend CipherVault + Postgres. Replicação é bidirecional via logical replication (pub/sub).
Componentes
1. tenant_regions — fonte da verdade
Tabela com tenant_id PK, primary_region, replica_regions[]. Cada
operação consulta para decidir se redireciona ou processa local.
# Configurar tenant
curl -X PUT https://cv.acme.com.br/admin/tenant-region/42 \
-H "Authorization: Bearer $CV_TOKEN" \
-d '{
"primary_region": "sa-east-1",
"replica_regions": ["us-east-1", "eu-west-1"]
}'
2. requireRegionForwarding middleware
Aplicado em app.js após auth. Comportamento:
- Lê
tenant_iddo JWT/AppConnection - Consulta
tenant_regions(cache 60s TTL) - Se região atual ≠ primary, retorna 307 com
Locationapontando para a região primária - Loop detection via header
X-CV-Region-Origin— se já bate com região atual, processa local mesmo em mismatch (failover scenario)
Endpoints excluídos (region-local, processados independente):
/auth— login pode acontecer em qualquer região/admin/tenant-region— config global/attestation— local/crdt/sync— peer-to-peer/clusters— federation, region-local/health,/metrics— operacional
3. Postgres logical replication
infrastructure/multi-region/setup-replication.sql configura pub/sub
para 24 tabelas. Excluídas:
| Tabela | Motivo |
|---|---|
audit_logs | CRDT counter cobre métrica agregada cross-region |
dynamic_leases | Region-local (lease só faz sentido na região onde DB upstream está) |
crdt_states | Sync próprio via syncer (não via PG replication) |
4. CRDTs para metadata cross-region
5 tipos completos em lib/crdt/index.js:
| Tipo | Quando usar |
|---|---|
| GCounter | Contadores monotônicos (audit_count, leases_issued_total) |
| PNCounter | Contadores que sobem e descem (active_sessions) |
| GSet | Conjuntos que só crescem (regions_seen) |
| LWWRegister | Última escrita ganha (config flags) |
| ORSet | Conjuntos com add+remove concurrent (tags) |
Background syncer push 5s para peers via CRDT_PEERS env. Cada peer
expõe POST /crdt/sync (auth via X-CRDT-Sync-Token).
Convergência: total cross-region em < 10s.
Failover
Failover de region primária é gated por dual-control (Approvals) porque é mudança de alta criticidade:
# Tenant 42 — primary atual: sa-east-1, replicas: [us-east-1, eu-west-1]
curl -X POST https://cv.acme.com.br/admin/tenant-region/42/promote \
-H "Authorization: Bearer $CV_TOKEN" \
-d '{
"new_primary_region": "us-east-1",
"reason": "DR drill 2026-Q2 + sa-east-1 outage simulada"
}'
# Retorna 202 — aguardando 2 aprovações em /Approvals
Action: tenant_region_promote (ver Approvals).
Após N aprovações (default 2), o promote acontece atomicamente:
UPDATE tenant_regions SET primary_region = 'us-east-1' WHERE tenant_id = 42- Cache regional invalidado em todas regiões via broadcast
- Próximas requests redirecionam para nova primary
- Audit log:
tenant_region_promotedseverity high
Runbook completo: docs/runbooks/REGIONAL_FAILOVER.md no produto.
Métricas
| Métrica | Tipo | Labels |
|---|---|---|
cv_cross_region_forwards_total | counter | from, to, status |
cv_cross_region_forward_duration_seconds | histogram | from, to |
cv_replication_lag_seconds | gauge | replica_application_name |
cv_crdt_sync_total | counter | peer, result |
cv_crdt_state_size_bytes | gauge | tenant_id, name, type |
cv_replication_lag_seconds é populada via pg_stat_replication. Alerta crítico se > 60s.
Configuração
Backend env vars
# Region atual (obrigatório em multi-region)
CV_REGION=sa-east-1
# Endpoints dos peers para CRDT sync (uma URL por peer, separados por vírgula)
CRDT_PEERS=https://cv-us.acme.com.br,https://cv-eu.acme.com.br
# Token de auth entre peers (compartilhado, igual em todas regiões)
CRDT_SYNC_TOKEN=<token aleatório>
# Loop detection — não modificar (header padrão)
# X-CV-Region-Origin é setado automaticamente em forwards
DNS / GeoDNS
Recomendado: Route 53 latency-based routing ou GeoDNS aponta o
hostname cv.acme.com.br para a região mais próxima do client. O
middleware redireciona quando o tenant tem primary diferente.
Alternativa simples: subdomínios por região
(cv-sa., cv-us., cv-eu.) sem GeoDNS, app escolhe explicitamente
qual usar.
Boas práticas
- Postgres logical replication exige
wal_level=logical— ajuste empostgresql.confantes do setup - Monitor
cv_replication_lag_seconds— alerta crítico em > 60s - DR drill trimestral — promote para region replica, valide RPO/RTO, depois reverta
- CRDT_SYNC_TOKEN rotacionar a cada 6 meses
- Excluir SIEM forwarding multi-region — configure SIEM para coletar de todas regiões diretamente, evita duplicação de events
- Tenants region-pinned para LGPD — clientes BR ficam em
sa-east-1apenas se exigência legal de dado em território nacional