NãoProteja seu banco de dados de futuros incêndios para evitar perdas de capital em grande escala em sua fase de Série A
Não
Proteja seu banco de dados de futuros incêndios para evitar perdas de capital em grande escala em sua fase de Série A
Disclaimer: O seguinte é um estudo de caso fictício usado para comunicar as melhores práticas para design de esquema MongoDB, ajuste de desempenho e otimização de custos
NãoDisclaimer: The following is a fictional case study used to communicate best practices for MongoDB schema design, performance tuning, and cost optimization
O dia em que o projeto de lei se tornou nuclear
O convite veio através do2:17. o
Atlas causou outra escalada de cluster de produção não solicitada que resultou em umaM60máquina a um custo mensal de$15kO painel queria saber por que a queima aumentou em 20% enquanto o M60 serve como um caro$15 k/monthdo sistema.
I opened the profiler:
db.system.profile.aggregate([
{ $match: { millis: { $gt: 100 } } },
{ $group: {
_id: { op: "$op", ns: "$ns", query: "$command.filter" },
n: { $sum: 1 },
avgMs: { $avg: "$millis" }
}},
{ $sort: { n: -1 } }, { $limit: 10 }
]).pretty();
Cada widget do painel exigiu que o criminoso puxe um total de1.7 GB every minute before rendering. The sheer quantity of memory usage created mountain peaks in the graph that resembled Everest.
Os servidores M30 agora operam com um desses aglomerados. A solução não levou a um aumento nos fragmentos. Três defeitos comuns conhecidos comoshape crimesexistiu na base de código antes da eliminação.
Investigação da cena do crime
2.1 N + 1 Tsunami de Query
Isto é reconhecido como um anti-padrão – quando ordenar um conjunto de ordens requer executar N consultas separadas para recuperar linhas de ordem.
// Incorrect: Orders + 1 000 extra queries
const orders = await db.orders.find({ userId }).toArray();
for (const o of orders) {
o.lines = await db.orderLines.find({ orderId: o._id }).toArray();
}
Hidden taxes
Network
Metrô
Por que ele se espalha
Compute
1 000 cursores = 1 000 interruptores de contexto
1 000 cursores = 1 000 interruptores de contexto
Storage I/O
1 000 caminhadas de índice + 1 000 deserializações doc
Network
Cada round-trip consome ~1 ms RTT + TLS sobre a cabeça
Refactor (4 lines):
// Success: Single round‑trip, 1 read unit per order
db.orders.aggregate([
{ $match: { userId } },
{ $lookup: {
from: "orderLines",
localField: "_id",
foreignField: "orderId",
as: "lines"
}},
{ $project: { lines: 1, total: 1, ts: 1 } }
]);
A latência p95 caiu de 2.300 ms para 160 ms.
2 300 ms160 milhãoAtlas read‑ops: 101 → 1.Isso é 99% de desconto – nenhum código de cupom necessário.
2.2 Unbounded Query Firehose
“Mas temos que mostrar a história completa do clique!”
Não“Mas temos que mostrar a história completa do clique!”
Claro - apenas não em um único cursor.
// Failure: Streams 30 months of data through the API gateway
db.events.find({ userId }).toArray();
Fix: hard-cap o lote e projetar apenas os campos que você renderiza.
db.events.find(
{ userId, ts: { $gte: ISODate(new Date() - 1000*60*60*24*30) } },
{ _id: 0, ts: 1, page: 1, ref: 1 } // projection
).sort({ ts: -1 }).limit(1_000);
Então deixe Mongo limpar por trás de você:
// 90‑day sliding window
db.events.createIndex({ ts: 1 }, { expireAfterSeconds: 60*60*24*90 });
Um cliente de fintech cortou sua conta de armazenamento em 72% durante a noite simplesmente adicionando TTLs.
NãoUm cliente de fintech cortou sua conta de armazenamento em 72% durante a noite simplesmente adicionando TTLs.
2.2 Jumbo-Document Money Pit
Mongo cobre documentos em 16 MB, mas qualquer coisa acima de 256 KB já é uma bandeira vermelha.
{
"_id": "...",
"type": "invoice",
"customer": { /* 700 kB */ },
"pdf": BinData(0,"..."), // 4 MB binary
"history": [ /* 1 200 delta rows */ ],
"ts": ISODate()
}
Why it hurts
- Não
- O documento inteiro é paginado, mesmo que você leia um campo. Não
- WiredTiger não pode armazenar tantos documentos por página → menor cache hit ratio. Não
- As entradas de índice são enormes → o filtro de bloom perde → mais buscas de disco. Não
SolutionA:schema‑by‑access‑patternA:
graph TD
Invoice[(invoices<br/><2 kB)] -->|ref| Hist[history<br/><1 kB * N]
Invoice -->|ref| Bin[pdf‑store (S3/GridFS)]
Pequenas metas de fatura permanecem quentes; BLOBS no S3 custam US $ 0,023/GB-mês em vez de SSDs Atlas de classe NAND.
NãoPequenas metas de fatura permanecem quentes; BLOBS no S3 custam US $ 0,023/GB-mês em vez de SSDs Atlas de classe NAND.
Mais quatro crimes de forma que você provavelmente é culpado
- Não
- Cabeça de índice de baixa cardinalidade ({ tipo: 1, ts: -1 }) — re-ordená-lo para { userId: 1, ts: -1 }. Não
- $regex começa em um campo não indexado – a varredura de cordas do inferno. Não
- findOneAndUpdate queue—document‐level locking bottleneck; use Redis/Kafka. Não
- skip + grande paginação de offset - Mongo deve contar cada doc ignorado; mover para os cursores do intervalo (ts, _id). Não
Custo da Anatomia 101
“Mas Atlas diz que as leituras são baratas!”
“Mas Atlas diz que as leituras são baratas!”
Vamos fazer a matemática.
Metrô | Não Value | Custo Unidade | NãoCusto mensal | Não
---|---|---|---|
Não Writes (150 /s) | NãoNão 380 m |
Dólar 0,225 / M | Não86 milhões |
Metrô
Metrô
Value
Custo Unidade
Custo mensal
Leia mais (3 k/s)
8.7 B
8.7 B
R$ 0,09 / M
R$ 0,09 / M
Dólar 702
$702
Duração (150 segundos)
Duração (150 segundos)
380 m
Dólar 0,225 / M
Dólar 0,225 / M
86 milhões
$86
Transferência de dados
Transferência de dados
1,5 TB
1,5 TB
R$ 0,25 / GB
R$ 0,25 / GB
$375
Capacidade de armazenamento (2 TB)
Capacidade de armazenamento (2 TB)
R$ 0,24 / GB
480 milhões
$480
no total:$1,643.
Aplique os fixos:
- Não
- Leads caem 70% → US $ 210
- Transferência cai 80% → $75
- Armazenamento cai 60% → $192 Não
Nova fatura: $ 564. Isso é um engenheiro de nível médio ou pista até o Q4 - você escolhe.
NãoNew bill: $564.É um engenheiro de nível médio.orPonto de saída para o Q4 – você escolhe.
48-Hour Rescue Sprint (Temporada testada em batalha)
Hora
Ação
Ação
Ferramentas
Ganhou
Ganhou
0 – 2
0 – 2
Volte para o perfil (lentos = 50).
Mongo Shell
Mongo Shell
Superfície Top 10 Slow Ops.
Superfície Top 10 Slow Ops.
2o 6
Rewrite N + 1 into $lookup
.
Reescreva N + 1 em$lookup
. o
Código VS + Testes
Código VS + Testes
90 por cento menos leitura.
6 - 10
6 - 10
Adicionar projeções e limitar resultados ilimitados.
Adicionar projeções e limitar resultados ilimitados.
Lago de Fogo
Lago de Fogo
RAM steady; API 4× faster.
RAM estável; API 4x mais rápido.
Funciona em conjunto com a RAM.
10 - 16
Break jumbo docs → metas + GridFS/S3.
Escrito por ETL
Working set fits in RAM.
Funciona em conjunto com a RAM.
16 - 22
16 - 22
Drop/replace low‑cardinality indexes.
Drop/replace low‑cardinality indexes.
Compasso
Disk shrinks; cache hits ↑.
Disk shrinks; cache hits ↑.
22 - 30
Atlas da UI
60% do armazenamento é salvo.
22 - 30
22 - 30
Criar TTLs, dados frios de partição de mês, habilitar o Arquivo Online.
Atlas UI
Atlas da UI
60% do armazenamento é salvo.
60% do armazenamento é salvo.
30 a 36
Adicione painéis do Grafana: cache hit %, scan:ix ratio, taxa de eviction.
Adicione painéis do Grafana: cache hit %, scan:ix ratio, taxa de eviction.
Prometheus
Prometheus
Visual early warnings.
Visual early warnings.
Teste de carregamento com K6
k6 + métricas atlas
Confirmar p95 < 150 ms @ 2× carga.
36‑48
Load‑test with k6
Teste de carregamento com K6
k6 + métricas atlas
k6 + Atlas metrics
Confirm p95 < 150 ms @ 2× load.
Confirmar p95 < 150 ms @ 2× carga.
Lista de verificação de auto-auditoria - Pin It Above Your Desk
- Não
-
Largest doc ÷ median > 10? → Refactor.
Não -
Cursor returns > 1 000 docs? → Paginate.
- TTL em todas as mesas de eventos / lojas? (Sim/Não)
- Qualquer índice onde a cardinalidade < 10 %? → Drop/re‐order. Não
- Profile slowops > 1% total de opções? → otimizar ou cache.
If primary cache hits remain under 90% it is wise to separate collections or add additional RAM memory post fixes.
Place the checklist on your laptop with adhesive glue after laminating it for printing.
Por que Shape Beats Índices
MongoDB’s query planner does a cost‑based search across candidate plans. The cost vector includes:
workUnits = ixScans + fetches + sorts + #docs returned
Os índices são reduzidosixScans
. Bad shape inflates fetchesesortsque muitas vezes dominam.Exemplo:
db.logs.find(
{ ts: { $gte: start, $lt: end }, level: "error" }
).sort({ level: 1, ts: -1 });
Índice{ level: 1, ts: -1 }
does not help planner avoid fetching every document when it adds a predicate to an unmentioned array field in your projections. Net result: 20 k fetches for 200 hits. Index should precede shape operations in daily operations.
Metricas ao vivo que você deve estar assistindo (Grafana PromQL)
# WiredTiger cache hit ratio
(rate(wiredtiger_blockmanager_blocks_read[1m]) /
(rate(wiredtiger_blockmanager_blocks_read[1m]) +
rate(wiredtiger_blockmanager_blocks_read_from_cache[1m]))
) < 0.10
Alerta se > 10 % falhar por 5 m.
# Docs scanned vs returned
rate(mongodb_ssm_metrics_documents[1m]{state="scanned"}) /
rate(mongodb_ssm_metrics_documents[1m]{state="returned"}) > 100
Se você digitalizar 100x mais documentos do que você retorna, você está queimando dinheiro.
NãoSe você digitalizar 100x mais documentos do que você retorna, você está queimando dinheiro.
Hands-On: Thin-Slice Escritório de Migração
Precisa de um 1 TBevents
Colecção emclicks
, views
, emlogins
without downtime? Use the double‑write / backfillO padrão.
// 1. Add trigger
const changeStream = db.events.watch([], { fullDocument: 'updateLookup' });
changeStream.on('change', ev => {
const dest = db[`${ev.fullDocument.type}s`];
dest.insertOne({ ...ev.fullDocument });
});
// 2. Backfill historical in chunks
let lastId = ObjectId("000000...");
while (true) {
const batch = db.events.find({_id: {$gt: lastId}}).sort({_id: 1}).limit(10_000);
if (!batch.hasNext()) break;
const docs = batch.toArray();
docs.forEach(d => db[`${d.type}s`].insertOne(d));
lastId = docs[docs.length - 1]._id;
}
Zero downtime, minimal extra storage (thanks to TTL), everyone sleeps.
Quando o ShardingIsA resposta
De acordo com a regra do polegar, você só deve rasgar se verificaroneAs seguintes condições se aplicam após a otimização do seu banco de dados:
- Não
-
The system operates with a working set representing more than 80 percent of RAM regardless of the cache performance rate.
- O sistema gera mais de 15 mil operações por segundo em seu desempenho máximo de escrita quando usa um servidor primário. Não
-
Your main priorities should be maintaining sub-70-millisecond multi-region latency because high AWS billing costs do not represent your critical concern.
Não
The decision should be simple when the conditions do not match these rules.
Case Study Wrap‑Up
6 700
Metrô
Metrô
Antes
Depois
Δ
RAM footprint
RAM footprint
120 GB
120 GB
36 GB
36 GB
- 70 por cento
−70 %
Reads/sec
6 700
6 700
900 milhões
86 por cento
Storage (hot)
Storage (hot)
2 TB
2 TB
600 GB
- 71 por cento
- 71 por cento
p95 latency
9.1 S
9.1 S
140 ms
- 92 por cento
- 92 por cento
Atlas cost / mo.
Atlas cost / mo.
R$ 15 284
R$ 3 210
R$ 3 210
−79 %
−79 %
No shards, no major code freeze, just ruthless shape surgery.
NãoSem fragmentos, sem congelamento de código, apenas cirurgia de forma implacável.
Takeaway: Debt vs. Death Spiral
O requisito de entregar rapidamente é obrigatório, mas manter o trabalho de baixa qualidade pertence à acumulação voluntária de dívidas. O provedor de nuvem cobra-lhe juros compostos que se acumulam com uma taxa de percentagem anual de 1000% sobre a sua dívida não paga. Examinamos os cartões de crédito de alto interesse da MongoDB como eles representam os cinco crimes de forma que estudamos.
Você precisa abrir o perfil para trabalhar com o$lookup
Pistões ao adicionar poeira TTL e, em seguida, implantar o seu projeto no modo lean.O seu painel e a sua equipe de desenvolvimento e o seu pager às 02:17 obterão descanso de qualidade.
Proceda com o refactoring do seu código até que o próximo incidente de autoescala ocorra.