Tl; dr

Saa M20 → M60über Nacht 20 % zu ihrer Cloud -Rechnung. In einem hektischen 48 -stündigen Dash: Wir:

  • abgeflachte N + 1 Wasserfälle mit $lookup Anwesend
  • Mit Projektion unbegrenzte Cursoren gezähmt, restrict() und TTL,
  • Break up 16 MB „Jumbo“ -Dokumente in magere Metadaten + Gridfs -Blobs,
  • neu beordnet eine Handvoll schläfrige Indizes

Und sah zu $ 15 284 → $ 3 210/Mo (–79 %) Während die P95 -Latenz stürzte 1,9 s → 140 ms.

Alles auf einem einfachen Replik -Set.


Schritt 1: Der Tag, an dem die Rechnung Supernova ging

02:17 Uhr – Das On -Name -Telefon beleuchtete wie ein Flipper -Pc. Atlas hatte unsere vertrauen M20 Für einen maximierten M60. Lack gefüllt mit 🟥 Invoice Schock Warnungen, während Grafanas rotgeleitete Graphen in Echtzeit einen Horrorfilm gemalt haben.

„Finanzen sagt, dass der neue Ausgeben neun Monate Landebahn ausgelöscht hat. Wir brauchen vor dem Stand -up eine Lösung.“
– COO, 02:38

Der Ingenieur hat den Profiler geknackt. Drei Schuldige sprangen vom Bildschirm:

  • Abfragebüros – Jeder API -Auftrag der Bestellung löste einen zusätzlichen Abruf für seine Zeilen aus. 1 000 Bestellungen? 1 001 Rundstreifen.
  • Feuerwehrmann Cursor – Ein Klick -Stream -Endpunkt gestreamt 30 Monate von Ereignissen auf jeder Seite Laden.
  • Jumbo -Dokumente – 16 MB -Rechnungen (komplett mit PDFs) haben den Cache auf Bits und zurück geblasen.

Atlas versuchte zu helfen, indem er {Hardware} ins Feuer warf – von 64 GB RAM auf 320 GB, die IOPS erhöhte und natürlich die Rechnung erhöhte.

Durch das Frühstück waren die Regeln für die Kriegszimmer klar: Schneiden Sie 70 % der Ausgaben in 48 Stunden, keine Ausfallzeit, keine Schema -Nukes. Das Play -by -Play beginnt unten.


Schritt 2: Drei Formverbrechen und wie man sie repariert

2,1 n + 1 Abfrage Tsunami

Symptom: Für jede Bestellung hat die API eine zweite Abfrage für ihre Werbebuchungen abgefeuert. 1 000 Bestellungen ⇒ 1 001 Rundstreifen.

// Outdated (painful)
const orders = await db.orders.discover({ userId }).toArray();
for (const o of orders) {
  o.traces = await db.orderLines.discover({ orderId: o._id }).toArray();
}

Versteckte Gebühren: 1 000 Indexwanderungen, 1 000 TLS -Handshakes, 1 000 Kontextschalter.

Mittel (4 Zeilen):

// New (single cross)
db.orders.mixture((
 { $match: { userId } },
 { $lookup: {
   from: 'orderLines',
   localField: '_id',
   foreignField: 'orderId',
   as: 'traces'
 } },
 { $challenge: { traces: 1, complete: 1, ts: 1 } }
));

Latenz P95: 2 300 ms → 160 ms. Ops lesen: 101 → 1 (–99 %).

2.2 Unbefragte Abfrage Feuerwehrlinge

Symptom: Ein Endpunkt wurde 30 Monate Klickhistorie in einem einzigen Cursor gestreamt.

// Earlier than
const occasions = db.occasions.discover({ userId }).toArray();

Repair: Verschließen Sie das Fenster und projizieren Sie nur gerenderte Felder.

const occasions = db.occasions.discover(
  {
   userId,
   ts: { $gte: new Date(Date.now() - 30*24*3600*1000) }
  },
  { _id: 0, ts: 1, web page: 1, ref: 1 }
).type({ ts: -1 }).restrict(1_000);

Dann lassen Sie Mongo für Sie beschnitten:

// 90‑day TTL
db.occasions.createIndex({ ts: 1 }, { expireAfterSeconds: 90*24*3600 });

Ein Fintech -Shopper abgeschnitten 72 % von ihrem Speicher über Nacht mit nichts als TTL.

2.3 Jumbo -Dokumentgeldgrube

Alles oben 256 kb Bereits Stämme Cache -Linien; Eine Sammlung speicherte Multi -MB -Rechnungen mit PDFs und 1 200 -Rob -Geschichten.

