Ny historia

Sänk dina MongoDB-kostnader med 79 % med Shape‐First-optimeringar

förbi Hayk Ghukasyan8m2025/04/18
Read on Terminal Reader

För länge; Att läsa

Genom att profilera långsamma operationer, fixa N + 1-frågor med $lookup, capping / TTL'ing obegränsade frågor, refactoring jumbo-dokument och omordnande av index över en 48-timmars sprint, minskade de sin månatliga kostnad från $ 15,284 till $ 3,210 (-79%) och förbättrade p95-latensen från 1,9 s till 140 msno-sharding krävs.
featured image - Sänk dina MongoDB-kostnader med 79 % med Shape‐First-optimeringar
Hayk Ghukasyan HackerNoon profile picture
0-item


är

Skydda din databas från framtida bränder för att undvika stora kapitalförluster i serie A-fasen

är

Skydda din databas från framtida bränder för att undvika stora kapitalförluster i serie A-fasen


är

Ansvarsfriskrivning: Följande är en fiktiv fallstudie som används för att kommunicera bästa praxis för MongoDB schema design, prestanda tuning och kostnadsoptimering

är

Disclaimer: The following is a fictional case study used to communicate best practices for MongoDB schema design, performance tuning, and cost optimization


Dagen då lagen blev kärnvapen

The call came through at 2:17och .


Atlas orsakade en annan oönskad produktionsklusteruppskalning som resulterade i enM60maskiner till en månads kostnad av$15kStyrelsen ville veta varför förbränningen ökade med 20% medan M60 tjänar som en dyr$15 k/monthoch system.


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();


Varje instrumentpanel widget krävde förövaren att dra i en total av1.7 GBDen stora mängden minne som användes skapade bergstoppar i grafen som liknade Everest.


M30-servrarna arbetar nu med en av dessa kluster. Lösningen resulterade inte i en ökning av fragment.Former av brottfanns i kodbasen före eliminering.


brottsplats utredning

2.1 N + 1 Förfrågan Tsunami

Detta erkänns som ett anti-mönster – när du beställer en uppsättning order kräver att du kör N separata frågor för att hämta orderlinjer.

// 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

ärMätare Varför det spikar Beräkning 1 000 kursorer = 1 000 kontextomkopplingar Lagring I/O 1 000 indexgångar + 1 000 doc deserialiseringar Nätverk Varje rundresa äter ~1 ms RTT + TLS handskakning över huvudet
Mätare Varför det spikar Beräkning 1 000 kursorer = 1 000 kontextomkopplingar Lagring I/O 1 000 indexgångar + 1 000 doc deserialiseringar Nätverk Varje rundresa äter ~1 ms RTT + TLS handskakning över huvudetärMätareärVarför det spetsarärMätare

Mätare

Varför det spetsar

Varför det spetsar

Beräkna 1 000 kursorer = 1 000 kontextomkopplingarär

räkna

Compute

1 000 kursorer = 1 000 kontextomkopplingar

1 000 kursorer = 1 000 kontextomkopplingar

Lagring I/O 1 000 indexgångar + 1 000 doc deserialiseringarär

Förvaring I/O

Storage I/O

1 000 indexpromenader + 1 000 doc deserialiseringar

1 000 indexpromenader + 1 000 doc deserialiseringar

Nätverk Varje rundresa äter ~1 ms RTT + TLS handskakning över huvudetNätverk

Network

är

Varje rundresa äter ~1 ms RTT + TLS handskakning över huvudet

Varje rundresa äter ~1 ms RTT + TLS handskakning över huvudet


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 } }
]);


Latency p95 sjönk från 2 300 ms till 160 ms.

2 300 ms160 ms

Författare till read-ops:101 → 1.Det är 99 % rabatt – ingen kupongkod behövs.


2.2 Obegränsad sökning Firehose

är

”Men vi måste visa hela klickhistorien!”

är

”Men vi måste visa hela klickhistorien!”


Visst - bara inte i en enda kursor.

