Replication — CDC Postgres → Mongo/MySQL
A partir da v4.5, o CipherVault suporta Change Data Capture (CDC) replication do Postgres primário para sinks externos (MongoDB / MySQL). Use case: dashboards analíticos / data lakes / sistemas legados que precisam dos eventos do CV em tempo quase-real sem expor Postgres diretamente.
Replication ≠ Multi-region. Multi-region usa Postgres logical replication entre instâncias CV. Replication aqui é para sinks externos (não-CV).
Architecture (Opção B — External CDC)
Após análise de 3 opções (A: logical decoding, B: external CDC, C: WAL-shipping), foi escolhida B:
┌─────────────┐ poll ┌─────────────────┐ write ┌──────────┐
│ Postgres CV │ ───────────▶ │ CDC connector │ ─────────────▶ │ MongoDB │
│ (primary) │ `cdc_state` │ background │ │ (sink) │
│ │ table │ job no CV │ └──────────┘
└─────────────┘ └─────────────────┘ ┌──────────┐
│ MySQL │
│ (sink) │
└──────────┘
Trade-off escolhido: simplicidade operacional (zero binding C nativo) ao custo de latência ~5-30s (vs. ms de logical decoding).
Phase 1 — CDC core (entregue)
Background job poll cdc_state (cursor por sink) → busca rows em 4
tabelas whitelistadas:
| Tabela | Replicada | Comentário |
|---|---|---|
secrets | ✅ (redacted) | Sem value (apenas metadata + hash) |
vaults | ✅ | Completa |
audit_logs | ✅ | Completa |
risk_scores | ✅ | Completa |
Não replicadas: AppConnections (cert privado), dynamic_leases
(region-local), fortress_* (segregação), eaas_*/pki_*/ssh_*
(chaves privadas).
Phase 2 — Failover state machine (entregue)
Sinks podem ser promotidos se primary cair (DR scenario):
┌────────┐ promote ┌──────────┐
│ mirror │ ─────────▶ │ promoted │
└────────┘ └────┬─────┘
▲ │ demote (após primary recovery)
│ ▼
└─────────────── ┌──────────┐
│ demoted │
└──────────┘
Split-brain auto-detect: se 2 sinks reportam promoted simultaneamente,
flag de alerta crítico + audit log severity=critical.
5 endpoints admin:
POST /replication/sinks Criar sink
GET /replication/sinks Listar
PATCH /replication/sinks/:id Update config
POST /replication/sinks/:id/promote Promote (dual-control)
POST /replication/sinks/:id/demote Demote (dual-control)
Promote/demote gated por dual-control (Approvals
action replication_state_change).
Phase 3 — Storage abstraction (PoC, deferred)
Foundation only. AuditLogRepository interface + Postgres/MongoDB
impls. Refactor full (200 call sites no backend) requer 6-10
sprints — postergado para v5+.
Por enquanto, replication funciona com Postgres como source of truth universal; sinks são read-only mirrors.
Configuração
Criar sink MongoDB
curl -X POST https://cv.acme.com.br/replication/sinks \
-H "Authorization: Bearer $CV_TOKEN" \
-d '{
"name": "analytics-mongo",
"type": "mongodb",
"connection": {
"uri": "mongodb://cv_replicator:***@mongo-analytics.acme.com.br:27017/ciphervault",
"database": "ciphervault",
"tls": true
},
"tables": ["secrets", "vaults", "audit_logs", "risk_scores"],
"poll_interval_seconds": 30,
"batch_size": 1000,
"state": "mirror"
}'
Sink MySQL
{
"type": "mysql",
"connection": {
"host": "mysql-analytics.acme.com.br",
"port": 3306,
"user": "cv_replicator",
"password_secret": "internal/db/mysql-replicator",
"database": "ciphervault",
"ssl_ca": "<PEM>"
},
...
}
Monitoring
| Métrica | Tipo | Labels |
|---|---|---|
cv_replication_lag_seconds | gauge | sink_id, sink_name, table |
cv_replication_rows_replicated_total | counter | sink_id, table, status |
cv_replication_state | gauge | sink_id (0=mirror, 1=promoted, -1=demoted) |
cv_replication_split_brain_detected | counter | sink_id |
Alerta crítico: cv_replication_lag_seconds > 300 (5min).
Recovery após falha primary
# 1. Confirmar primary realmente down (não bug de monitoramento)
curl --max-time 5 https://cv.acme.com.br/health
# 2. Promote sink
curl -X POST https://cv.acme.com.br/replication/sinks/12/promote \
-d '{ "reason": "Primary down 2026-05-12T14:00 — DR drill / incidente real" }'
# → 202, aguardando dual-control
# 3. Após approvals, sink vira source of truth read+write
# Apps continuam apontando para o endpoint normal (CV-frontend)
# que agora roteia via sink promoted
# 4. Quando primary volta:
curl -X POST https://cv.acme.com.br/replication/sinks/12/demote \
-d '{ "reason": "Primary recovered" }'
Limitações conhecidas
- Latência 5-30s entre escrita no primary e disponibilidade no sink (poll-based, não streaming)
- Sinks são read-only em estado
mirror— writes vão direto para primary - Sem suporte a schema migration cross-version — sink precisa ter mesma estrutura de tabelas do primary (esquema versionado)
- Phase 3 storage abstraction — sem ela, apps que querem usar sink como primary precisam adaptar (não há driver "magicamente" usando sink)
Boas práticas
- Comece com
audit_logsapenas — tabela mais útil para analytics, sem PII relevante - TLS obrigatório para conexão CV → sink
- Credenciais de replicator com permissão mínima (
INSERT,UPDATE,DELETEapenas) - Backup do sink também — não substitui backup do primary mas adiciona camada
- Alerta em
split_brain_detected— incidente que exige resposta imediata
Referências
docs/REPLICATION.mdno repo do produto — design doc completo- Postgres logical replication para Multi-region (caso diferente)