# Design Doc: API de Pagamentos Multi-Região Active-Active

Este documento propõe uma arquitetura active-active multi-região para uma API de pagamentos crítica, endereçando RTO/RPO próximos de zero, replicação de dados com resolução de conflitos determinística e um rollout faseado que minimiza risco operacional. O design parte de princípios reais de engenharia financeira e padrões AWS, com trade-offs explícitos entre consistência, latência e custo.

- URL: https://fernando.moretes.com/studies/design-doc-multiregion-active-active-payments

- Markdown: https://fernando.moretes.com/studies/design-doc-multiregion-active-active-payments/study.md?lang=pt

- Type: Design Doc / RFC

- Company: Payments API (cenário)

- Domain: Resiliência

- Date: 2026-02-01

- Tags: multi-region, active-active, payments, resilience, rto-rpo, conflict-resolution, aws, data-replication

- Reading time: 10 min

---

Uma API de pagamentos que cai por 4 minutos pode custar milhões e destruir confiança regulatória. Este RFC define como construir um sistema que sobrevive à perda total de uma região AWS sem interrupção perceptível — e por que a maioria das abordagens naive falha exatamente no momento em que mais importa.

## O Problema: Resiliência de Pagamentos Não É Só Alta Disponibilidade

A maioria dos sistemas que se autodeclaram 'altamente disponíveis' são, na prática, active-passive com failover manual ou semi-automático. Para uma API de pagamentos, isso é insuficiente por razões que vão além do SLA técnico.

Primeiro, o contexto regulatório. Bancos centrais e esquemas de cartão (Visa, Mastercard) exigem RTO mensurável em segundos, não minutos. No Brasil, o Banco Central monitora a disponibilidade do PIX com granularidade de minutos e aplica multas a participantes que ultrapassam thresholds de indisponibilidade. Isso transforma resiliência de uma decisão de engenharia em uma obrigação de compliance.

Segundo, a natureza dos dados. Pagamentos não são operações idempotentes simples. Uma transferência envolve débito de uma conta, crédito em outra, registro em ledger, notificações e, frequentemente, integração com sistemas externos (clearing houses, bancos correspondentes). Se uma região falha no meio de uma saga distribuída, o estado pode ficar inconsistente de formas que são difíceis de detectar e perigosas de corrigir automaticamente.

Terceiro, o problema de split-brain. Em um sistema active-active genuíno, duas regiões podem receber requisições concorrentes para o mesmo recurso — por exemplo, dois saques simultâneos da mesma conta processados em regiões diferentes antes que a replicação propague o saldo atualizado. Sem um mecanismo de resolução de conflitos robusto, o sistema pode autorizar transações que deveriam ser negadas, resultando em fraude ou overdraft.

Este documento não propõe 'alta disponibilidade'. Propõe um sistema que mantém corretude transacional mesmo sob falha de região, com latência adicionada mínima no caminho feliz e comportamento degradado previsível no caminho de falha.

## Ficha do Sistema

- **Sistema:** API de Pagamentos (cenário composto)
- **Volume estimado:** 5.000–15.000 TPS no pico
- **Regiões AWS alvo:** us-east-1 (primária), sa-east-1 (secundária), us-west-2 (terciária)
- **RTO alvo:** < 30 segundos para failover automático de região
- **RPO alvo:** < 1 segundo (replicação síncrona para transações financeiras)
- **Stack principal:** Amazon Aurora Global Database, DynamoDB Global Tables, Amazon Route 53 ARC, API Gateway, EKS, SQS, EventBridge
- **Domínio regulatório:** Pagamentos instantâneos (PIX), cartões, transferências internacionais
- **Disponibilidade alvo:** 99,995% (~26 minutos de downtime/ano)

## Objetivos e Não-Objetivos

