Tecnologias/02 - Bancos de Dados/02 - Não Relacionais/01 - MongoDB6 min
Transações, Replica Set e Durabilidade
Conceito
A pergunta desta nota é uma só: quando o MongoDB responde "ok" para uma escrita, o que exatamente ele garantiu?
A resposta não é fixa — ela é configurável em cada operação, por três parâmetros. Esse
é o ponto que mais diferencia o Mongo de um relacional, onde COMMIT significa uma coisa
só.
O trio que define sua consistência
1. writeConcern — quantos nós confirmaram
| Valor | Significa | Risco |
|---|---|---|
w: 0 |
nem espera resposta | perde escrita sem saber |
w: 1 |
o primário gravou | failover pode reverter a escrita |
w: "majority" |
a maioria do replica set gravou | seguro; é o padrão desde a 5.0 |
j: true |
gravado no journal (disco) | mais lento, sobrevive a queda de energia |
2. readConcern — de qual versão você lê
| Valor | Significa |
|---|---|
local |
o que o nó tem, mesmo não replicado |
majority |
só o que a maioria já confirmou (não pode ser revertido) |
snapshot |
visão consistente de um instante (necessário em transação) |
linearizable |
leitura garantidamente após toda escrita confirmada; caro |
3. readPreference — de qual nó você lê
primary (padrão) · primaryPreferred · secondary · secondaryPreferred · nearest
A regra que evita 80% dos incidentes:
w: "majority"+ ler do primário.readPreference: secondaryé uma otimização de leitura que compra dado atrasado — não é "escalar leitura de graça".
Replica set e failover
- Só o primário aceita escrita. Secundários aplicam o oplog.
- Se o primário cai, os nós elegem outro (protocolo tipo Raft) — tipicamente em ~10 s.
- Escritas com
w: 1que ainda não replicaram podem ser revertidas (rollback) quando o antigo primário voltar como secundário. Comw: "majority", não podem. - Retryable writes (padrão nos drivers modernos): o driver repete automaticamente uma escrita que falhou por troca de primário — o que só é seguro porque a operação carrega um id de transação.
Transações multi-documento
Existem desde a 4.0 (replica set) e 4.2 (sharded cluster), com ACID de verdade e
readConcern: snapshot:
const session = client.startSession()
try {
await session.withTransaction(async () => {
await contas.updateOne({ _id: a }, { $inc: { saldo: -100 } }, { session })
await contas.updateOne({ _id: b }, { $inc: { saldo: 100 } }, { session })
}, { readConcern: { level: "snapshot" }, writeConcern: { w: "majority" } })
} finally {
await session.endSession()
}
withTransaction já embute o retry para TransientTransactionError — o que é
obrigatório, porque conflito de escrita aborta a transação e o app precisa tentar de
novo.
E a operação sobre um único documento é sempre atômica, sem transação nenhuma:
// atômico por definição, mesmo mexendo em 3 campos e num array
db.contas.updateOne(
{ _id: a, saldo: { $gte: 100 } },
{ $inc: { saldo: -100 }, $push: { extrato: { tipo: "debito", valor: 100 } } }
)
Este é o desenho que o MongoDB quer que você use: se o agregado está num documento, a atomicidade é grátis (2. Modelagem de Documentos).
Trade-offs
Transação existe, mas custa e é limitada. Duração padrão de 60 s
(transactionLifetimeLimitSeconds), conflitos abortando sob contenção, custo de
coordenação maior em cluster sharded. A posição oficial da MongoDB é honesta:
transação é uma saída de emergência, não a ferramenta principal.
Se o seu sistema precisa de transação multi-documento em toda operação de escrita, o modelo de dados está relacional — e você provavelmente escolheu o banco errado. Ver 4. MongoDB vs Relacional.
w: "majority" custa latência. Você espera o round-trip até a maioria dos nós. É o
preço de não perder escrita, e é o mesmo trade-off de Availability vs Consistency.
Ler do secundário parece grátis e não é. Você troca latência por staleness. Fluxos "escreve e imediatamente lê" (criar um recurso e devolver ele na resposta) quebram — e quebram de forma intermitente, dependendo do lag. Só use secundário para leitura que tolera atraso: relatório, listagem, busca.
Onde o MongoDB fica no CAP Theorem: com w: majority + readConcern: majority +
leitura no primário, ele é CP — sob partição, o lado minoritário para de aceitar
escrita. Afrouxando os parâmetros, você caminha para AP. É o mesmo sistema em pontos
diferentes do espectro, escolhidos por configuração. Ver Eventual Consistency.
Exemplo prático
O mesmo UPDATE com três garantias diferentes — e o que cada uma custa:
// 1) Telemetria: perder um ponto não importa. Rápido.
db.metricas.insertOne(doc, { writeConcern: { w: 0 } })
// 2) Padrão de aplicação: não perder escrita em failover.
db.pedidos.insertOne(doc, { writeConcern: { w: "majority" } }) // já é o default
// 3) Financeiro: sobreviver a queda de energia no datacenter.
db.lancamentos.insertOne(doc, { writeConcern: { w: "majority", j: true } })
E o padrão que substitui transação na maioria dos casos — idempotência por chave natural:
// Reprocessar a mesma mensagem do Kafka N vezes produz o mesmo resultado.
db.pedidos.updateOne(
{ _id: msg.pedido_id }, // a chave natural do evento
{ $setOnInsert: { criado_em: new Date() },
$set: { status: msg.status, atualizado_em: new Date() } },
{ upsert: true }
)
Uma operação, atômica, sem transação, segura sob reentrega. É a ponte entre esta seção e 4. Garantias de Entrega e Idempotência — e o motivo pelo qual Idempotent Operations é o padrão mais valioso de sistemas distribuídos.
Como isso falha em produção
| Sintoma | Causa |
|---|---|
| Escrita "desapareceu" depois de um failover | w: 1: o primário confirmou e caiu antes de replicar |
Recurso criado não aparece no GET seguinte |
readPreference: secondary com lag |
WriteConflict / TransientTransactionError |
contenção em transação — precisa de retry (use withTransaction) |
| Transação abortando em 60 s | trabalho demais dentro da transação; quebre em partes idempotentes |
| Réplica exigindo initial sync completo | ficou fora mais tempo que a janela do oplog |
| Latência de escrita subiu sem mudar o código | um nó lento faz w: majority esperar por ele |
| Escrita revertida silenciosamente | rollback de oplog do antigo primário |
Se um upgrade de nó leva 4 horas, a janela do oplog precisa cobrir 4 horas de escrita — senão a réplica volta e precisa de sincronização total, justamente no pior momento.
Aprofundamento aplicado
Configurar a jornada de leitura depois de escrita
Escolher um secondary para leitura define preferência de roteamento. Escolher write concern define o que uma confirmação de escrita exige. Read concern define propriedades da visão lida. São controles relacionados, mas nenhum substitui automaticamente os demais.
Cenário: uma atualização confirma e uma tela lê em outro nó. A jornada precisa definir se aceita atraso ou se requer observar a própria escrita, usando as garantias e sessões apropriadas. “Há três réplicas” não responde a essa pergunta.
Falha durante o commit
Um timeout pode deixar o cliente sem saber se a transação confirmou. Os drivers fornecem mecanismos e classificações para retry de transações e commits; a aplicação deve seguir o contrato do driver. Não coloque um efeito externo não idempotente dentro de um callback que pode ser repetido.
Exercício: desenhe pedido, reserva e envio de confirmação. Resultado esperado: alterações que exigem atomicidade ficam na transação; a comunicação externa tem registro durável e identificação para lidar com reexecução.
Referências: transações e consistência causal.
Relacionado
- 1. MongoDB
- 2. Modelagem de Documentos
- 4. MongoDB vs Relacional
- CAP Theorem
- Availability vs Consistency
- Eventual Consistency
- Idempotent Operations
- 6. Transações e ACID
Parte de MOC MongoDB · Transações (MongoDB)