trilha

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


Parte de MOC MongoDB · WiredTiger (docs)

Buscar

Busca por título, seção e texto das notas