- ✅ OBJETIVO: Sobreviver à perda total de qualquer região AWS sem intervenção manual e sem perda de transações confirmadas
- ✅ OBJETIVO: Garantir que cada transação financeira seja processada exatamente uma vez (exactly-once semantics) mesmo sob falhas de rede entre regiões
- ✅ OBJETIVO: Latência P99 < 200ms para autorização de pagamento no caminho feliz (sem failover ativo)
- ✅ OBJETIVO: Resolução de conflitos determinística e auditável para escritas concorrentes entre regiões
- ✅ OBJETIVO: Rollout faseado com capacidade de rollback em cada fase sem impacto ao cliente
- ❌ NÃO-OBJETIVO: Replicação para regiões fora da AWS (on-premises ou outros clouds)

## Design Proposto: Arquitetura e Decisões Fundamentais

O design parte de uma premissa que considero inegociável para sistemas financeiros: **a corretude precede a disponibilidade**. Isso significa que prefiro rejeitar uma transação com um erro claro a processá-la de forma incorreta. Com isso estabelecido, o design se organiza em três camadas.

**Camada 1: Roteamento e Failover (Route 53 ARC + Global Accelerator)**

O Amazon Route 53 Application Recovery Controller (ARC) é o ponto de controle central para failover. Ele mantém readiness checks contínuos para cada célula de recuperação (uma por região) e permite failover programático com um único API call. O Global Accelerator roteia tráfego para a região mais próxima com base em latência de rede, com health checks em nível de endpoint a cada 10 segundos.

A decisão crítica aqui é **não usar DNS-based failover puro**. TTLs de DNS, mesmo com valores baixos (60s), combinados com caches de resolvedores intermediários, tornam o failover imprevisível em termos de tempo. O Global Accelerator opera na camada de rede (Anycast), o que elimina esse problema e permite failover em segundos.

**Camada 2: Processamento de Transações (EKS + Saga Pattern)**

Cada região executa um cluster EKS com os microserviços de pagamento. O padrão Saga Coreografada é usado para transações que envolvem múltiplos serviços, com eventos publicados no Amazon EventBridge. Cada etapa da saga é idempotente e registra seu estado em DynamoDB Global Tables com conditional writes.

A idempotência é implementada via **idempotency key** no header de cada requisição (UUID v4 gerado pelo cliente). O serviço de autorização verifica a existência da chave em DynamoDB antes de processar — se já existe, retorna o resultado anterior. Isso garante exactly-once mesmo quando o cliente faz retry após timeout.

**Camada 3: Persistência e Replicação (Aurora Global + DynamoDB Global Tables)**

O ledger financeiro (saldos, transações confirmadas) vive no Amazon Aurora Global Database com uma instância writer em us-east-1 e readers em sa-east-1 e us-west-2. O lag de replicação típico do Aurora Global é < 1 segundo, o que atende ao RPO definido.

O estado operacional de curto prazo (idempotency keys, locks distribuídos, sessões) vive em DynamoDB Global Tables, que usa replicação assíncrona multi-master. Aqui está o trade-off central: DynamoDB Global Tables usa **last-writer-wins (LWW) baseado em timestamp** como política de resolução de conflitos padrão. Para idempotency keys isso é aceitável — o primeiro write vence na prática porque o TTL da chave é longo o suficiente. Para saldos, **não é aceitável** — por isso saldos ficam no Aurora, não no DynamoDB.

Para o caso de failover do Aurora (promoção de reader para writer), o Route 53 ARC coordena a sequência: (1) parar escritas na região primária, (2) aguardar confirmação de replicação zero-lag, (3) promover reader, (4) atualizar endpoints. Esse processo leva tipicamente 60–90 segundos, o que está dentro do RTO de 30 segundos apenas se o passo 2 for eliminado — o que requer aceitar potencial perda de dados sub-segundo. Minha recomendação é manter o RTO real em 90 segundos para failover de banco e ser transparente sobre isso com stakeholders.

## Arquitetura Active-Active Multi-Região

Fluxo de uma transação de pagamento em operação normal e comportamento durante failover de região. Cada região é uma célula de recuperação independente capaz de processar transações completas.

### 🌐 Global Layer

- Client Mobile/Web (user)
- Global Accelerator Anycast routing (network)
- Route 53 ARC Readiness + Failover (network)
- AWS WAF DDoS + Rate limit (security)

### 🇺🇸 us-east-1 (Primary Writer)

