it-swarm.com.de

MongoDB-Abfrage für über 5 Millionen Datensätze

Wir haben vor kurzem die> 2 Millionen-Rekorde für eine unserer Hauptkollektionen erreicht und leiden nun unter den Hauptleistungsproblemen dieser Sammlung.

Ihre Dokumente in der Sammlung haben etwa 8 Felder, die Sie mithilfe der Benutzeroberfläche filtern können. Die Ergebnisse werden nach einem Zeitstempelfeld sortiert, in dem der Datensatz verarbeitet wurde.

Ich habe mehrere zusammengesetzte Indizes mit den gefilterten Feldern und dem Zeitstempel hinzugefügt z.B:

db.events.ensureIndex({somefield: 1, timestamp:-1})

Ich habe auch ein paar Indizes für die gleichzeitige Verwendung mehrerer Filter hinzugefügt, um hoffentlich eine bessere Leistung zu erzielen. Bei einigen Filtern dauert es jedoch noch sehr lange, bis sie ausgeführt werden.

Ich habe sichergestellt, dass mithilfe von erklären, dass die Abfragen die von mir erstellten Indizes verwenden, die Leistung aber immer noch nicht gut genug ist.

Ich habe mich gefragt, ob Sharding jetzt der richtige Weg ist. Aber wir werden bald etwa 1 Million neue Datensätze pro Tag in dieser Sammlung haben. Ich bin mir nicht sicher, ob es gut skalieren wird.

EDIT: 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.

64
Yarin Miran

Dies sucht die Nadel im Heuhaufen. Wir benötigen etwas Ausgabe von explain() für die Abfragen, die nicht gut funktionieren. Unglücklicherweise würde auch das Problem nur für diese bestimmte Abfrage behoben. Daher finden Sie hier eine Strategie zum Vorgehen:

  1. Stellen Sie sicher, dass es nicht an unzureichendem RAM und übermäßigem Paging liegt
  2. Aktivieren Sie den DB-Profiler (mit db.setProfilingLevel(1, timeout), wobei timeout der Schwellenwert für die Anzahl der Millisekunden ist, für die die Abfrage oder der Befehl benötigt wird. Alles, was langsamer ist, wird protokolliert.)
  3. Überprüfen Sie die langsamen Abfragen in db.system.profile und führen Sie die Abfragen manuell mit explain() aus.
  4. Versuchen Sie, die langsamen Vorgänge in der explain()-Ausgabe zu identifizieren, z. B. scanAndOrder oder große nscanned usw.
  5. Grund für die Selektivität der Abfrage und ob es möglich ist, die Abfrage mithilfe eines Indexes at all zu verbessern. Wenn dies nicht der Fall ist, sollten Sie die Filtereinstellung für den Endbenutzer nicht zulassen oder ihm ein Warndialogfeld mitteilen, dass der Vorgang möglicherweise langsam ist.

Ein Hauptproblem ist, dass Sie Ihren Benutzern offenbar erlauben, Filter nach Belieben zu kombinieren. Ohne Indexüberschneidungen sprengt dies die Anzahl der erforderlichen Indizes dramatisch.

Auch das blinde Werfen eines Index bei jeder möglichen Abfrage ist eine sehr schlechte Strategie. Es ist wichtig, die Abfragen zu strukturieren und sicherzustellen, dass die indizierten Felder über ausreichende Selektivität verfügen. 

Angenommen, Sie haben eine Abfrage für alle Benutzer mit status "aktiv" und einigen anderen Kriterien. Aber von den 5 Millionen Nutzern 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. Es ist besser, zuerst nach den anderen Kriterien zu suchen und dann die Ergebnisse zu scannen. Bei der Rückgabe von 100 Dokumenten müssen Sie im Durchschnitt 167 Dokumente scannen, was die Leistung nicht zu sehr beeinträchtigen kann. So einfach ist das nicht. Wenn das primäre Kriterium das joined_at-Datum des Benutzers ist und die Wahrscheinlichkeit hoch ist, dass Benutzer die Nutzung mit der Zeit einstellen, müssen Sie möglicherweise tausende von Dokumenten scannen, bevor Sie hundert Übereinstimmungen finden.

Die Optimierung hängt also stark von den Daten ab (nicht nur von ihrer -Struktur, sondern auch von den data selbst), ihren internen Korrelationen und Ihren Abfragemustern.

Die Dinge werden noch schlimmer, wenn die Daten für den RAM zu groß sind, da dann ein Index groß ist. Wenn Sie die Ergebnisse jedoch scannen (oder einfach nur zurückgeben), kann es erforderlich sein, viele Daten von der Festplatte zufällig abzurufen, was viel Zeit in Anspruch nimmt.

Die beste Möglichkeit, dies zu steuern, besteht darin, die Anzahl der verschiedenen Abfragetypen zu begrenzen, Abfragen zu Informationen mit geringer Selektivität zuzulassen und zu versuchen, den zufälligen Zugriff auf alte Daten zu verhindern.

Wenn alles andere fehlschlägt und Sie wirklich so viel Flexibilität beim Filtern benötigen, kann es sinnvoll sein, eine separate Such-Datenbank in Betracht zu ziehen, die Indexschnittpunkte unterstützt, die Mongo-IDs von dort abzurufen und dann die Ergebnisse aus dem Mongo mit $in abzurufen. Das ist jedoch mit eigenen Gefahren behaftet.

- BEARBEITEN -

Die von Ihnen veröffentlichte Erläuterung 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]". Diese Dokumente zu finden und nach Zeitstempel absteigend zu sortieren, ist ziemlich schnell, da sie von Indexen mit hoher Selektivität unterstützt wird. Da es nur zwei Gerätetypen gibt, muss mongo 30060-Dokumente scannen, um das erste Dokument zu finden, das zu "mobil" passt.

Ich gehe davon aus, dass dies eine Art Web-Tracking ist und das Abfragemuster des Benutzers die Abfrage langsam macht (würde er täglich zwischen Handy und Web wechseln, wäre die Abfrage schnell).

Um diese spezielle Abfrage schneller zu machen, könnte ein zusammengesetzter Index verwendet werden, 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 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 befürchte, dass es derzeit keine sehr gute Lösung für Mongodb gibt.

63
mnemosyn

Wenn Sie $ in verwenden, verwendet mongodb niemals INDEX. Ändern Sie Ihre Abfrage, indem Sie dieses $ in entfernen. Es sollte den Index verwenden und würde eine bessere Leistung erzielen als zuvor.

http://docs.mongodb.org/manual/core/query-optimization/

0
Gopal

Mongo verwendet nur einen Index pro Abfrage ..__ Wenn Sie also nach zwei Feldern filtern möchten, verwendet Mongo den Index mit einem der Felder, muss jedoch die gesamte Teilmenge scannen.

Dies bedeutet, dass Sie grundsätzlich für jeden Abfragetyp einen Index 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 jedes Feld, aber es sind möglicherweise zu viele Daten dafür verarbeiten.

0
Mark Meeus