// Failure: Streams 30 months of data through the API gateway
db.events.find({ userId }).toArray();


Fix: hard‑cap the batch and project only the fields you render.

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);


Låt Mongo städa upp bakom dig:

// 90‑day sliding window
db.events.createIndex({ ts: 1 }, { expireAfterSeconds: 60*60*24*90 });


är

En fintech-kund minskade sin lagringsräkning med 72 % över natten genom att helt enkelt lägga till TTL.

är

En fintech-kund minskade sin lagringsräkning med 72 % över natten genom att helt enkelt lägga till TTL.


2.3 Jumbo-Document Pengar Pit

Mongo täcker dokument på 16 MB, men allt över 256 KB är redan en röd flagga.

{
  "_id": "...",
  "type": "invoice",
  "customer": { /* 700 kB */ },
  "pdf": BinData(0,"..."),        // 4 MB binary
  "history": [ /* 1 200 delta rows */ ],
  "ts": ISODate()
}


Why it hurts

    är
  1. Hela dokumentet är sidvisat även om du läser ett fält.
  2. är
  3. WiredTiger kan inte lagra så många dokument per sida → lägre cache hit ratio.
  4. är
  5. Indexposter är stora → bloom filter missar → fler disk sökningar.
  6. är

Solutionoch :schema‑by‑access‑pattern:

graph TD
  Invoice[(invoices<br/>&lt;2 kB)] -->|ref| Hist[history<br/>&lt;1 kB * N]
  Invoice -->|ref| Bin[pdf‑store (S3/GridFS)]


är

Små faktura metas förblir heta; BLOBS i S3 kostar $ 0,023 / GB-månad i stället för NAND-klass Atlas SSDs.

är

Små faktura metas förblir heta; BLOBS i S3 kostar $ 0,023 / GB-månad i stället för NAND-klass Atlas SSDs.


Fyra fler formbrott du förmodligen är skyldig till

    är
  1. Low‑cardinality index head ({ type: 1, ts: -1 })—re‑order it to { userId: 1, ts: -1 }.
  2. $regex börjar på ett icke-indexerat fält – strängscan från helvetet.
  3. är
  4. findOneAndUpdate queue—document‐level locking bottleneck; använd Redis/Kafka.
  5. är
  6. skip + stor offset pagination - Mongo måste räkna varje hoppat doc; växla till intervall (ts, _id) markörer.

4 Kostnadsanatomi 101

”Men Atlas säger att läsning är billigt!”

är

”Men Atlas säger att läsning är billigt!”


Låt oss göra matte.

ärMetrisk värde enhetskostnad Månadskostnad Läsning (3 k/s) 7.8 B $0.09 / M $702 Skrivet (150 / s) 380 M $0.225 / M $86 Dataöverföring 1.5 TB $0.25 / GB $375 Lagring (2 TB) $0.24 / GB $480
Metrisk värde enhetskostnad Månadskostnad Läsning (3 k/s) 7.8 B $0.09 / M $702 Skrivet (150 / s) 380 M $0.225 / M $86 Dataöverföring 1.5 TB $0.25 / GB $375 Lagring (2 TB) $0.24 / GB $480Metrisk enhetskostnad MånadskostnadMetriska

Metriska

är

värde

värde

enhetskostnad

enhetskostnad

Månadskostnad

Månadskostnad

Läser (3 k/s) 7.8 B $0.09 / M $702Läsning (3 k/s)

Läsning (3 k/s)

är

7.8 B för

7.8 B för

0,09 kr / m

0,09 kr / m

För 702

$702

ärFörfattare (150 sek)är380 mär0,225 kr / mär86 miljonerärFörfattare (150 sek)

Författare (150 sek)

380 m

380 m

0,225 kr / m

$0.225 / M

86 miljoner

$86

Dataöverföring 1,5 TB $0.25 / GB $375Dataöverföring

Dataöverföring

är

1.5 TB

1,5 TB

är

0,25 kr / GB