Lösung: Aufgeteilt nach Zugriffsmuster – HOT -Metadaten in Rechnungen, kalte Blobs in S3/Gridfs.

graph TD
  Bill((invoices <2 kB)) -->|ref| Hist(historical past <1 kB * N)
  Bill -->|ref| Bin(pdf‑retailer (S3/GridFS))

SSD verbringen schneedrocknen; Cache -Trefferverhältnis sprang 22 PP


Schritt 3: Vier Kind Sünden, die sich in Sicht verstecken

In Kind geht es nicht nur um Dokumentengröße, sondern wie Abfragen, Indizes und Zugriffsmuster sich verflechten.

Diese vier Anti -Empattern lauern in den meisten Produktionsclustern und lassen bar ab.

3.1 Low -Kardinalität führender Indexschlüssel

Symptom Der Index beginnt mit einem Feld, das <10 % unterschiedliche Werte enthält, z. B. {Typ: 1, ts: -1} . Der Planer muss riesige Teile durchqueren, bevor er den selektiven Teil anwendet.

Kosten Excessive B -Stree Fan -Out, schlechte Cache -Lokalität, zusätzliche Festplatten sucht.

Repair Bewegen Sie den selektiven Schlüssel (BenutzerID, Orgid, Mieter) Erste: {userId: 1, ts: -1} . On-line umbauen und dann den alten Index fallen lassen.

3.2 Blind $regex Scan

Symptom $regex: /foo/i Auf einem nicht indizierten Feld erzwingt ein vollständiger Sammlungsscan; CPU -Spikes, Cache Churns.

KOSTEN JEDES MUSTER -PASSING GESEHEN JEDES Dokument und dekodiert BSON auf dem heißen Pfad.

Repair bevorzugt verankerte Muster ( /^foo/ ) mit einem unterstützenden Index oder ein durchsuchbares Slug -Feld hinzufügen ( decrease(identify) ) und zeige das stattdessen.

3.3 FindoneAndupdate als Nachrichtenwarteschlange

Symptom Arbeitnehmer Umfrage mit findOneAndUpdate({ standing: 'new' }, { $set: { standing: 'taken' } }).

Kosten Locks auf Dokumentebene serialisieren Schriftsteller; Durchsatz bricht über ein paar tausend OPS/s zusammen.

Repair Verwenden Sie eine zweckgebaute Warteschlange (Redis -Streams, Kafka, SQS) oder MongoDbDie nativen Veränderung von Streams, um Ereignisse zu überschreiten und Schreibvorgänge anzuhängen.

3.4 Offset -Paginierungsfalle

Symptom discover().skip(N).restrict(20) wobei N sechsstellige Offsets erreichen kann.

Kosten Mongo zählt und wirkt immer noch alle übersprungenen Dokumente – Linienzeit. Latenzballons und Abrechnungen zählen jeweils.

Repair Wechseln zu Reichweite Cursor unter Verwendung des zusammengesetzten Index (ts, _id) :

// web page after the final merchandise of earlier web page
discover({ ts: { $lt: lastTs } })
 .type({ ts: -1, _id: -1 })
 .restrict(20);

Beherrschen Sie diese vier und Sie werden RAM zurückerhalten, die Lesen von Lektüren und Verschiebung von Vierteln verschieben.


Schritt 4: Kosten Anatomie 101

Metrisch Vor Einheit $ Kosten Nach Δ %
Liest (3 okay/s) 7.8 b 0,09/m $ 702 2.3 b -70
Schreibt (150/s) 380 m 0,225/m 86 $ 380 m 0
Xfer 1,5 TB 0,25/GB $ 375 300 GB -80
Lagerung 2 TB 0,24/GB $ 480 800 GB -60
Gesamt $ 1.643 -66

Schritt 5: 48 -stündige Rettungszeitleiste

Stunde Aktion Werkzeug Gewinnen
0-2 Profiler aktivieren (Slowms = 50) Mongoschale Prime 10 Gradual Ops befanden sich
2-6 Ersetzen Sie N + 1 durch $ Lookup VS Code + Assessments 90 % weniger Lesevorgänge
6-10 Projektionen hinzufügen & restrict () API -Schicht RAM stabil, API 4 × schneller
10-16 Break up Jumbo Docs Skript -ETL Arbeitssatz passt in RAM
16-22 Drop/Neueinstellung schwache Indizes Kompass Scheibe schrumpft, Cache -Hits ↑
22-30 Erstellen Sie TTLS / On-line -Archiv Atlas UI –60 % Speicher
30-36 Grafana -Panels verdrahtet Prometheus Frühwarnungen leben
36-48 Lastentest mit K6 K6 + Atlas P95 <150 ms @ 2 × Final

