MongoDB-Abfrageleistung: Verständnis des Abfrageplaners und der erweiterten Index-Typen

Die Beziehung eines jeden Ingenieurs zu einer Datenbank hat verschiedene Phasen. Sie könnte folgendermaßen aussehen:

  • Schock: "Was ist das für eine neue Sache? Das ist schlimm! Ich bin das nicht gewöhnt!"
  • Euphorie: "ICH KANN ALLE DINGE MACHEN"
  • Selbstzweifel: "Aber ich dachte, ich könnte all diese Dinge tun?
  • taucht der Held auf: "Ich muss nur dieses und jenes einstellen und das richtige Werkzeug für meinen Anwendungsfall verwenden".

Die Arbeit mit MongoDB ist da keine Ausnahme. MongoDB gehört zur Familie der "NoSQL"-Datenbanken im weitesten Sinne und ist vielleicht etwas gewöhnungsbedürftig, wenn Sie Postgres, MySQL oder ähnliche RDBMS gewohnt sind. Wenn Sie erst seit kurzem mit MongoDB in der Produktion arbeiten und eine verstärkte Nutzung durch die Anwendung feststellen, beginnen Sie wahrscheinlich mit dem Stadium der "Selbstzweifel". Die Dinge sind vielleicht nicht so einfach wie die Indizierung einiger offensichtlicher Felder. Wir bei FloQast nutzen MongoDB seit einigen Jahren intensiv und haben festgestellt, dass man die Technologie, wie jede andere produktive Technologie auch, wirklich verstehen muss, wenn man die Anforderungen von vielen Tausend Nutzern erfüllt. In diesem Beitrag befassen wir uns mit einigen Bereichen, die für die MongoDB-Performance "über die Grundlagen" hinausgehen. Sie erhalten einige hilfreiche Tools, wie z. B. die Arbeit mit dem Abfrageplaner und das Wissen, wann Sie spezielle Indizes verwenden sollten.

Was abfragen?

Viele ausgereifte Datenbanken haben unglaublich komplexe Architekturen. Sie bestehen oft aus Dingen wie Speicher-Engines, Abfrageplanern und -optimierern, Replikationsschemata, Sharding-Ansätzen, On-Disk-Speicherformaten, On-Wire-Protokollen usw. Über die meisten dieser Dinge sollte ein Anwendungsentwickler nicht viel Zeit nachdenken müssen, wenn überhaupt. Aber eines davon kann unglaublich nützlich sein: der Abfrageplaner.

Die meisten Datenbanken mit einer Abfragesprache (SQL für relationale Datenbanken, die MongoDB-Abfragesprache für MongoDB) verfügen über einen Abfrageplaner und einen Abfrageoptimierer oder werden irgendwann über einen solchen verfügen. Diese beiden Komponenten arbeiten zusammen, um die Abfragen von Programmen und Benutzern so effizient und schnell wie möglich zu gestalten. Dies sind hochkomplexe Bereiche, die aber ziemlich selbsterklärend sind:

  • Abfrageplaner: nimmt eine optimierte Abfrage und bestimmt, wie die Daten aus dem Speichermodul der Datenbank abgerufen werden sollen. Legt im Detail fest, welche Schritte zum Abrufen der Daten unternommen werden sollen. Dies kann Schritte wie das Transformieren, Abrufen oder Sortieren von Daten umfassen.
  • Abfrageoptimierer: Bevor der Planer eine Abfrage ausführt, sucht er nach Möglichkeiten zur Verbesserung einer Abfrage. Stellt Fragen wie "Kann ich einen Index verwenden? Kann ich Indizes kombinieren? Welcher Plan ist der beste?" Kostenbasierte Optimierer bewerten oft so viele Pläne, wie für eine bestimmte Abfrage Zeit zur Verfügung steht, und wählen dann den kostengünstigsten davon aus.

Planen wir einige Abfragen