- API Gateway REST + mTLS (frontend)
- EKS Cluster Payment Services (compute)
- Authorization Service (compute)
- Saga Orchestrator EventBridge Pipes (messaging)
- Aurora Global Writer (Primary) (data)
- DynamoDB Global Tables (data)
- SQS FIFO Dead Letter Queue (messaging)

### 🇧🇷 sa-east-1 (Secondary)

- API Gateway REST + mTLS (frontend)
- EKS Cluster Payment Services (compute)
- Authorization Service (compute)
- Aurora Global Reader → Writer* (data)
- DynamoDB Global Tables (data)

### 🔁 Replication & Control

- Aurora Replication < 1s lag (data)
- DynamoDB Replication Async multi-master (data)
- CloudWatch Alarms + Dashboards (security)

### Fluxos

- client -> ga: HTTPS
- ga -> waf: Anycast
- waf -> apigw1: Normal
- waf -> apigw2: Failover
- arc -> ga: Controla roteamento
- apigw1 -> eks1
- eks1 -> auth1
- auth1 -> dynamo1: Idempotency check
- auth1 -> aurora1: Write (ledger)
- eks1 -> saga1
- saga1 -> sqs1: DLQ on failure
- aurora1 -> rep_aurora
- rep_aurora -> aurora2
- dynamo1 -> rep_dynamo
- rep_dynamo -> dynamo2
- apigw2 -> eks2
- eks2 -> auth2
- auth2 -> dynamo2
- auth2 -> aurora2
- cloudwatch -> arc: Trigger failover

## Resolução de Conflitos: O Problema Mais Difícil

Conflitos em sistemas de pagamento active-active não são teóricos — eles acontecem toda vez que há latência de replicação e tráfego concorrente. A questão não é se vão ocorrer, mas como o sistema se comporta quando ocorrem.

**Cenário de conflito típico:** Usuário tem saldo de R$ 1.000. Faz dois saques de R$ 800 quase simultaneamente — um processado em us-east-1, outro em sa-east-1, antes que a replicação propague o saldo atualizado. Sem proteção, ambos são autorizados, resultando em saldo negativo de R$ 600.

**Nossa abordagem: Reserva de Saldo com Lease Distribuído**

Para transações que envolvem saldo (saques, transferências de débito), implementamos um mecanismo de **lease distribuído** usando DynamoDB com conditional writes. Antes de processar, o serviço de autorização tenta adquirir um lease exclusivo para a conta com TTL de 5 segundos. O conditional write garante que apenas uma região adquire o lease por vez.

```
ConditionExpression: attribute_not_exists(lease_owner) OR lease_expires < :now
UpdateExpression: SET lease_owner = :region, lease_expires = :now + 5s, version = version + 1
```

Se o lease não pode ser adquirido (outra região está processando), a requisição retorna HTTP 409 com `Retry-After: 1`. O cliente faz retry, e na segunda tentativa o lease anterior já expirou ou foi liberado.

**Por que não usar Two-Phase Commit (2PC)?**

2PC garante atomicidade distribuída, mas introduz um coordenador de transação que se torna ponto único de falha e adiciona latência proporcional ao número de participantes. Para uma API de pagamentos com SLA de 200ms P99, 2PC cross-região é inviável — o round-trip entre us-east-1 e sa-east-1 sozinho é ~120ms.

**Por que não usar Paxos/Raft?**

Protocolos de consenso como Raft garantem consistência forte, mas requerem quorum de maioria para cada write. Com três regiões, isso significa que cada transação precisa de confirmação de pelo menos 2 regiões — novamente, latência inaceitável para o caminho feliz.

**A solução pragmática:** Separar o domínio de dados pelo padrão de acesso. Dados que requerem consistência forte (saldos, ledger) ficam no Aurora com um único writer por vez. Dados que toleram eventual consistency (idempotency keys, eventos de audit) ficam no DynamoDB. O lease distribuído via DynamoDB é o mecanismo de coordenação — ele é eventual, mas o TTL curto (5s) limita a janela de conflito a um intervalo aceitável para o negócio.

## Alternativas de Arquitetura Avaliadas

### Active-Passive com Aurora Multi-AZ