Schritt 6: Checkliste zur Selbstauditen

  • Größter Doc ÷ Median> 10? → Refaktor.
  • Irgendwelche Cursor> 1 000 Dokumente? → paginieren.
  • TTL auf jeder Veranstaltungskollektion? (Y/n)
  • Jugendkardinalität <10 %? → Drop oder Nachbestellung.
  • Profiler „langsame“ Ops> 1 %? → Optimieren oder Cache.

Kleben Sie dies an Ihren Monitor, bevor Freitag bereitgestellt wird.


Schritt 7: Warum Kind> Indizes (an den meisten Tagen)

Das Hinzufügen eines Index ist wie der Kauf eines schnelleren Gabelstaplers für das Lagerhaus: Es beschleunigt das Abhol, aber es tut es Nichts Wenn die Gänge mit übergroßen Kisten überfüllt sind. In MongoDB -Begriffen ist die Kostenformel des Planers ungefähr:

workUnits = ixScans + fetches + kinds + returnedDocs

Indizes Trim noch Und kann immer noch dominieren, wenn Dokumente aufgebläht, spärlich zugezogen oder schlecht gruppiert sind.

Eine Geschichte von zwei Abfragen

Skinny Doc (2 KB) Jumbo Doc (16 MB)
ixscans 1 000 1 000
holt 1 000 × 2 kb = 2 MB 1 000 × 16 MB = 16 GB
Nettozeit 80 ms 48 S + Räumungsstürme

Gleicher Index, gleiches Abfragemuster – der einzige Unterschied ist Kind.

Die Faustregel

Repair Kind zuerst und dann einmal index.
– Jedes umgestaltete Dokument schrumpft jeden zukünftigen Abruf, Cache -Zeile und Replikationspaket.

Drei Kind gewinnt leicht ein Dutzend further B -Strees.


Schritt 8: Dwell -Metriken, auf denen Sie alarmieren sollten (PromQL)

# Cache miss ratio (>10 % for five m triggers alert)
 (charge(wiredtiger_blockmanager_blocks_read(1m)) /
 (charge(wiredtiger_blockmanager_blocks_read(1m)) +
 charge(wiredtiger_blockmanager_blocks_read_from_cache(1m)))) > 0.10

# Docs scanned vs returned (>100 triggers alert)
 charge(mongodb_ssm_metrics_documents{state="scanned"}(1m)) /
 charge(mongodb_ssm_metrics_documents{state="returned"}(1m)) > 100

Schritt 9: Migrationsskript von Dünnschalen

Müssen einen 1 -TB brechen occasions Sammlung in Unterkollektionen ohne Ausfallzeiten? Verwenden Sie Doppelschreiber + Backfülle:

// 1) Ahead writes
const cs = db.occasions.watch((), { fullDocument: 'updateLookup' });
cs.on('change', ev => {
 db(`${ev.fullDocument.sort}s`).insertOne(ev.fullDocument);
});

// 2) Backfill historical past
let lastId = ObjectId("000000000000000000000000");
whereas (true) {
 const batch = db.occasions
  .discover({ _id: { $gt: lastId } })
  .type({ _id: 1 })
  .restrict(10_000)
  .toArray();
 if (!batch.size) break;
 db(batch(0).sort + 's').insertMany(batch);
 lastId = batch(batch.size - 1)._id;
}

Schritt 11: Wenn tatsächlich Sharding erforderlich ist

Sharding ist a Final -Mile -Taktik, keine erste Heilung. Es brütet Daten, multipliziert Fehlermodi und kompliziert jede Migration. Vertikale Auspuffverbesserungen und zuerst formbasierte Optimierungen. Greifen Sie nur dann nach einem Shard -Schlüssel, wenn mindestens einer der folgenden Schwellenwerte liegt anhaltend unter realer Belastung und kann nicht billiger gelöst werden.

Harte Kapazitätsdecken

Symptom Faustregel Warum horizontale Trennung hilft
Der Arbeitssatz liegt über 80 % des physischen RAM für 24 H+ <60 % sind gesund; 60–80 % können durch eine größere Box maskiert werden. > 80 % Seiten ständig Durch die Aufteilung wird heiße Partitionen auf separate Knoten gesetzt, wodurch das Cache -Hit -Verhältnis wiederhergestellt wird
Primär -Schreibdurchsatz> 15 000 OPs/s nach Indexabstimmung Unter 10 000 OPs/s können Sie oft durch Batching oder Bulk Upserts überleben Das Isolieren von Hochverkleidungsbrocken reduziert die Verzögerung des Journalverzögerung und des Schlosses Streit
Multi -Area -Produktanforderungen <70 ms P95 Lesen Latenz Velocity ​​-of -Gentle -Units ~ 80 ms US↔eu -Boden Zone Sharding Pins Daten in der Nähe von Benutzern, ohne auf Edge -Caches zurückzugreifen