Hier ist das Beispielschema, das wir beim Lernen verwenden werden (falls Sie nicht schon einen unserer anderen Beiträge über die Leistung von MongoDB gelesen haben). Wir bauen eine Anwendung für einen imaginären Mitfahrdienst: FloQars.

{
    "_id": "5cf0029caff5056591b0ce7d",
    "name": "A. Person",
    "email": "[email protected]",
    "avatarURL": "https://static.image.com/1234",
    "password": "$2a$14$ajq8Q7fbtFRQvXpdCq7Jcuy.Rx1h/L4J60Otx.gyNLbAYctGMJ9tK",
    "rides": [
        {
            "_id": "5cf0029caff5056591b0ce2f",
            "date": "01/01/2006",
            "driverID": "5cf0029caff5056591b0ce20",
            "from": {
                "lat": -70.55044,
                "lng": 165.39229
            },
            "to": {
                "lat": -29.9244,
                "lng": 88.2593
            }
        },
        {
            "_id": "5cf0029caff5056591b0ce2a",
            "date": "01/02/2006",
            "from": {
                "lat": -70.55044,
                "lng": 165.39229
            },
            "to": {
                "lat": -29.9244,
                "lng": 88.2593
            }
        }
    ],
    "location": {
        "current": {
            "lat": -70.55044,
            "lng": 165.39229
        }
    }
}

Lassen Sie uns eine Abfrage für ein Textfeld einrichten, damit unsere fiktiven Benutzer ihre Fahrtennotizen durchsuchen können. Für den Anfang könnten wir so etwas versuchen: { rideNotes: /receive/ }.

Aus Erfahrung mit Datenbanken wissen wir, dass dies keine gute Idee ist: Wir grenzen die Abfrage nicht durch andere Kriterien ein, sondern verwenden einen regulären Ausdruck. Dies wird wahrscheinlich eine Untersuchung aller Dokumente in einer Sammlung bedeuten. Wir können die .explain() Befehl, um unsere Annahmen zu überprüfen. Die explain in einer Abfrage wird die Ausgabe der Komponenten des Abfrageplaners/-optimierers sichtbar gemacht, so dass wir mehr über unsere Abfrage erfahren können. Probieren wir es aus!

db.riders.explain().find({ rideNotes: /receive/ })

erbringt:

{
  "queryPlanner" : {
    "plannerVersion" : 1,
    "namespace" : "floqars.riders",
    "indexFilterSet" : false,
    "parsedQuery" : {
      "rideNotes" : {
        "$regex" : "receive"
      }
    },
    "queryHash" : "8A9E186F",
    "planCacheKey" : "8A9E186F",
    "winningPlan" : {
      "stage" : "COLLSCAN",
      "filter" : {
        "rideNotes" : {
          "$regex" : "receive"
        }
      },
      "direction" : "forward"
    },
    "rejectedPlans" : [ ]
  },
  "ok" : 1,
  "$clusterTime" : {
    "clusterTime" : Timestamp(1588804213, 35),
    "signature" : {
      "hash" : BinData(0,"+2onXLjgqb4/oXSs1b+80prrEKk="),
      "keyId" : NumberLong("6759458550222684161")
    }
  },
  "operationTime" : Timestamp(1588804213, 35)
}

Es gibt einige Schlüsselbereiche, die zu beachten sind: winningPlan und die darin enthaltene(n) Stufe(n). Dadurch erfahren wir, was der Abfrageoptimierer für die beste Art der Ausführung unserer Abfrage hält und welche Stufen sie umfasst. Sie sollten hoffen, Folgendes zu sehen IXSCAN (Index-Scan) so viel wie möglich hier und COLLSCAN (collection scan) so wenig wie möglich. Je mehr MonogDB Ihre Indizes nutzen kann, desto besser. Fügen wir ein beliebiges Sortierfeld zu unserer Abfrage hinzu und sehen wir uns an, welche weiteren Schritte in unserer Abfrage involviert sein könnten: db.riders.explain().find({ rideNotes: /receive/ }, { _id: -1 } )