0,25 kr / GB

375 kr

$375

Lagring (2 TB) $0.24 / GB $480Lagringsutrymme (2 TB)

Lagringsutrymme (2 TB)

är


är

0,24 kr / GB

0,24 kr / GB

480 kr

$480


Totalt är:$1,643.

Applicera dessa fixar:

    är
  • Läser faller 70 % → $210
  • är
  • Överföring faller 80 % → $75
  • är
  • Lagring sjunker 60 % → $192
  • är


Ny räkning: $ 564. Det är en mellannivåingenjör eller runway till Q4 - du väljer.

är

Ny räkning: $ 564. Det är en mellannivåingenjör eller runway till Q4 - du väljer.


48-timmars räddningssprinten (Battle-tested Timeline)

ärHour Action Tool Wins 0‐2 Turn on profiler (slowms = 50). Mongo shell Surface top 10 slow ops. 2‐6 Rewrite N + 1 into $lookup. VS Code + Jest testar 90 % färre läser. 6‐10 Lägg till projektioner och gränser till obegränsade fynd. API-lager RAM stabil; API 4× snabbare. 10‐16 Break jumbo docs → metas + GridFS/S3. Scripted ETL Working set fits in RAM. 16‐22 Drop/place låg-cardinality index. Compass Disk krymper; cache hits ↑. 22‐30 Skapa TTLs, månad-partition kalla data, aktivera Online Archive. Atlas UI 60 % lagring sparad. 30‐36 Lägg till grafpaneler: hit cache %, scan: ratio, eviction rate.
Hour Action Tool vinnerär0‐2 Slå på profiler (slow = 50). Mongo shell Surface topp 10 långsamma optioner.är2‐6 Skriv om N + 1 till $lookup. VS Code + Jest testar 90 % färre läsningar.är6‐10 Lägg till projektioner och begränsa obegränsade fynd. API-lagret RAM stabilt; API 4x snabbare.är10‐16 Break jumbo docs → metas + GridFS/S3. Scripted ETL Working set passar i RAM.är16‐22 Drop/replace low‐cardinality indexes. Compass Disk krymper; cache träffar ↑.22‐30 Skapa TTL, månad-partition kalla data, aktivera Online Archive. Atlas UI 60 % lagring sparas.30‐36 Lägg till Grafana-paneler: cache hit %, scan:ix ratio, eviction rate.är36‐48 Lasttest med k6 k6 + Atlas metrics Bekräfta p95 < 150 ms @ 2× belastning.ärärtimmeäråtgärderärverktygärVinnareärtimme

Hour

åtgärder

åtgärder

är

verktyg

verktyg

är

Vinnare

Vinnare

0‐2 Slå på profiler (slow = 50). Mongo shell Surface topp 10 långsamma optioner.är

0 - 2

0 - 2

Vänd på profiler (slowms = 50).

Vänd på profiler (slowms = 50).

är

Mongo skal

Mongo skal

är

Surface Top 10 långsamma optioner.

Surface Top 10 långsamma optioner.

2‐6 Skriv om N + 1 till $lookup. VS Code + Jest testar 90 % färre läsningar.2 - 6

2 - 6

är

Skriv om N + 1 i $lookup.

Skriv om N + 1 i $lookup.

är

VS Code + Jest tester

VS Code + Jest tester

är

90 procent läser mindre.

90 procent läser mindre.

6 - 10ärär

Lägg till projektioner och begränsa till obegränsade resultat.

är

Brandsläckare

ärRAM är stabilt, API 4x snabbare.ärär

6‑10

6 - 10

är

Lägg till projektioner och begränsa till obegränsade resultat.

Lägg till projektioner och begränsa till obegränsade resultat.

Brandsläckare

Brandsläckare

är

RAM steady; API 4× faster.

RAM är stabilt, API 4x snabbare.

10 - 16Bryta jumbo docs → metas + GridFS/S3.ärär

Skrivet av ETL

Arbetsplatser i Ram.

är