Weiches Signale Sharding nähert sich

  • Index -Builds überschreiten Wartungsfenster auch mit On-line -Indexierung.
  • Die Verdichtungszeit frisst in eine Katastrophenreduzierung SLA.
  • Ein einzelner Mieter besitzt> 25 % des Clustervolumens.
  • Der Profiler zeigt> 500 ms Sperrspikes aus langen Transaktionen.

Checkliste, bevor Sie schneiden

  • Dokumente neu formen: Wenn der größte Dokument 20 × der Median ist, Refactor zuerst.
  • Komprimierung aktivieren ( ZSTD oder bissig ) Kauft sich oft 30 % auf Lagerkopffreiheit.
  • Kaltdaten archivieren per On-line -Archiv oder abgestufter S3 -Speicher.
  • Schreiben Sie die heißesten Endpunkte in Go/Rust neu, wenn JSON -Parsing CPU dominiert.
  • Laufen mongo‑perf; Wenn die Workload zu einem einzigen Replikat -Set -Postfixen passt, lassen Sie den Shard -Plan ab.

Auswahl eines Shard -Schlüssels

  • Verwenden Hochkardinalität, monotoner zunehmender Felder ( ObjectId Zeitstempelpräfix).
  • Tasten mit niedriger Entropie vermeiden (standing Anwesend nation ) Dieser Trichter schreibt auf ein paar Stücke.
  • Setzen Sie zuerst das häufigste Abfrageprädikat ein, um Streuung zu vermeiden.

Sharding ist eine Operation; Sobald Sie geschnitten haben, leben Sie mit der Narbe. Stellen Sie sicher, dass der Affected person den Betrieb wirklich benötigt.


Schlussfolgerung – Formen vor der Gesetzesrechnung

Wenn die M60 Improve landete mit einem stillen Increase, es struggle nicht die Schuld der {Hardware}, es struggle ein Weckruf. Hier ging es nicht um CPU, Gedächtnis oder Festplatte, es ging um Kind. Kind der Dokumente. Kind der Abfragen. Kind der Annahmen, die über Monate nach „einfach versenden“ Sprints leise aufgebläht waren.

Es wurde weder eine neue Datenbank, eine Wochenendmigration noch eine Armee von Beratern vorgenommen. Es brauchte ein Group, das bereit struggle, nach innen zu schauen, Panik gegen die Profilerstellung zu tauschen und das zu verändern, was sie bereits hatten.

Die Ergebnisse waren unbestreitbar: Latenz nach unten durch 92%Kosten um quick gesenkt 80%und eine Codebasis, die sich jetzt so meizt, um zu atmen.

Aber hier ist das eigentliche Imbiss: Technische Schulden in Kind sind nicht nur ein Leistungsproblem, es ist finanziell. Und im Gegensatz zu Indexen oder Caching -Tips zahlt sich die Gestaltung der Dinge direkt aus, wenn Ihre Abfrage jedes Mal, wenn Ihre Daten repliziert, jedes Mal, wenn Sie skalieren, ausgeführt werden.

Fragen Sie sich vor Ihrem nächsten Abrechnungszyklusspitzen:

  • Benötigt jeder Endpunkt das vollständige Dokument?
  • Entwerfen wir für Lesevorgänge oder schreiben wir einfach schnell?
  • Funktionieren unsere Indizes oder einfach nur hart?

Form-First ist keine Technik-es ist eine Denkweise. Eine Gewohnheit. Und je früher Sie es übernehmen, desto länger wird Ihr System – und Ihre Landebahn – dauern.


Quellen und weiteres Lesen

Über den Autor

Hayk Ghukasyan ist ein Chef für Ingenieurwesen bei Hexact, wo er hilft, Automatisierungsplattformen wie hexomatisch und hexospark aufzubauen. Er verfügt über mehr als 20 Jahre Erfahrung in groß angelegten Systemen Architektur, Echtzeitdatenbanken und Optimierungsgenieurwesen.

Von admin

Schreibe einen Kommentar

Deine E-Mail-Adresse wird nicht veröffentlicht. Erforderliche Felder sind mit * markiert