{
  "queryPlanner" : {
    "plannerVersion" : 1,
    "namespace" : "floqars.riders",
    "indexFilterSet" : false,
    "parsedQuery" : {
      "rideNotes" : {
        "$regex" : "receive"
      }
    },
    "queryHash" : "8A9E186F",
    "planCacheKey" : "8A9E186F",
    "winningPlan" : {
      "stage" : "COLLSCAN",
      "filter" : {
        "rideNotes" : {
          "$regex" : "receive"
        }
      },
      "direction" : "forward"
    },
    "rejectedPlans" : [ ]
  },
  "ok" : 1,
  "$clusterTime" : {
    "clusterTime" : Timestamp(1588804213, 35),
    "signature" : {
      "hash" : BinData(0,"+2onXLjgqb4/oXSs1b+80prrEKk="),
      "keyId" : NumberLong("6759458550222684161")
    }
  },
  "operationTime" : Timestamp(1588804213, 35)
}

Beachten Sie hier, dass wir eine Sortierbedingung für _id wurde der Abfrage eine zusätzliche Stufe hinzugefügt. Das liest sich irgendwie rückwärts: Mongo scannt zunächst alles, wendet einen Regex-Filter an und gibt dann die Ergebnisse sortiert nach unserem Schlüssel zurück. Dieser Plan unterscheidet sich etwas von der "normalen" Sortierung mit einem anderen Feld, weil _id ist bereits ein indiziertes Feld.

Einige der anderen Etappen sind:

  • PROJECTIONAuswahl bestimmter Datenattribute und Verwerfen des Rests
  • FETCHIDs, die in einer früheren Phase erzeugt wurden, um Dokumente zu holen
  • LIMIT: prüft eine begrenzte Anzahl von Dokumenten und leitet nur diese an die nächste Stufe weiter
  • COLLSCANDokument: liest ein Dokument nach dem anderen und geht zum nächsten Schritt über
  • IXSCANLesen eines Indexes und Weitergabe der Ergebnisse an die nächste Stufe
  • SKIP: Überspringen N Dokumente und leitet den Rest an die nächste Stufe weiter
  • SORT_KEY_GENERATORSortierung: vor einer Sortierstufe; erzeugt einen Schlüssel für die Sortierung
  • SORTSortieren: Sortiert Dokumente nach einem Sortierschlüssel
  • SHARD_MERGE und SHARDING_FILTERfür das Kombinieren/Filtern von Dokumenten aus Abfragen gegen Sharded-Cluster

Theorie und Praxis

Es ist ja schön und gut, dass wir uns Filter ansehen können, aber vielleicht ist Ihnen etwas aufgefallen: Unsere Abfragepläne zeigen nicht die "echten" Ergebnisse unserer langsamen Suchvorgänge. Die explain Der Befehl wird schnell ausgeführt und sagt uns nur, was der Planer würde tun. Wir sollten einen Ausführlichkeitsmodus wählen für explain damit wir mehr Details darüber sehen können, wie sich unsere Abfragen tatsächlich verhalten werden. Sie können dies tun, indem Sie einen Ausführlichkeitsmodus an die explain Aufruf in Ihrer Abfrage wie folgt: db.riders.explain('allPlansExecution').find({ rideNotes: /receive/ }, { avatarURL: -1 } ). Dadurch erhält man viel mehr Details:

{
    "queryPlanner": {
        "plannerVersion": 1,
        "namespace": "floqars.riders",
        "indexFilterSet": false,
        "parsedQuery": {
            "rideNotes": {
                "$regex": "receive"
            }
        },
        "winningPlan": {
            "stage": "PROJECTION_SIMPLE",
            "transformBy": {
                "avatarURL": -1
            },
            "inputStage": {
                "stage": "COLLSCAN",
                "filter": {
                    "rideNotes": {
                        "$regex": "receive"
                    }
                },
                "direction": "forward"
            }
        },
        "rejectedPlans": []
    },
    "executionStats": {
        "executionSuccess": true,
        "nReturned": 29848,
        "executionTimeMillis": 2906,
        "totalKeysExamined": 0,
        "totalDocsExamined": 1615003,
        "executionStages": {
            "stage": "PROJECTION_SIMPLE",
            "nReturned": 29848,
            "executionTimeMillisEstimate": 1419,
            "works": 1615005,
            "advanced": 29848,
            "needTime": 1585156,
            "needYield": 0,
            "saveState": 12617,
            "restoreState": 12617,
            "isEOF": 1,
            "transformBy": {
                "avatarURL": -1
            },
            "inputStage": {
                "stage": "COLLSCAN",
                "filter": {
                    "rideNotes": {
                        "$regex": "receive"
                    }
                },
                "nReturned": 29848,
                "executionTimeMillisEstimate": 1400,
                "works": 1615005,
                "advanced": 29848,
                "needTime": 1585156,
                "needYield": 0,
                "saveState": 12617,
                "restoreState": 12617,
                "isEOF": 1,
                "direction": "forward",
                "docsExamined": 1615003
            }
        },
        "allPlansExecution": []
    },
    "ok": 1,
    "$clusterTime": {
        "clusterTime": Timestamp(1588806878, 53),
        "signature": {
            "hash": BinData(0, "rPDyp8XIs+c/T4nZVFPC22kskN0="),
            "keyId": NumberLong("6759458550222684161")
        }
    },
    "operationTime": Timestamp(1588806878, 53)
}

Wow! Das ist eine Menge mehr an Details. Eine Sache, die vielleicht sofort auffällt, ist executionTimeMillis beträgt fast 3 Sekunden. Das ist eine lange Zeit für eine Datenbankabfrage, insbesondere für eine benutzerseitige Abfrage. Wir wollen, dass unsere Benutzer Zeit sparen und nicht darauf warten, dass alles geladen wird, und das wird nicht nötig sein. Schauen wir uns eine der Phasen genauer an:

"inputStage" : {
        "stage" : "COLLSCAN",
        "filter" : {
          "rideNotes" : {
            "$regex" : "receive"
          }
        },
        "nReturned" : 29848,
        "executionTimeMillisEstimate" : 1400,
        "works" : 1615005,
        "advanced" : 29848,
        "needTime" : 1585156,
        "needYield" : 0,
        "saveState" : 12617,
        "restoreState" : 12617,
        "isEOF" : 1,
        "direction" : "forward",
        "docsExamined" : 1615003
      }

Hier gibt es ein paar Dinge zu beachten. Erstens können Sie sehen, wie viele Dokumente in dieser Phase zurückgegeben wurden nReturned. Je nach Ihrer Anfrage kann dies eine Zahl sein, mit der Sie zufrieden sind oder auch nicht. In einer perfekten Welt gibt jede Stufe so wenige Dokumente wie möglich zurück. In einigen Fällen kann es jedoch vorkommen, dass ein Nutzer tatsächlich nach einer großen Menge an Daten sucht, und in diesen Fällen benötigen Sie eine Paginierungsstrategie oder ähnliches.

Das nächste zu beachtende Feld ist möglicherweise das nützlichste im Abfrageplaner: docsExamined. Diese Zahl gibt an, wie viele Dokumente in einer bestimmten Phase geprüft wurden, und Sie wollen, dass diese Zahl niedrig ist. Noch wichtiger ist, dass Sie sich das Verhältnis zwischen docsExamined und nReturned. Anhand dieser Zahlen können Sie feststellen, wie viel Arbeit MongoDB leistet, um mir nützliche Daten zu liefern. Wir können hier sehen, dass unsere "Trefferquote" 29848 / 1615003 oder ~1,8 % beträgt. Das ist ziemlich schlecht - wir arbeiten auf einem absymalen Effizienzniveau. Wenn Ihr Cluster eine große Anzahl von Dokumenten im Verhältnis zu den zurückgegebenen Dokumenten untersucht, werden Sie wahrscheinlich einige Dinge beobachten:

  • insgesamt längere Abfragezeiten
  • bessere Nutzung der CPU- und Speicherressourcen Ihres Clusters
  • choppier cache residency and eviction
  • gesperrte und blockierte Abfragen unter Last