**Pros**
- Sem conflitos de escrita — único writer sempre
- Modelo operacional simples e bem compreendido
- Custo significativamente menor

**Cons**
- RTO de 2–5 minutos para failover de região (inaceitável para PIX)
- Região secundária não serve tráfego — capacidade desperdiçada
- Failover manual ou semi-automático com risco de erro humano

**Verdict:** Rejeitado: RTO incompatível com requisitos regulatórios

### Active-Active com CockroachDB (multi-região)

**Pros**
- Consistência serializável global nativa
- Sem necessidade de lógica de resolução de conflitos na aplicação
- SQL familiar com semântica ACID distribuída

**Cons**
- Latência de escrita proporcional ao quorum: ~120–200ms cross-região
- Não é serviço gerenciado AWS — aumenta overhead operacional
- Custo de licença e operação significativamente maior

**Verdict:** Rejeitado: Latência de escrita viola SLA de 200ms P99

### Active-Active com Aurora Global + DynamoDB (proposto)

**Pros**
- Replicação Aurora < 1s atende RPO financeiro
- DynamoDB Global Tables para estado operacional com latência de milissegundos
- Route 53 ARC + Global Accelerator para failover em segundos
- Serviços gerenciados reduzem overhead operacional

**Cons**
- Complexidade de lógica de lease distribuído na aplicação
- Aurora ainda tem único writer — promoção leva 60–90s
- Custo de replicação cross-região (egress + storage)

**Verdict:** Aceito: Melhor equilíbrio entre consistência, latência e operabilidade

### Active-Active com Event Sourcing + CQRS Global

**Pros**
- Audit trail completo e imutável por design
- Resolução de conflitos via reordenação de eventos (causal ordering)
- Escalabilidade de leitura excelente com projeções regionais

**Cons**
- Complexidade de implementação muito alta — curva de aprendizado longa
- Eventual consistency para leituras de saldo é problemático para UX
- Requer reescrita completa do modelo de domínio

**Verdict:** Considerado para versão futura — complexidade excessiva para fase inicial

## Decisão: Estratégia de Replicação de Dados

**Status:** accepted

**Contexto**

Precisamos de RPO < 1s para dados financeiros, mas consistência forte global implica latência de escrita inaceitável. A decisão é sobre como segmentar os dados entre os mecanismos de persistência.

**Decisão**

Dados de ledger (saldos, transações confirmadas) → Aurora Global Database com único writer. Dados operacionais (idempotency keys, leases, sessões) → DynamoDB Global Tables com LWW. Eventos de auditoria → Kinesis Data Streams com replicação cross-região via S3 Replication.

**Consequências**
- Aurora writer é ponto único de falha para escritas — failover leva 60–90s, não 30s
- DynamoDB LWW é aceitável para idempotency keys porque o primeiro write é o que importa e TTL é longo
- Custo adicional estimado de 35–45% vs. arquitetura single-region (estimativa baseada em preços públicos AWS)

## Plano de Rollout Faseado

1. **Fase 0 — Fundação (Semanas 1–4)** — Provisionar infraestrutura multi-região via IaC (Terraform). Configurar Aurora Global Database com replicação para sa-east-1. Configurar DynamoDB Global Tables. Implementar Route 53 ARC com readiness checks. Nenhum tráfego de produção nas novas regiões. Validar RPO via testes de injeção de falha (AWS FIS) em ambiente staging.

2. **Fase 1 — Leitura Multi-Região (Semanas 5–8)** — Direcionar 100% das leituras (consultas de saldo, histórico) para a região mais próxima do cliente usando Aurora readers regionais. Escritas ainda vão 100% para us-east-1. Monitorar lag de replicação e consistência de leitura. KPI: lag P99 < 500ms. Rollback: remover routing de leitura regional, voltar para us-east-1.

3. **Fase 2 — Escrita Canary Multi-Região (Semanas 9–14)** — Habilitar escritas em sa-east-1 para 5% do tráfego de clientes brasileiros (seleção por feature flag no API Gateway). Ativar mecanismo de lease distribuído. Monitorar conflitos, latência de lease acquisition e taxa de 409. Aumentar gradualmente para 25%, 50%, 100% com gates de qualidade em cada etapa. Rollback granular por feature flag sem impacto ao cliente.

