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
Anwesendnation
) 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.