10 - 16

10 - 16

är

Break jumbo docs → metas + GridFS/S3.

Break jumbo docs → metas + GridFS/S3.

är

Skrivet av ETL

Skrivet av ETL

Arbetsplatser i Ram.

Arbetsplatser i Ram.

16‐22 Drop/replace low‐cardinality indexes. Compass Disk krymper; cache träffar ↑.

16 - 22

16‑22

är

Släpp / ersätt index med låg kardinalitet.

Släpp / ersätt index med låg kardinalitet.

är

Compass

Kompass

är

Disk shrinks; cache träffar ↑.

Disk shrinks; cache hits ↑.

22 – 30ärSkapa TTL, månad-partition kalla data, aktivera Online Archive.ärAtlas UIär60 % lagring sparas.

22‑30

22‑30

är

Create TTLs, month‑partition cold data, enable Online Archive.

Skapa TTL, månad-partition kalla data, aktivera Online Archive.

är

Atlas UI

Atlas UI

60 % storage saved.

60 % lagring sparas.

30‐36 Lägg till Grafana-paneler: cache hit %, scan:ix ratio, eviction rate.är

30 – 36

30 – 36

är

Lägg till Grafana-paneler: cache hit %, scan:ix ratio, eviction rate.

Lägg till Grafana-paneler: cache hit %, scan:ix ratio, eviction rate.

är

Prometheus

Prometheus

Visual early warnings.

Tidiga visuella varningar.

36‐48 Lasttest med k6 k6 + Atlas metrics Bekräfta p95 < 150 ms @ 2× belastning.

36 till 48

36‑48

Laddningstest med K6

Load‑test with k6

k6 + Atlas mätningar

k6 + Atlas mätningar

är

Bekräfta p95 < 150 ms @ 2× laddning.

Bekräfta p95 < 150 ms @ 2× laddning.


Självrevisions checklista - Pin It Above Your Desk

    är
  • Largest doc ÷ median > 10? → Refactor.

  • är
  • Cursor returnerar > 1 000 docs? → Paginate.
  • TTL på varje event/butiksbord? (Ja/Nej)
  • Any index where cardinality < 10 %? → Drop/re‑order.

  • Profiler slowops > 1 % totala optioner? → Optimera eller 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.


Varför Shape Beats Index

MongoDB:s frågeplanerare gör en kostnadsbaserad sökning över kandidatplaner. Kostnadsvektorn innehåller:

workUnits = ixScans + fetches + sorts + #docs returned


Indexet minskar baraixScansdålig form inflatesfetchesochsorts, which often dominate. Example:

db.logs.find(
  { ts: { $gte: start, $lt: end }, level: "error" }
).sort({ level: 1, ts: -1 });


indexera{ level: 1, ts: -1 }hjälper inte planeraren att undvika att hämta varje dokument när den lägger till en predikat till ett icke nämnt matrisfält i dina projektioner. Netto resultat: 20 k hämtar för 200 träffar. Index bör föregå formoperationer i dagliga operationer.


