Padrões de sincronização de estado entre serviços distribuídos

1. Fundamentos da sincronização de estado em sistemas distribuídos

Em arquiteturas de microsserviços, o estado compartilhado refere-se a dados que precisam ser acessados ou modificados por múltiplos serviços independentes. A sincronização de estado é o processo de garantir que todos os serviços tenham uma visão consistente desses dados, mesmo diante de falhas de rede, latência e concorrência.

Os desafios fundamentais incluem:
- Latência de rede: Atrasos na comunicação entre serviços podem levar a discrepâncias temporárias de estado.
- Falhas parciais: Um serviço pode falhar enquanto outros continuam operando, criando inconsistências.
- Concorrência: Múltiplos serviços podem tentar modificar o mesmo estado simultaneamente, gerando conflitos.

O Teorema CAP estabelece que sistemas distribuídos não podem garantir simultaneamente Consistência, Disponibilidade e Tolerância a Partições. Na prática, é necessário escolher dois desses três atributos, influenciando diretamente o padrão de sincronização adotado.

2. Padrão de Sincronização baseado em Eventos (Event-Driven)

No padrão orientado a eventos, serviços publicam eventos de estado em um barramento de mensagens (ex: Apache Kafka, RabbitMQ) e outros serviços consomem esses eventos para atualizar seu estado local.

Exemplo de evento de atualização de estoque:

Evento: "EstoqueAtualizado"
{
  "produtoId": "12345",
  "quantidadeAnterior": 50,
  "quantidadeNova": 45,
  "timestamp": "2025-04-10T14:30:00Z",
  "origem": "servico-pedidos"
}

Para garantir consistência, é essencial implementar:
- Idempotência: Processar o mesmo evento múltiplas vezes deve produzir o mesmo resultado.
- Ordenação: Usar chaves de partição e timestamps para processar eventos na sequência correta.
- Compensação: Eventos de rollback para desfazer alterações em caso de falha.

3. Sincronização via Log de Transações (Change Data Capture)

Change Data Capture (CDC) utiliza logs de transação do banco de dados (ex: PostgreSQL WAL, MySQL binlog) como fonte única de verdade para propagar mudanças para serviços downstream. Ferramentas como Debezium capturam essas mudanças e as publicam como eventos.

Exemplo de evento CDC capturado do PostgreSQL:

Evento CDC: "LinhaAtualizada"
{
  "schema": "public",
  "tabela": "clientes",
  "operacao": "UPDATE",
  "dadosAntigos": {
    "id": 1001,
    "saldo": 5000.00
  },
  "dadosNovos": {
    "id": 1001,
    "saldo": 4800.00
  },
  "timestamp": "2025-04-10T15:00:00Z"
}

Para lidar com inconsistências temporárias, serviços downstream devem implementar reconciliação periódica, comparando o estado local com a fonte primária e corrigindo divergências.

4. Sincronização baseada em Leases e Bloqueios Distribuídos

Bloqueios distribuídos (ex: ZooKeeper, Redis Redlock) permitem que apenas um serviço por vez modifique um recurso compartilhado. O padrão de Lease concede acesso exclusivo por um período limitado, renovável.

Exemplo de lease usando Redis:

# Serviço A adquire lease para editar documento
SET documento:12345:lock "servico-a" NX EX 30

# Se sucesso, serviço A tem 30 segundos exclusivos
# Para renovar:
EXPIRE documento:12345:lock 30

# Após conclusão:
DEL documento:12345:lock

Riscos incluem:
- Deadlock: Se um serviço falha sem liberar o lock.
- Partição de rede: Serviço perde conectividade mas ainda mantém lease.
- Timeouts inadequados: Leases muito longos ou muito curtos.

5. Consistência Eventual com CQRS e Event Sourcing

O padrão CQRS (Command Query Responsibility Segregation) separa operações de escrita (comandos) e leitura (consultas), permitindo escalabilidade independente. Event Sourcing armazena o estado como uma sequência imutável de eventos, que pode ser reproduzida para reconstruir o estado atual.

Exemplo de fluxo CQRS com Event Sourcing:

# Comando recebido
Comando: "AdicionarItemAoCarrinho"
{
  "carrinhoId": "carrinho-789",
  "produtoId": "prod-456",
  "quantidade": 2
}

# Evento gerado e armazenado
Evento: "ItemAdicionadoAoCarrinho"
{
  "eventoId": "evt-001",
  "carrinhoId": "carrinho-789",
  "produtoId": "prod-456",
  "quantidade": 2,
  "versao": 5
}

# Projeção de leitura atualizada assincronamente
ProjecaoAtualizada: "CarrinhoVisualizacao"
{
  "carrinhoId": "carrinho-789",
  "itens": [
    {"produtoId": "prod-456", "quantidade": 2, "preco": 29.90}
  ],
  "total": 59.80
}

Projeções de leitura podem ficar desatualizadas temporariamente. Mecanismos de atualização assíncrona e versões ajudam a mitigar esse problema.

6. Padrões de Replicação de Estado: Gossip Protocol e CRDTs

O Gossip Protocol (usado em Cassandra, Dynamo) dissemina estado entre nós de forma epidêmica: cada nó periodicamente compartilha informações com alguns outros nós, até que todos estejam atualizados.

CRDTs (Conflict-free Replicated Data Types) permitem que múltiplos nós modifiquem dados concorrentemente sem coordenação centralizada, garantindo convergência automática.

Exemplo de CRDT contador (G-Counter):

# Nó A incrementa seu contador local
ContadorA = { "a": 3, "b": 0, "c": 0 }

# Nó B incrementa seu contador local  
ContadorB = { "a": 0, "b": 5, "c": 0 }

# Após merge, estado convergente:
ContadorMerge = { "a": 3, "b": 5, "c": 0 }
Total = 3 + 5 + 0 = 8

Vantagens incluem ausência de coordenação centralizada, mas limitações existem para tipos de dados complexos.

7. Estratégias de Consistência Forte com Consenso Distribuído

Algoritmos de consenso como Raft e Paxos garantem linearizabilidade, onde operações parecem ocorrer instantaneamente em uma ordem global. Sistemas como etcd e Consul implementam esses algoritmos para eleição de líder e replicação de estado.

Exemplo de operação em etcd:

# Escrever chave com garantia de consistência forte
etcdctl put /config/limite-maximo 1000

# Ler chave (garantido ler o valor mais recente)
etcdctl get /config/limite-maximo
# Saída: /config/limite-maximo : 1000

Trade-offs incluem menor desempenho em cenários de alta latência e indisponibilidade durante partições de rede.

8. Monitoramento, Testes e Mitigação de Inconsistências

Ferramentas de verificação incluem diff checks periódicos entre serviços e reconciliação programada. Testes de caos (ex: Chaos Monkey) simulam falhas de rede e latência para validar resiliência.

Estratégias de rollback e compensação:

# Compensação para pedido cancelado
Evento: "PedidoCancelado"
{
  "pedidoId": "ped-999",
  "motivo": "pagamento_recusado"
}

# Serviço de estoque reage:
# - Restaura quantidade de produtos reservados
# - Publica evento "EstoqueRestaurado"
Evento: "EstoqueRestaurado"
{
  "pedidoId": "ped-999",
  "produtos": [
    {"produtoId": "prod-456", "quantidade": 2}
  ]
}

Testes de consistência devem incluir cenários de falha parcial, concorrência extrema e recuperação após queda de serviço.

Referências