All dies führt zu einer schlechteren Leistung, mehr Aufwand und frustrierten Benutzern. Die gute Nachricht ist, dass Sie diese Fälle entweder in der Vorproduktion beim Testen von Abfragen abfangen können oder indem Sie den MongoDB-Profiler aktivieren, damit langsame Abfragen protokolliert werden und Benachrichtigungen erzeugen.

Spezielle Index-Typen

Die Indexarten von MongoDB sind im Allgemeinen für die meisten Anwendungsanforderungen ausreichend. Sie können Einzelfeld-, Verbund- und Multikey-Indizes für die meisten Anwendungsfälle verwenden. Wenn Sie einen Überblick über diese Indizes benötigen, lesen Sie einen unserer anderen Beiträge über die Leistung von MonogDB. Aber manchmal brauchen Sie etwas Spezielleres. Unsere obige Textsuchabfrage ist wahrscheinlich ein guter Fall; Regex-Suchen werden aufgrund ihrer Natur ziemlich langsam sein, egal was wir tun. Wir könnten eine weitere Einschränkung zu unserer Abfrage hinzufügen, um nur nach den Daten eines bestimmten Benutzers zu suchen, oder vielleicht unsere Indizierungsstrategie verbessern. Aber was könnten wir tun, wenn dies nicht möglich wäre? Mongo hat eine Antwort: Wir können wahrscheinlich den Mongo-Indextyp Volltextsuche verwenden. Ich sage "wahrscheinlich", weil die Volltextsuche eine Domäne für sich ist und es Systeme wie ElasticSearch gibt, die man für eine extrem anspruchsvolle Suche in Betracht ziehen sollte. Der Textindex von Mongo sollte uns zumindest weiter bringen, als es unsere Regex derzeit tut.

Fügen wir den Volltextindex zur rideNotes Feld:

db.riders.createIndex({ rideNotes: "text", description: "my first full-text index"})

Bevor wir versuchen, Abfragen mit dem Volltextindex durchzuführen, sollten Sie einige Dinge über diesen Typ wissen:

  • können Sie nur einen Volltextindex pro Sammlung haben. Er kann sich jedoch auf mehrere Felder beziehen
  • Dieser Indextyp benötigt in der Regel viel mehr Platz als andere, da die Datenstrukturen der Volltextsuche (Buchungslisten, Stoppwörter usw.) eine Rolle spielen. Dieser Index benötigt ~200 MB gegenüber 16,2 MB für den _id Index in dieser speziellen Sammlung.
  • Sie müssen eine bestimmte Abfragesyntax verwenden, um die Volltextsuche zu nutzen.
  • wie bei jedem Index kann ein zusätzlicher Index zu geringfügigen Leistungseinbußen beim Schreiben führen

Aber genug von den Kompromissen - probieren wir das Ding doch mal aus. Dies ist ein guter Zeitpunkt, um Sie zu warnen, dass die Ausgabe des Abfrageplaners ziemlich groß sein kann! Aber keine Sorge - Sie können es Schritt für Schritt angehen.