Live Metrics You Should Be Watching (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


Alert if > 10 % misses for 5 m.

# Docs scanned vs returned
rate(mongodb_ssm_metrics_documents[1m]{state="scanned"}) /
rate(mongodb_ssm_metrics_documents[1m]{state="returned"}) > 100


är

Om du skannar 100x fler dokument än du returnerar, bränner du pengar.

Om du skannar 100x fler dokument än du returnerar, bränner du pengar.


Hands-On: Thin-Slice Migration skript

Need to crack a 1‑TB eventsSamlingen in iclicks, viewsochlogins without downtime? Use the double‑write / backfill pattern.

// 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.


When Sharding IsSvaret är

According to the rule of thumb you should shard only if you verify oneAv dessa villkor uppstår efter optimering av din databas:

  1. Systemet fungerar med en arbetsuppsättning som representerar mer än 80 procent av RAM oavsett cache prestanda.
  2. är
  3. Systemet genererar mer än 15 tusen operationer per sekund i sin toppskrivningsprestanda när man använder en primärserver.
  4. är
  5. Your main priorities should be maintaining sub-70-millisecond multi-region latency because high AWS billing costs do not represent your critical concern.


The decision should be simple when the conditions do not match these rules.


Fallstudie Wrap-Up

ärärärärärärärärärärärärp95 latens 1,9 s 140 ms −92 %ärärärärär

Metric

är

Före

Efterär

Δ

Ram fotspårFör 120 GBär

36 GB

−70 procent

Läsare / Sec

6 700900 stycken

86 procent

är

Förvaringsutrymme (varma)

2 TB600 GB- 71 procent
Atlas kostnad / mo.15 284 krär

3 210 kr

är

-79 procent

är

Metric

är

Före

ärEfterΔärärRam fotspårFör 120 GBär

36 GB

är

−70 procent

är

Läsare / Sec

6 700är900 styckenär

86 procent

ärärär

Förvaringsutrymme (varma)

är2 TBär600 GB- 71 procentp95 latens 1,9 s 140 ms −92 %ärärAtlas kostnad / mo.15 284 krärär

3 210 kr

ärär

-79 procent

ärärär

Metric

är

Före

ärEfterΔär

Metric

Metric

är

Före

Före

Efter

Efter

Δ

Δ

Ram fotspårFör 120 GBär

36 GB

är

−70 procent

Ram fotspår

RAM footprint

För 120 GB

För 120 GB

är

36 GB

36 GB

−70 %

−70 procent

Läsare / Sec

6 700är900 styckenär

86 procent

är

Läsare / Sec

Läsare / Sec

6 700

6 700

900

900

86 procent

−86 %

är

Storage (hot)

är2 TBär600 GB- 71 procentär

Storage (hot)

Förvaringsutrymme (varma)

2 TB

2 TB

600 GB

600 GB

- 71 procent

- 71 procent

p95 latens 1,9 s 140 ms −92 %är

P95 latens

P95 latens

1.9 S

1.9 S

är

140 ms

140 ms

-92 procent

-92 procent

ärAtlas kostnad / mo.är15 284 krärär

3 210 kr

ärär

-79 procent

ärAtlas kostnad / mo.

Atlas cost / mo.

15 284 kr

15 284 kr

är

3 210 kr

3 210 kr

är

-79 procent

−79 %


är

Inga bitar, ingen större kodfrysning, bara hänsynslös formkirurgi.

är

No shards, no major code freeze, just ruthless shape surgery.


Takeaway: Debt vs. Death Spiral

Kravet på att leverera snabbt är obligatoriskt, men att upprätthålla dålig kvalitet på arbetet hör till frivillig skuldackumulering. molnleverantören debiterar dig kompositränta som ackumuleras med en årlig procentsats på 1000 % på din obetalda skuld.Vi undersökte MongoDBs högränta kreditkort eftersom de representerar de fem formerna av brott vi studerade. Att kasta bort dessa skulder inom din nuvarande sprintperiod kommer att resultera i tacksamma tekniska och finansiella rapporter.


Du måste öppna profilen för att arbeta med$lookupPistoner medan du lägger till TTL-damm, och sedan distribuera ditt projekt i magert läge.Din styrelse och ditt utvecklingsteam och din pager vid 02:17 kommer att få kvalitets vila.


Proceed with the refactoring of your code until the next autoscaling incident happens.

L O A D I N G
. . . comments & more!

About Author

Hayk Ghukasyan HackerNoon profile picture
Hayk Ghukasyan@hayk.ghukasyan
Senior Software Engineer and Backend Team Lead with 20+ years of experience. Passionate about system design, backend architecture, microservices, and helping others grow in tech.

HÄNG TAGGAR

DENNA ARTIKEL PRESENTERAS I...

Trending Topics

blockchaincryptocurrencyhackernoon-top-storyprogrammingsoftware-developmenttechnologystartuphackernoon-booksBitcoinbooks