4. **Fase 3 — Failover Automático (Semanas 15–18)** — Ativar failover automático via Route 53 ARC baseado em CloudWatch alarms (error rate > 1% por 60s, latência P99 > 500ms por 120s). Realizar game day: simular falha total de us-east-1 em horário de baixo tráfego. Medir RTO real. Documentar runbook de failover e recuperação. Treinar equipe de plantão (on-call).

5. **Fase 4 — Terceira Região e Steady State (Semanas 19–24)** — Adicionar us-west-2 como terceira célula de recuperação. Implementar circuit breaker regional no API Gateway. Realizar chaos engineering mensal com AWS FIS. Revisar e ajustar thresholds de failover com base em dados reais. Publicar SLA externo de 99,995% após 30 dias de operação estável.

> **Riscos Críticos e Mitigações:** **Risco 1: Clock Skew entre Regiões**
DynamoDB LWW usa timestamps do servidor. Se houver skew de relógio entre regiões (possível mesmo com NTP), o 'último write' pode não ser o cronologicamente mais recente. Mitigação: usar versioning lógico (número de versão monotônico) em vez de timestamps para leases críticos. Monitorar clock drift via CloudWatch.

**Risco 2: Cascata de Retry durante Failover**
Quando uma região falha, todos os clientes fazem retry simultaneamente para a região sobrevivente, podendo causar thundering herd. Mitigação: implementar exponential backoff com jitter no cliente, circuit breaker no API Gateway com rate limiting por conta, e auto-scaling pré-aquecido na região secundária.

**Risco 3: Lease Orphan por Crash de Instância**
Se a instância que adquiriu o lease crashar antes de liberá-lo, o lease fica preso até o TTL expirar (5s). Durante esses 5s, a conta fica bloqueada para novas transações. Mitigação: TTL de 5s é aceitável para o negócio; implementar heartbeat de lease para transações longas; monitorar taxa de lease expiration.

**Risco 4: Divergência de Schema durante Deploy**
Deploys rolling em múltiplas regiões podem ter versões diferentes do schema de banco rodando simultaneamente. Mitigação: usar migrations backward-compatible obrigatórias (never remove columns, only add); feature flags para novos campos; testes de compatibilidade cross-versão no pipeline de CI.

**Risco 5: Custo de Egress Inesperado**
Replicação cross-região gera custo de egress que pode ser significativo em alto volume. Estimativa: a 10.000 TPS com payload médio de 2KB, o egr

## Avaliação AWS Well-Architected

- **security**: mTLS obrigatório entre serviços; KMS com CMK por região para dados em repouso; IAM roles com least privilege por microserviço; VPC com private subnets e PrivateLink para serviços AWS; WAF com regras de rate limiting por conta de pagamento
- **reliability**: Route 53 ARC para failover automático; Aurora Global com RPO < 1s; DynamoDB Global Tables multi-master; circuit breakers regionais; chaos engineering mensal com AWS FIS
- **performance**: Global Accelerator para roteamento de baixa latência; leituras regionais via Aurora readers; DynamoDB para estado operacional com latência de milissegundos; auto-scaling baseado em métricas de negócio (TPS) não apenas CPU
- **sustainability**: Consolidação de workloads em instâncias Graviton3 (ARM) para melhor eficiência energética; auto-scaling agressivo para reduzir capacidade ociosa; regiões com mix de energia renovável priorizadas quando possível

## Métricas de Sucesso e Targets

- **Disponibilidade:** ≥ 99,995% medido mensalmente
- **RTO (failover de região):** < 90 segundos (roteamento: < 30s; promoção Aurora: < 90s)
- **RPO:** < 1 segundo para dados de ledger
- **Latência P99 (autorização):** < 200ms no caminho feliz
- **Taxa de conflitos de escrita:** < 0,01% das transações (409 responses)
- **Lag de replicação Aurora P99:** < 1 segundo em operação normal
- **Taxa de sucesso de failover automatizado:** 100% em game days (sem intervenção manual)
- **Custo adicional vs. single-region (estimativa):** 35–45% (baseado em preços públicos AWS, sem negociação)

