MongoDB fragt die Leistung für über 5 Millionen Datensätze ab

76

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.

Yarin Miran
quelle
Verwenden Sie das limitArgument?
Joe
Schön! Ich werde wahrscheinlich in Zukunft ein ähnliches Szenario haben und Antworten könnten auch für mich hilfreich sein. Wie groß ist Ihre Datenbank mit dieser Sammlung? Wie lange hat es gedauert, diese 8-kV-Paare abzufragen, bis Sie 2 mil erreicht haben, und wie lange dauert es jetzt? (nur neugierig)
Anvarik
Joe, natürlich verwende ich Limits. Im Moment beschränke ich meine Ergebnisse auf 25 Dokumente. Ich möchte nicht einmal über Sprünge sprechen, da ich sie in naher Zukunft durch Bereichsabfragen ersetzen werde.
Yarin Miran
2
Als die Sammlung ungefähr 1-2 Millionen Datensätze umfasste, bemerkte ich einige Leistungsprobleme (5-50 Sekunden Abfragezeit). Dann habe ich Indizes hinzugefügt und eine angemessene Leistung für die Abfrage von <1000 ms erhalten. Abfragen dauern jetzt zwischen 20 ms und 60 Sekunden. Dies hängt jedoch von der Wertverteilung der gefilterten Felder ab und davon, wie hilfreich die Indizes tatsächlich waren.
Yarin Miran
Welche Abfragen sind langsam? Ist eine einfache Abfrage ohne Filterung bereits langsam? Oder werden nur Abfragen nach einem Feld langsam gefiltert? Oder durch zwei Felder?
Joe

Antworten:

71

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:

  1. Stellen Sie sicher, dass dies nicht auf unzureichenden Arbeitsspeicher und übermäßiges Paging zurückzuführen ist
  2. Aktivieren Sie den DB-Profiler (unter Verwendung von db.setProfilingLevel(1, timeout)wo timeoutist der Schwellenwert für die Anzahl der Millisekunden, die die Abfrage oder der Befehl benötigt, wird alles, was langsamer ist, protokolliert)
  3. Überprüfen Sie die langsamen Abfragen in db.system.profileund führen Sie die Abfragen manuell mit ausexplain()
  4. Versuchen Sie, die langsamen Operationen in der explain()Ausgabe zu identifizieren , z. B. scanAndOrderoder groß nscannedusw.
  5. Grund über die Selektivität der Abfrage und ob es möglich ist, die Abfrage mit einem Index zu verbessern überhaupt . Wenn nicht, sollten Sie die Filtereinstellung für den Endbenutzer nicht zulassen oder ihm einen Warndialog geben, dass der Vorgang möglicherweise langsam ist.

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 das joined_atDatum 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.

Mnemosyn
quelle
Danke für die Antwort! Ein weiteres Problem ist, dass es auf unserem Mongo mehrere Kundendatenbanken gibt, in denen jede diese riesige Sammlung hat. Wir befürchten, dass die Indizierung all dieser Sammlungen die Leistung erheblich beeinträchtigt, da wir über eine große Menge an RAM verfügen müssen, um gleichzeitige Abfragen von verschiedenen Benutzern zu unterstützen. Haben Sie einen Vorschlag für eine gute Such-DB zu diesem Zweck?
Yarin Miran
Ich denke, das hängt von den Suchfunktionen ab, die Sie benötigen. Für die Grundlagen sollte jede Datenbank, die Indexschnittpunkte unterstützt, ausreichen. Wenn Sie eine Volltextsuche, eine facettierte Suche oder sogar Slice & Dice benötigen, wird es schwierig und es gibt ein ganzes Universum an Werkzeugen, von SolR über Elastic Search bis hin zu OLAP-Cubes. Wenn
mnemosyn
Ich denke, wir werden uns für ElasticSearch für diese bestimmte Tabelle entscheiden. Was denkst du darüber ?
Yarin Miran
2
Gute Antwort. Mich würde interessieren, was sich in den letzten 4,5 Jahren in dieser Hinsicht geändert hat.
Daniel Hilgarth
2

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.

Mark Meeus
quelle
-1

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/

Gopal
quelle
14
Zu Ihrer Information, $ in verwendet einen Index, $ nin verwendet keinen Index. Das Problem in $ in, was wir erlebt haben, ist, dass Mongo die Abfrage pro Wert in $ in ausführt. Trotz der Verwendung eines Index für jede Abfrage ist es extrem langsam ..
Yarin Miran