{
    "queryPlanner": {
        "plannerVersion": 1,
        "namespace": "floqars.riders",
        "indexFilterSet": false,
        "parsedQuery": {
            "$text": {
                "$search": "receive",
                "$language": "english",
                "$caseSensitive": false,
                "$diacriticSensitive": false
            }
        },
        "winningPlan": {
            "stage": "PROJECTION_SIMPLE",
            "transformBy": {
                "_id": -1
            },
            "inputStage": {
                "stage": "TEXT",
                "indexPrefix": {},
                "indexName": "rideNotes_text_description_text",
                "parsedTextQuery": {
                    "terms": ["receiv"],
                    "negatedTerms": [],
                    "phrases": [],
                    "negatedPhrases": []
                },
                "textIndexVersion": 3,
                "inputStage": {
                    "stage": "TEXT_MATCH",
                    "inputStage": {
                        "stage": "FETCH",
                        "inputStage": {
                            "stage": "OR",
                            "inputStage": {
                                "stage": "IXSCAN",
                                "keyPattern": {
                                    "_fts": "text",
                                    "_ftsx": 1
                                },
                                "indexName": "rideNotes_text_description_text",
                                "isMultiKey": true,
                                "isUnique": false,
                                "isSparse": false,
                                "isPartial": false,
                                "indexVersion": 2,
                                "direction": "backward",
                                "indexBounds": {}
                            }
                        }
                    }
                }
            }
        },
        "rejectedPlans": []
    },
    "executionStats": {
        "executionSuccess": true,
        "nReturned": 33820,
        "executionTimeMillis": 299,
        "totalKeysExamined": 33820,
        "totalDocsExamined": 33820,
        "executionStages": {
            "stage": "PROJECTION_SIMPLE",
            "nReturned": 33820,
            "executionTimeMillisEstimate": 222,
            "works": 33821,
            "advanced": 33820,
            "needTime": 0,
            "needYield": 0,
            "saveState": 264,
            "restoreState": 264,
            "isEOF": 1,
            "transformBy": {
                "_id": -1
            },
            "inputStage": {
                "stage": "TEXT",
                "nReturned": 33820,
                "executionTimeMillisEstimate": 208,
                "works": 33821,
                "advanced": 33820,
                "needTime": 0,
                "needYield": 0,
                "saveState": 264,
                "restoreState": 264,
                "isEOF": 1,
                "indexPrefix": {},
                "indexName": "rideNotes_text_description_text",
                "parsedTextQuery": {
                    "terms": ["receiv"],
                    "negatedTerms": [],
                    "phrases": [],
                    "negatedPhrases": []
                },
                "textIndexVersion": 3,
                "inputStage": {
                    "stage": "TEXT_MATCH",
                    "nReturned": 33820,
                    "executionTimeMillisEstimate": 206,
                    "works": 33821,
                    "advanced": 33820,
                    "needTime": 0,
                    "needYield": 0,
                    "saveState": 264,
                    "restoreState": 264,
                    "isEOF": 1,
                    "docsRejected": 0,
                    "inputStage": {
                        "stage": "FETCH",
                        "nReturned": 33820,
                        "executionTimeMillisEstimate": 206,
                        "works": 33821,
                        "advanced": 33820,
                        "needTime": 0,
                        "needYield": 0,
                        "saveState": 264,
                        "restoreState": 264,
                        "isEOF": 1,
                        "docsExamined": 33820,
                        "alreadyHasObj": 0,
                        "inputStage": {
                            "stage": "OR",
                            "nReturned": 33820,
                            "executionTimeMillisEstimate": 31,
                            "works": 33821,
                            "advanced": 33820,
                            "needTime": 0,
                            "needYield": 0,
                            "saveState": 264,
                            "restoreState": 264,
                            "isEOF": 1,
                            "dupsTested": 33820,
                            "dupsDropped": 0,
                            "inputStage": {
                                "stage": "IXSCAN",
                                "nReturned": 33820,
                                "executionTimeMillisEstimate": 26,
                                "works": 33821,
                                "advanced": 33820,
                                "needTime": 0,
                                "needYield": 0,
                                "saveState": 264,
                                "restoreState": 264,
                                "isEOF": 1,
                                "keyPattern": {
                                    "_fts": "text",
                                    "_ftsx": 1
                                },
                                "indexName": "rideNotes_text_description_text",
                                "isMultiKey": true,
                                "isUnique": false,
                                "isSparse": false,
                                "isPartial": false,
                                "indexVersion": 2,
                                "direction": "backward",
                                "indexBounds": {},
                                "keysExamined": 33820,
                                "seeks": 1,
                                "dupsTested": 33820,
                                "dupsDropped": 0
                            }
                        }
                    }
                }
            }
        }
    },
    "ok": 1,
    "$clusterTime": {
        "clusterTime": Timestamp(1588808094, 73),
        "signature": {
            "hash": BinData(0, "Y6IyUuYrIT7kMyn9Oob5OHZ9OWE="),
            "keyId": NumberLong("6759458550222684161")
        }
    },
    "operationTime": Timestamp(1588808094, 73)
}

