Das ist die Suche nach der Nadel im Heuhaufen. Wir benötigen eine Ausgabe von explain()
für die Abfragen, die nicht gut funktionieren. Leider würde selbst das das Problem nur für diese bestimmte Abfrage beheben, also hier eine Strategie, wie Sie dies angehen können:
- Stellen Sie sicher, dass dies nicht an unzureichendem RAM und übermäßigem Paging liegt
- Aktivieren Sie den DB-Profiler (mit
db.setProfilingLevel(1, timeout)
wobeitimeout
ist der Schwellenwert für die Anzahl der Millisekunden, die die Abfrage oder der Befehl dauert, alles Langsamere wird protokolliert) - Untersuchen Sie die langsamen Abfragen in
db.system.profile
und führen Sie die Abfragen manuell mitexplain()
aus - Versuchen Sie, die langsamen Operationen in
explain()
zu identifizieren Ausgabe wiescanAndOrder
oder großnscanned
usw. - Begründen Sie die Selektivität der Abfrage und ob es möglich ist, die Abfrage mit einem Index überhaupt zu verbessern . Wenn dies nicht der Fall ist, ziehen Sie in Betracht, die Filtereinstellung für den Endbenutzer zu verbieten, oder geben Sie ihm einen Warndialog, dass der Vorgang möglicherweise langsam ist.
Ein Hauptproblem besteht darin, dass Sie Ihren Benutzern anscheinend erlauben, Filter nach Belieben zu kombinieren. Ohne Indexüberschneidung wird die Anzahl der erforderlichen Indizes dramatisch steigen.
Außerdem ist es eine sehr schlechte Strategie, blindlings einen Index auf jede mögliche Abfrage 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 einige andere Kriterien. Aber von den 5 Millionen Benutzern sind 3 Millionen aktiv und 2 Millionen nicht, also gibt es bei über 5 Millionen Einträgen nur zwei verschiedene Werte. Ein solcher Index hilft normalerweise nicht weiter. Es ist besser, zuerst nach den anderen Kriterien zu suchen und dann die Ergebnisse zu durchsuchen. Im Durchschnitt müssen Sie bei der Rückgabe von 100 Dokumenten 167 Dokumente scannen, was die Leistung nicht allzu sehr beeinträchtigt. Aber es ist nicht so einfach. Wenn das primäre Kriterium der joined_at
ist Datum des Benutzers und die Wahrscheinlichkeit, dass Benutzer die Nutzung mit der Zeit einstellen, hoch ist, müssen Sie möglicherweise Tausende scannen von Dokumenten, bevor hundert Übereinstimmungen gefunden werden.
Die Optimierung hängt also sehr stark von den Daten ab (nicht nur von ihrer Struktur , sondern auch die Daten selbst ), seine internen Korrelationen und Ihre Abfragemuster .
Die Dinge werden schlimmer, wenn die Daten zu groß für den Arbeitsspeicher sind, denn dann ist es großartig, einen Index zu haben, aber das Scannen (oder sogar das einfache Zurückgeben) der Ergebnisse erfordert möglicherweise das zufällige Abrufen vieler Daten von der Festplatte, was viel Zeit in Anspruch nimmt.
Der beste Weg, dies zu kontrollieren, besteht darin, die Anzahl der verschiedenen Abfragetypen zu begrenzen, Abfragen auf Informationen mit geringer Selektivität zu verbieten und zu versuchen, den zufälligen Zugriff auf alte Daten zu verhindern.
Wenn alles andere fehlschlägt und Sie wirklich so viel Flexibilität bei den Filtern benötigen, könnte es sich lohnen, eine separate Suchdatenbank in Betracht zu ziehen, die Indexüberschneidungen unterstützt, die Mongo-IDs von dort abzurufen und dann die Ergebnisse von Mongo mit $in
-- BEARBEITEN --
Die Erklärung, die Sie gepostet haben, ist ein schönes Beispiel für das Problem beim Scannen von Feldern mit niedriger Selektivität. Anscheinend gibt es viele Dokumente für "[email protected]". Das Auffinden dieser Dokumente und deren absteigende Sortierung nach Zeitstempel geht jetzt ziemlich schnell, weil es von Indizes mit hoher Selektivität unterstützt wird. Da es nur zwei Gerätetypen gibt, muss Mongo leider 30060 Dokumente scannen, um das erste zu finden, das mit „mobil“ übereinstimmt.
Ich nehme an, dass dies eine Art Web-Tracking ist und das Nutzungsmuster des Benutzers die Abfrage langsam macht (würde er täglich zwischen Mobilgerät und Internet wechseln, wäre die Abfrage schnell).
Diese spezielle Abfrage könnte schneller gemacht werden, indem ein zusammengesetzter Index verwendet wird, der den Gerätetyp enthält, z. mit
a) ensureIndex({'username': 1, 'userAgent.deviceType' : 1, 'timestamp' :-1})
oder
b) ensureIndex({'userAgent.deviceType' : 1, 'username' : 1, 'timestamp' :-1})
Leider bedeutet das, dass Abfragen wie find({"username" :"foo"}).sort({"timestamp" :-1});
kann nicht mehr denselben Index verwenden, daher wird die Anzahl der Indizes wie beschrieben sehr schnell wachsen.
Ich fürchte, es gibt derzeit keine sehr gute Lösung dafür mit mongodb.