Tecnologias/02 - Bancos de Dados/02 - Não Relacionais/01 - MongoDB6 min
WiredTiger e Armazenamento
Conceito
WiredTiger é o motor de armazenamento do MongoDB desde a 3.2, e ele é o responsável por quase tudo que hoje se considera "o MongoDB é rápido/lento": concorrência, compressão, uso de memória e durabilidade.
Concorrência em nível de documento
Antes do WiredTiger, o MongoDB travava a coleção inteira numa escrita (e, antes disso, o banco). Hoje:
Duas escritas em documentos diferentes da mesma coleção acontecem em paralelo.
O mecanismo é MVCC por snapshot: cada operação vê uma visão consistente do instante em
que começou. Escritas conflitantes no mesmo documento geram WriteConflict, e o servidor
repete a operação automaticamente (para operações simples) ou devolve
TransientTransactionError para você repetir (em transação — ver
3. Transações, Replica Set e Durabilidade).
Cache: o parâmetro que decide tudo
wiredTigerCacheSizeGB = max(50% da (RAM - 1 GB), 256 MB) ← padrão
Diferente do InnoDB (que quer 70% da RAM), o WiredTiger usa 50% e conta com o page cache do SO para a outra metade — porque os dados em disco estão comprimidos e no cache do WiredTiger estão descomprimidos. As duas camadas guardam formatos diferentes, e as duas são úteis.
A regra que resume a operação de MongoDB:
O working set (dados quentes + índices) precisa caber na RAM.
Índice que não cabe na RAM significa ler índice do disco a cada consulta — e aí o MongoDB, que é rápido por natureza, fica lento por configuração.
// o tamanho dos índices é a métrica de capacidade mais importante
db.pedidos.stats().totalIndexSize
db.stats().indexSize // total do banco: compare com a RAM disponível
Eviction: os limiares que ninguém conhece e todos sofrem
Quando o cache enche, threads de eviction liberam páginas. Há três marcas:
| Limiar | Padrão | O que acontece |
|---|---|---|
eviction_target |
80% | eviction em background começa |
eviction_trigger |
95% | as threads da aplicação passam a fazer eviction — latência dispara |
eviction_dirty_trigger |
20% de sujo | idem, para páginas sujas |
O sintoma clássico é: tudo bem por horas, e de repente a latência p99 multiplica por 20
sem mudança de carga. Causa: o cache atravessou o eviction_trigger e agora a sua própria
thread de query está limpando cache em vez de responder.
// monitore isto ANTES do incidente
db.serverStatus().wiredTiger.cache["bytes currently in the cache"]
db.serverStatus().wiredTiger.cache["tracked dirty bytes in the cache"]
db.serverStatus().wiredTiger.cache["pages read into cache"] // taxa alta = working set > RAM
db.serverStatus().wiredTiger.cache["application threads page read from disk to cache time (usecs)"]
Durabilidade: journal + checkpoint
escrita → cache (memória)
├→ journal (WAL do WiredTiger): flush a cada ~100 ms ou a cada commit com j:true
└→ checkpoint: a cada 60 s, grava um snapshot consistente em disco
Na recuperação, o MongoDB volta ao último checkpoint e reaplica o journal a partir dele.
Consequência prática: sem j: true, uma queda de energia pode custar até ~100 ms de
escritas; com w: "majority", o dado está em outros nós, o que na prática cobre o caso.
Compressão: ligada por padrão
| Nível | Padrão | Alternativas |
|---|---|---|
| Coleções | Snappy (rápido, ~2–4×) | zstd (menor, mais CPU), none |
| Índices | prefix compression | — |
Isso explica por que uma coleção de 100 GB de JSON ocupa ~30 GB em disco, e por que os
números de dataSize (descomprimido) e storageSize (em disco) divergem tanto.
db.createCollection("eventos", {
storageEngine: { wiredTiger: { configString: "block_compressor=zstd" } }
})
zstd costuma ser a escolha certa para dados frios e volumosos: menos disco, menos I/O,
mais CPU — e I/O geralmente é o gargalo, não CPU.
Trade-offs
Cache de 50% parece pouco e é intencional. Aumentar
wiredTigerCacheSizeGB para 80% da RAM tira memória do page cache do SO e da própria
alocação do processo — e produz mais eviction, não menos. Só faça com medição.
Compressão troca CPU por I/O e por RAM útil. Dado comprimido em disco cabe mais no
page cache; descomprimir custa CPU em cada leitura fria. Em SSD rápido e CPU escassa,
snappy (ou nada) pode ganhar de zstd.
Concorrência por documento não elimina hot document. Se mil escritas por segundo
disputam o mesmo documento (um contador global, um documento de "estado do sistema"),
o WriteConflict e o retry viram o gargalo. A solução é de modelagem: distribuir em N
documentos e somar na leitura (2. Modelagem de Documentos).
Documento grande custa em toda operação. O WiredTiger lê e grava o documento
inteiro. Um $push num documento de 8 MB reescreve 8 MB. É o argumento técnico contra
arrays crescentes — antes mesmo do limite de 16 MB.
O trade-off central: o WiredTiger é excelente enquanto o working set cabe no cache e os documentos são pequenos. Fora dessas duas condições, o desempenho degrada de forma não linear — o que faz com que operar MongoDB seja, na prática, vigiar essas duas condições.
Exemplo prático
As métricas de um dashboard de MongoDB
// 1) o cache está sob pressão?
const c = db.serverStatus().wiredTiger.cache
print("uso:", (c["bytes currently in the cache"] / c["maximum bytes configured"] * 100).toFixed(1), "%")
print("sujo:", (c["tracked dirty bytes in the cache"] / c["maximum bytes configured"] * 100).toFixed(1), "%")
// > 80% de uso ou > 20% sujo = investigue AGORA, não depois
// 2) o working set cabe na RAM?
db.stats(1024*1024*1024) // dataSize, storageSize, indexSize em GB
// 3) quais operações estão lentas AGORA
db.currentOp({ "secs_running": { $gt: 3 }, "op": { $ne: "none" } })
// 4) o profiler: registre operações acima de 100ms (use com parcimônia em prod)
db.setProfilingLevel(1, { slowms: 100, sampleRate: 0.1 })
db.system.profile.find().sort({ ts: -1 }).limit(10).pretty()
// 5) taxa de conflito de escrita: hot document
db.serverStatus().metrics.operation.writeConflicts
Dimensionar de verdade
Coleção: 120 GB de dado (storageSize, comprimido)
Índices: 18 GB ← este é o número crítico
Documentos quentes: ~15% = 18 GB
Working set ≈ 18 (índices) + 18 (dado quente) = 36 GB
RAM necessária ≈ 36 / 0,5 (o cache é ~50% da RAM) ≈ 72 GB → escolha 64–96 GB
O erro comum é dimensionar pelo dado total (120 GB, "preciso de 128 GB de RAM!") ou ignorar índices ("só o dado quente conta"). O índice é acessado em toda consulta: ele é sempre quente.
Como isso falha em produção
| Sintoma | Causa |
|---|---|
| Latência p99 disparou sem mudar carga | cache passou de eviction_trigger; threads da app fazendo eviction |
pages read into cache alto e constante |
working set maior que a RAM: leitura de disco em toda query |
writeConflicts crescendo |
hot document: muitas escritas no mesmo _id |
| Escrita ficando mais lenta com o tempo | documento crescendo (array): reescrita do documento inteiro |
| Disco cheio com pouco dado "lógico" | storageSize ≫ dataSize por fragmentação; compact (com lock) ou resync da réplica |
| Perdeu ~100 ms de escritas num corte de energia | j: false e w: 1 — comportamento esperado, não bug |
| CPU alta em leitura | descompressão (zstd) de dados frios |
Aprofundamento aplicado
Cache cheio é diferente de cache sob pressão
Um cache útil tende a ocupar memória disponível. O sinal de problema aparece quando expulsar páginas não acompanha a demanda, operações precisam participar desse trabalho ou leituras úteis deixam de encontrar páginas residentes. Observe tendência e latência, sem tratar ocupação alta isolada como falha.
Uma consulta que varre grandes documentos para retornar um campo pode deslocar dados úteis. Melhorar o acesso ou reduzir o documento lido pode diminuir pressão sem aumentar RAM. O cache do WiredTiger e o cache de arquivos do sistema operacional também têm papéis distintos. Referência: uso de memória no WiredTiger.
Exercício: compare uma busca seletiva indexada e uma varredura ampla sobre os mesmos dados. Resultado esperado: relacione documentos examinados, páginas lidas e latência sob concorrência. Uma medição isolada com tudo em cache não representa o primeiro acesso.
Ao dimensionar, inclua índices, partes frequentemente acessadas dos documentos e espaço para manutenção; o tamanho comprimido no disco não é a memória necessária em execução.
Relacionado
- 1. MongoDB
- 2. Modelagem de Documentos
- 3. Transações, Replica Set e Durabilidade
- 5. Query Planner e Índices
- 6. Sharding por Dentro
- 2. InnoDB por Dentro
- Database Caching
Parte de MOC MongoDB · WiredTiger (docs)