Hier gibt es einiges zu beachten, aber beginnen wir mit der guten Nachricht: Das war viel, viel schneller! Hier sind die wichtigsten Ausführungsstatistiken, die uns am meisten interessieren:

"nReturned" : 33820,
"executionTimeMillis" : 299,
"totalKeysExamined" : 33820,
"totalDocsExamined" : 33820,

Der Abfrageplan war auch viel komplizierter als zuvor, und Sie haben vielleicht einige Dinge bemerkt, die wir vorher nicht abgedeckt haben, wie OR oder TEXT Phasen. Nehmen Sie sich eine Minute Zeit, um die Phasen durchzulesen und sie in einem Satz oder so zu beschreiben. Versuchen Sie, Dinge wie "ein Index wird gescannt und gibt IDs zurück" oder "eine FETCH-Phase ruft Dokumente von IDs ab" anzustreben. Sie sollten dies immer öfter tun, wenn Sie mit dem Abfrageplaner und -optimierer arbeiten. Mit der Zeit werden Sie "sehen", was Mongo tut, und sich überlegen, wie man die Dinge verbessern könnte. Vielleicht werden Sie auch überrascht sein, was der Optimierer tut, und das ist in Ordnung - probieren Sie verschiedene Kombinationen aus, um zu sehen, was Ihnen die besten Ergebnisse liefert. Der Abfrageoptimierer einer Datenbank wird oft im Laufe der Zeit vom Kernteam optimiert und wird in der Regel "kostenlos" besser und intelligenter, wenn Sie auf neuere Versionen der Datenbank aktualisieren. Wie cool ist das denn! Es kommt nicht oft vor, dass Dinge intelligenter und schneller werden, ohne dass der Benutzer direkt etwas dafür tun muss.

Wir können sehen, dass dieser Index für unseren Anwendungsfall besser geeignet ist: Er war etwa 10 Mal schneller (~300ms), lieferte mehr Ergebnisse und wir erhielten nur das zurück, was wir untersuchten (erinnern Sie sich an die wichtige nReturned / docsExamined Verhältnis). All dies zusammengenommen bedeutet, dass es sich um eine viel effizientere Abfrage handelt, und die dafür benötigte Zeit bestätigt dies auch. Darüber hinaus kann der Benutzer flexiblere Suchanfragen stellen, als dies mit der Regex möglich war. Hurra! Vergessen Sie aber nicht die Nachteile: Das schnellere Lesen geht mit einem leichten Nachteil beim Schreiben einher und nimmt eine Menge Speicherplatz in Anspruch.

Einpacken

Wir haben einen Blick auf die Arbeit mit dem Abfrageplaner und -optimierer geworfen, um unsere MongoDB-Abfragen zu verstehen und zu verbessern. Wir haben uns auch einige spezialisierte Index-Typen angesehen, die MongoDB für Fälle anbietet, in denen normale Indizes nicht ausreichen. Ich hoffe, dass Sie nun besser gewappnet sind, um langsame Abfragen zu bewältigen, und dass sich die Arbeit mit MongoDB weniger wie eine dunkle Kunst anfühlt. Viel Spaß beim Abfragen!

Mark Thomas

Mark ist ein Software-Ingenieur bei FloQast. Er genießt guten Kaffee, ein solides Gif und schöne Systeme.



Zurück zu Blog