> **Minha Perspectiva: O Que Eu Faria Diferente:** Depois de 16 anos construindo sistemas financeiros, a lição mais cara que aprendi é que **arquiteturas active-active são vendidas como solução de disponibilidade, mas o problema real é de consistência**. A maioria dos times subestima o segundo e superestima o primeiro.

O design proposto aqui é deliberadamente conservador em um ponto: manter o Aurora com único writer. Sei que isso parece contradizer o 'active-active', mas para dados financeiros, prefiro chamar de 'active-active no roteamento, active-passive no ledger'. Isso não é fraqueza de design — é honestidade sobre onde a consistência é inegociável.

Se eu pudesse mudar uma coisa neste design, seria **investir mais cedo em observabilidade de replicação**. Não apenas métricas de lag, mas rastreamento causal de transações cross-região: dado um payment_id, conseguir reconstruir em qual região cada etapa foi processada, com qual versão de dados, e qual foi o caminho de replicação. Isso é o que salva você às 3h da manhã durante um incidente.

Sobre o rollout: a Fase 2 (escrita canary) é onde a maioria dos projetos falha. Times tendem a acelerar o rollout quando os primeiros 5% parecem estáveis. Minha recomendação é manter cada gate por pelo menos uma semana de tráfego de pico, não apenas dias. Pagamentos têm padrões sazonais (fim de mês, datas comemorativas) que só aparecem com tempo suficiente de observação.

Finalmente, o ponto que raramente aparece em documentos de arquitetura: **treine sua equipe de on-call para o failover antes de precisar dele**. O runbook mais bem escrito falha quando a pessoa executando está sob pre

## Veredicto

Este design é tecnicamente viável e operacionalmente responsável para uma API de pagamentos com requisitos financeiros-grade. A combinação de Aurora Global Database, DynamoDB Global Tables e Route 53 ARC representa o estado da arte em resiliência multi-região na AWS, com trade-offs explícitos e gerenciáveis.

O ponto crítico que qualquer engenheiro deve entender antes de implementar: **você não está eliminando falhas, está escolhendo como falhar**. Este design escolhe falhar de forma detectável (409 em conflito de lease, degradação de latência durante failover de Aurora) em vez de falhar silenciosamente (overdraft, duplicação de transação). Para sistemas financeiros, essa escolha é a correta.

O custo adicional de 35–45% vs. single-region é real e deve ser justificado com dados de negócio: qual é o custo de 4 minutos de downtime em termos de transações perdidas, multas regulatórias e dano de reputação? Para a maioria dos processadores de pagamento em escala, a resposta torna o investimento óbvio. Para sistemas menores, uma arquitetura active-passive bem executada com RTO de 2 minutos pode ser a escolha economicamente correta.

O rollout faseado de 24 semanas pode parecer conservado

## Referências

- [AWS Architecture Center](https://aws.amazon.com/architecture/)
- [Amazon Aurora Global Database — Documentation](https://docs.aws.amazon.com/AmazonRDS/latest/AuroraUserGuide/aurora-global-database.html)
- [DynamoDB Global Tables — Documentation](https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/GlobalTables.html)
- [Route 53 Application Recovery Controller](https://docs.aws.amazon.com/r53recovery/latest/dg/what-is-route-53-recovery.html)
- [AWS Global Accelerator — Documentation](https://docs.aws.amazon.com/global-accelerator/latest/dg/what-is-global-accelerator.html)
- [Building Multi-Region Active-Active Architecture — AWS Blog](https://aws.amazon.com/blogs/architecture/disaster-recovery-dr-architecture-on-aws-part-iv-multi-site-active-active/)
- [AWS Fault Injection Simulator — Documentation](https://docs.aws.amazon.com/fis/latest/userguide/what-is.html)
- [Saga Pattern for Distributed Transactions — AWS Prescriptive Guidance](https://docs.aws.amazon.com/prescriptive-guidance/latest/modernization-data-persistence/saga-pattern.html)

## Fontes do caso

- [AWS Architecture Center](https://aws.amazon.com/architecture/)
