Wir haben kürzlich die> 2 Millionen Rekorde für eine unserer Hauptsammlungen erreicht und leiden jetzt unter großen Leistungsproblemen bei dieser Sammlung.
Die Dokumente in der Sammlung enthalten ungefähr 8 Felder, die Sie mithilfe der Benutzeroberfläche filtern können. Die Ergebnisse sollen nach einem Zeitstempelfeld sortiert sein, in dem der Datensatz verarbeitet wurde.
Ich habe mehrere zusammengesetzte Indizes mit den gefilterten Feldern und dem Zeitstempel hinzugefügt, z.
db.events.ensureIndex({somefield: 1, timestamp:-1})
Ich habe auch einige Indizes für die gleichzeitige Verwendung mehrerer Filter hinzugefügt, um hoffentlich eine bessere Leistung zu erzielen. Die Ausführung einiger Filter dauert jedoch immer noch sehr lange.
Ich habe sichergestellt, dass mithilfe von EXPLAIN die Abfragen die von mir erstellten Indizes verwenden, die Leistung jedoch immer noch nicht gut genug ist.
Ich habe mich gefragt, ob Sharding jetzt der richtige Weg ist. Aber wir werden bald ungefähr 1 Million neue Platten pro Tag in dieser Sammlung haben. Ich bin mir also nicht sicher, ob es gut skaliert.
BEARBEITEN: Beispiel für eine Abfrage:
> db.audit.find({'userAgent.deviceType': 'MOBILE', 'user.userName': {$in: ['[email protected]']}}).sort({timestamp: -1}).limit(25).explain()
{
"cursor" : "BtreeCursor user.userName_1_timestamp_-1",
"isMultiKey" : false,
"n" : 0,
"nscannedObjects" : 30060,
"nscanned" : 30060,
"nscannedObjectsAllPlans" : 120241,
"nscannedAllPlans" : 120241,
"scanAndOrder" : false,
"indexOnly" : false,
"nYields" : 1,
"nChunkSkips" : 0,
"millis" : 26495,
"indexBounds" : {
"user.userName" : [
[
"[email protected]",
"[email protected]"
]
],
"timestamp" : [
[
{
"$maxElement" : 1
},
{
"$minElement" : 1
}
]
]
},
"server" : "yarin:27017"
}
Bitte beachten Sie, dass deviceType nur 2 Werte in meiner Sammlung hat.
limit
Argument?Antworten:
Dies ist die Suche in der Nadel im Heuhaufen.
explain()
Für Abfragen, die nicht gut funktionieren, benötigen wir eine Ausgabe von . Leider würde selbst das das Problem nur für diese bestimmte Abfrage beheben. Hier ist eine Strategie, wie Sie dies angehen können:db.setProfilingLevel(1, timeout)
wotimeout
ist der Schwellenwert für die Anzahl der Millisekunden, die die Abfrage oder der Befehl benötigt, wird alles, was langsamer ist, protokolliert)db.system.profile
und führen Sie die Abfragen manuell mit ausexplain()
explain()
Ausgabe zu identifizieren , z. B.scanAndOrder
oder großnscanned
usw.Ein Hauptproblem besteht darin, dass Sie Ihren Benutzern anscheinend erlauben, Filter nach Belieben zu kombinieren. Ohne Indexschnitt wird die Anzahl der erforderlichen Indizes drastisch erhöht.
Außerdem ist es eine sehr schlechte Strategie, bei jeder möglichen Abfrage blind einen Index zu werfen. Es ist wichtig, die Abfragen zu strukturieren und sicherzustellen, dass die indizierten Felder eine ausreichende Selektivität aufweisen .
Angenommen, Sie haben eine Abfrage für alle Benutzer mit
status
"aktiv" und einigen anderen Kriterien. Aber von den 5 Millionen Benutzern sind 3 Millionen aktiv und 2 Millionen nicht. Über 5 Millionen Einträge gibt es also nur zwei verschiedene Werte. Ein solcher Index hilft normalerweise nicht. Es ist besser, zuerst nach den anderen Kriterien zu suchen und dann die Ergebnisse zu scannen. Wenn Sie 100 Dokumente zurückgeben, müssen Sie durchschnittlich 167 Dokumente scannen, was die Leistung nicht allzu stark beeinträchtigt. Aber so einfach ist das nicht. Wenn das Hauptkriterium dasjoined_at
Datum des Benutzers ist und die Wahrscheinlichkeit hoch ist, dass Benutzer die Verwendung mit der Zeit einstellen, müssen Sie möglicherweise Tausende von Dokumenten scannen, bevor Sie hundert Übereinstimmungen finden.Die Optimierung hängt also sehr stark von den Daten (nicht nur ihrer Struktur , sondern auch den Daten selbst ), ihren internen Korrelationen und Ihren Abfragemustern ab .
Es wird schlimmer, wenn die Daten zu groß für den Arbeitsspeicher sind, denn dann ist ein Index großartig, aber das Scannen (oder sogar einfach das Zurückgeben) der Ergebnisse erfordert möglicherweise das zufällige Abrufen vieler Daten von der Festplatte, was viel Zeit in Anspruch nimmt.
Die beste Möglichkeit, dies zu steuern, besteht darin, die Anzahl der verschiedenen Abfragetypen zu begrenzen, Abfragen für Informationen mit geringer Selektivität nicht zuzulassen und den zufälligen Zugriff auf alte Daten zu verhindern.
Wenn alles andere fehlschlägt und Sie wirklich so viel Flexibilität bei Filtern benötigen, kann es sinnvoll sein, eine separate Such-DB in Betracht zu ziehen, die Indexschnittpunkte unterstützt, die Mongo-IDs von dort abzurufen und dann die Ergebnisse von Mongo mithilfe von zu erhalten
$in
. Aber das ist mit seinen eigenen Gefahren behaftet.- BEARBEITEN -
Die Erklärung, die Sie veröffentlicht haben, ist ein schönes Beispiel für das Problem beim Scannen von Feldern mit geringer Selektivität. Anscheinend gibt es viele Dokumente für "[email protected]". Das Auffinden und Sortieren dieser Dokumente nach Zeitstempel ist jetzt ziemlich schnell, da sie von Indizes mit hoher Selektivität unterstützt werden. Da es nur zwei Gerätetypen gibt, muss Mongo leider 30060 Dokumente scannen, um das erste zu finden, das mit "mobil" übereinstimmt.
Ich gehe davon aus, dass dies eine Art Web-Tracking ist und das Nutzungsmuster des Benutzers die Abfrage verlangsamt (würde er täglich zwischen Handy und Web wechseln, wäre die Abfrage schnell).
Eine schnellere Abfrage kann mithilfe eines zusammengesetzten Index erfolgen, der den Gerätetyp enthält, z. B. mithilfe von
a) ensureIndex({'username': 1, 'userAgent.deviceType' : 1, 'timestamp' :-1})
oder
b) ensureIndex({'userAgent.deviceType' : 1, 'username' : 1, 'timestamp' :-1})
Leider bedeutet dies, dass Abfragen wie
find({"username" : "foo"}).sort({"timestamp" : -1});
nicht mehr denselben Index verwenden können , sodass die Anzahl der Indizes, wie beschrieben, sehr schnell zunimmt.Ich fürchte, es gibt derzeit keine sehr gute Lösung für die Verwendung von Mongodb.
quelle
Mongo verwendet nur 1 Index pro Abfrage. Wenn Sie also nach 2 Feldern filtern möchten, verwendet Mongo den Index mit einem der Felder, muss jedoch die gesamte Teilmenge scannen.
Dies bedeutet, dass Sie grundsätzlich einen Index für jede Art von Abfrage benötigen, um die beste Leistung zu erzielen.
Abhängig von Ihren Daten ist es möglicherweise keine schlechte Idee, eine Abfrage pro Feld zu haben und die Ergebnisse in Ihrer App zu verarbeiten. Auf diese Weise benötigen Sie nur Indizes für alle Felder, es können jedoch zu viele Daten für die Verarbeitung vorhanden sein.
quelle
Wenn Sie $ in verwenden, verwendet mongodb niemals INDEX. Ändern Sie Ihre Abfrage, indem Sie dieses $ in entfernen. Es sollte einen Index verwenden und eine bessere Leistung erzielen als zuvor.
http://docs.mongodb.org/manual/core/query-optimization/
quelle