MongoDB
 sql >> Datenbank >  >> NoSQL >> MongoDB

So optimieren Sie die Leistung von MongoDB

Eine hervorragende Datenbankleistung ist wichtig, wenn Sie Anwendungen mit MongoDB entwickeln. Manchmal kann der gesamte Datenbereitstellungsprozess aus einer Reihe von Gründen beeinträchtigt werden, darunter:

  • Unangemessene Schemaentwurfsmuster
  • Unsachgemäße Verwendung oder Nichtverwendung von Indizierungsstrategien
  • Unzureichende Hardware
  • Replikationsverzögerung
  • Schlechte Abfragetechniken

Einige dieser Rückschläge zwingen Sie möglicherweise dazu, die Hardwareressourcen zu erhöhen, während andere dies möglicherweise nicht tun. Beispielsweise können schlechte Abfragestrukturen dazu führen, dass die Verarbeitung der Abfrage lange dauert, was zu Verzögerungen bei der Replikation und möglicherweise sogar zu Datenverlusten führt. In diesem Fall könnte man denken, dass der Speicher möglicherweise nicht ausreicht und wahrscheinlich vergrößert werden muss. Dieser Artikel beschreibt die am besten geeigneten Verfahren, mit denen Sie die Leistung Ihrer MongoDB-Datenbank steigern können.

Schemadesign

Grundsätzlich sind die zwei am häufigsten verwendeten Schemabeziehungen...

  • One-to-Few
  • Eins-zu-Viele

Während das effizienteste Schemadesign die Eins-zu-Viele-Beziehung ist, hat jede ihre eigenen Vorzüge und Einschränkungen.

One-to-Few

In diesem Fall gibt es für ein bestimmtes Feld eingebettete Dokumente, die jedoch nicht mit der Objektidentität indiziert sind.

Hier ist ein einfaches Beispiel:

{
      userName: "Brian Henry",
      Email : "[email protected]",
      grades: [
             {subject: ‘Mathematics’,  grade: ‘A’},
             {subject: English,  grade: ‘B’},
      ]
}

Ein Vorteil dieser Beziehung besteht darin, dass Sie die eingebetteten Dokumente mit nur einer einzigen Abfrage abrufen können. Aus Abfragesicht können Sie jedoch nicht auf ein einzelnes eingebettetes Dokument zugreifen. Wenn Sie eingebettete Dokumente also nicht separat referenzieren, ist es optimal, dieses Schemadesign zu verwenden.

Eins-zu-Viele

Bei dieser Beziehung werden Daten in einer Datenbank mit Daten in einer anderen Datenbank in Beziehung gesetzt. Beispielsweise können Sie eine Datenbank für Benutzer und eine andere für Beiträge haben. Wenn also ein Benutzer einen Beitrag erstellt, wird er mit der Benutzer-ID aufgezeichnet.

Benutzerschema

{ 
    Full_name: “John Doh”,
    User_id: 1518787459607.0
}

Beitragsschema

{
    "_id" : ObjectId("5aa136f0789cf124388c1955"),
    "postTime" : "16:13",
    "postDate" : "8/3/2018",
    "postOwnerNames" : "John Doh",
    "postOwner" : 1518787459607.0,
    "postId" : "1520514800139"
}

Der Vorteil bei diesem Schemadesign ist, dass die Dokumente als eigenständig betrachtet werden (separat wählbar). Ein weiterer Vorteil ist, dass dieses Design es Benutzern mit unterschiedlichen IDs ermöglicht, Informationen aus dem Posts-Schema (daher der Name One-to-Many) zu teilen und manchmal ein „N-to-N“-Schema sein kann – im Grunde ohne die Verwendung von Tabellenverknüpfungen. Die Einschränkung bei diesem Schemadesign besteht darin, dass Sie mindestens zwei Abfragen durchführen müssen, um Daten in der zweiten Sammlung abzurufen oder auszuwählen.

Wie die Daten modelliert werden, hängt daher vom Zugriffsmuster der Anwendung ab. Außerdem müssen Sie das Schema-Design berücksichtigen, das wir oben besprochen haben.

Optimierungstechniken für das Schemadesign

  1. Verwenden Sie die Dokumenteneinbettung so weit wie möglich, da dies die Anzahl der Abfragen reduziert, die Sie für einen bestimmten Datensatz ausführen müssen.

  2. Verwenden Sie die Denormalisierung nicht für Dokumente, die häufig aktualisiert werden. Wenn anfield häufig aktualisiert wird, besteht die Aufgabe darin, alle Instanzen zu finden, die aktualisiert werden müssen. Dies führt zu einer langsamen Abfrageverarbeitung, wodurch sogar die mit der Denormalisierung verbundenen Vorteile überwältigt werden.

  3. Wenn ein Dokument separat abgerufen werden muss, ist es nicht erforderlich, die Einbettung zu verwenden, da die Ausführung komplexer Abfragen wie Aggregat-Pipelining mehr Zeit in Anspruch nimmt.

  4. Wenn das Array der einzubettenden Dokumente groß genug ist, betten Sie sie nicht ein. Das Array-Wachstum sollte zumindest eine begrenzte Grenze haben.

Richtige Indexierung

Dies ist der wichtigere Teil der Leistungsoptimierung und erfordert ein umfassendes Verständnis der Anwendungsabfragen, des Verhältnisses von Lese- zu Schreibvorgängen und des freien Speichers Ihres Systems. Wenn Sie einen Index verwenden, scannt die Abfrage den Index und nicht die Sammlung.

Ein ausgezeichneter Index ist einer, der alle von einer Abfrage gescannten Felder umfasst. Dies wird als zusammengesetzter Index bezeichnet.

Um einen einzelnen Index für ein Feld zu erstellen, können Sie diesen Code verwenden:

db.collection.createIndex({“fields”: 1})

Für einen zusammengesetzten Index, um die Indizierung zu erstellen:

db.collection.createIndex({“filed1”: 1, “field2”:  1})

Neben einer schnelleren Abfrage durch Indexierung gibt es einen zusätzlichen Vorteil anderer Operationen wie Sortieren, Abtasten und Begrenzen. Wenn ich zum Beispiel mein Schema als {f:1, m:1} entwerfe, kann ich eine zusätzliche Operation neben find as

ausführen
db.collection.find( {f: 1} ).sort( {m: 1} )

Das Lesen von Daten aus dem RAM ist effizienter als das Lesen derselben Daten von der Festplatte. Aus diesem Grund ist es immer ratsam sicherzustellen, dass Ihr Index vollständig in den Arbeitsspeicher passt. Um die aktuelle indexSize Ihrer Sammlung zu erhalten, führen Sie den Befehl :

aus
db.collection.totalIndexSize()

Sie erhalten einen Wert wie 36864 Bytes. Dieser Wert sollte auch keinen großen Prozentsatz der gesamten RAM-Größe einnehmen, da Sie die Anforderungen des gesamten Arbeitssatzes des Servers erfüllen müssen.

Eine effiziente Abfrage sollte auch die Selektivität verbessern. Selektivität kann als die Fähigkeit einer Abfrage definiert werden, das Ergebnis mithilfe des Index einzugrenzen. Um sekanter zu sein, sollten Ihre Abfragen die Anzahl möglicher Dokumente mit dem indizierten Feld begrenzen. Selektivität wird meistens mit einem zusammengesetzten Index in Verbindung gebracht, der ein Feld mit niedriger Selektivität und ein weiteres Feld enthält. Zum Beispiel, wenn Sie diese Daten haben:

{ _id: ObjectId(), a: 6, b: "no", c: 45 }
{ _id: ObjectId(), a: 7, b: "gh", c: 28 }
{ _id: ObjectId(), a: 7, b: "cd", c: 58 }
{ _id: ObjectId(), a: 8, b: "kt", c: 33 }

Die Abfrage {a:7, b:„cd“} durchsucht 2 Dokumente, um 1 übereinstimmendes Dokument zurückzugeben. Wenn jedoch die Daten für den Wert a gleichmäßig verteilt sind, d. h.

{ _id: ObjectId(), a: 6, b: "no", c: 45 }
{ _id: ObjectId(), a: 7, b: "gh", c: 28 }
{ _id: ObjectId(), a: 8, b: "cd", c: 58 }
{ _id: ObjectId(), a: 9, b: "kt", c: 33 }

Die Abfrage {a:7, b:„cd“} durchsucht 1 Dokument und gibt dieses Dokument zurück. Daher dauert dies kürzer als die erste Datenstruktur.

ClusterControlEine Konsole für Ihre gesamte DatenbankinfrastrukturErfahren Sie, was es sonst noch Neues in ClusterControl gibt. Installieren Sie ClusterControl KOSTENLOS

Ressourcenbereitstellung

Unzureichender Speicher, RAM und andere Betriebsparameter können die Leistung einer MongoDB drastisch beeinträchtigen. Wenn beispielsweise die Anzahl der Benutzerverbindungen sehr groß ist, wird die Serveranwendung daran gehindert, Anforderungen rechtzeitig zu bearbeiten. Wie in Wichtige Dinge, die in MongoDB überwacht werden sollten, besprochen, können Sie sich einen Überblick darüber verschaffen, welche begrenzten Ressourcen Sie haben und wie Sie sie entsprechend Ihren Spezifikationen skalieren können. Bei einer großen Anzahl gleichzeitiger Anwendungsanfragen wird das Datenbanksystem überfordert sein, um mit der Nachfrage Schritt zu halten.

Replikationsverzögerung

Manchmal stellen Sie möglicherweise fest, dass einige Daten in Ihrer Datenbank fehlen oder wenn Sie etwas löschen, wird es erneut angezeigt. So sehr Sie ein gut entworfenes Schema, eine angemessene Indizierung und genügend Ressourcen haben könnten, Ihre Anwendung wird am Anfang reibungslos und ohne Schluckauf laufen, aber irgendwann bemerken Sie die letztgenannten Probleme. MongoDB stützt sich auf ein Replikationskonzept, bei dem Daten redundant kopiert werden, um einige Designkriterien zu erfüllen. Eine Annahme dabei ist, dass der Prozess augenblicklich ist. Es kann jedoch aufgrund von Netzwerkausfällen oder nicht behandelten Fehlern zu Verzögerungen kommen. Kurz gesagt, es wird eine große Lücke zwischen der Zeit geben, mit der eine Operation auf dem primären Knoten verarbeitet wird, und der Zeit, zu der sie auf dem sekundären Knoten angewendet wird.

Rückschläge mit Replica-Lags

  1. Inkonsistente Daten. Dies ist insbesondere mit Lesevorgängen verbunden, die über Sekundärknoten verteilt sind.

  2. Wenn die Verzögerungslücke groß genug ist, befinden sich möglicherweise viele nicht replizierte Daten auf dem primären Knoten und müssen im sekundären Knoten abgeglichen werden. An einem bestimmten Punkt kann dies unmöglich sein, insbesondere wenn der primäre Knoten nicht wiederhergestellt werden kann.

  3. Wenn der primäre Knoten nicht wiederhergestellt wird, kann dies dazu führen, dass ein Knoten mit Daten ausgeführt wird, die nicht auf dem neuesten Stand sind, und folglich kann die gesamte Datenbank gelöscht werden, damit der primäre Knoten wiederhergestellt werden kann.

Ursachen für den Ausfall des sekundären Knotens

  1. Übertreffen der Primärleistung gegenüber der Sekundärleistung in Bezug auf die CPU-, Festplatten-IOPS- und Netzwerk-E/A-Spezifikationen.

  2. Komplexe Schreiboperationen. Zum Beispiel ein Befehl wie

    db.collection.update( { a: 7}  , {$set: {m: 4} }, {multi: true} )

    Der primäre Knoten zeichnet diese Operation schnell genug im Oplog auf. Für den sekundären Knoten muss er jedoch diese Operationen abrufen und alle Index- und Datenseiten in den RAM lesen, um einige Kriterienspezifikationen wie die ID zu erfüllen. Da dies schnell genug geschehen muss, um die Rate mit dem primären Knoten beizubehalten, der die Operation ausführt, wird es eine erwartete Verzögerung geben, wenn die Anzahl der Operationen groß genug ist.

  3. Sperren der Sekundärseite beim Erstellen eines Backups. In diesem Fall vergessen wir möglicherweise, die primäre zu deaktivieren, und werden daher mit ihrem Betrieb wie gewohnt fortfahren. Zu dem Zeitpunkt, an dem die Sperre aufgehoben wird, wird die Replikationsverzögerung eine große Lücke aufweisen, insbesondere wenn es sich um eine große Menge an Datensicherungen handelt.

  4. Indexaufbau. Wenn sich ein Index im sekundären Knoten aufbaut, werden alle anderen damit verbundenen Operationen blockiert. Wenn der Index lange ausgeführt wird, tritt ein Schluckauf bei der Replikationsverzögerung auf.

  5. Nicht angeschlossene Sekundärseite. Manchmal kann der sekundäre Knoten aufgrund von Netzwerkunterbrechungen ausfallen und dies führt zu einer Replikationsverzögerung, wenn er wieder verbunden wird.

So minimieren Sie die Replikationsverzögerung

  • Verwenden Sie neben Ihrer Sammlung mit dem _id-Feld eindeutige Indizes. Dadurch soll verhindert werden, dass der Replikationsprozess vollständig fehlschlägt.

  • Erwägen Sie andere Backup-Typen wie Point-in-Time- und Dateisystem-Snapshots, die nicht unbedingt gesperrt werden müssen.

  • Vermeiden Sie das Erstellen großer Indizes, da diese eine Hintergrundblockierung verursachen.

  • Machen Sie die Sekundärseite stark genug. Wenn der Schreibvorgang leicht ist, dann ist die Verwendung von Sekundärteilen mit geringer Leistung wirtschaftlich. Bei hohen Schreiblasten kann der sekundäre Knoten jedoch hinter dem primären zurückbleiben. Um Sekcanter zu sein, sollte der sekundäre Knoten über genügend Bandbreite verfügen, um Oplogs schnell genug lesen zu können, um seine Rate mit dem primären Knoten zu halten.

Effiziente Abfragetechniken

Neben dem Erstellen von indizierten Abfragen und der Verwendung der Abfrageselektivität, wie oben beschrieben, gibt es noch andere Konzepte, die Sie verwenden können, um Ihre Abfragen zu beschleunigen und effektiver zu machen.

Optimieren Ihrer Suchanfragen

  1. Verwenden einer verdeckten Abfrage. Eine abgedeckte Abfrage ist eine, die immer vollständig durch einen Index erfüllt wird und daher kein Dokument untersuchen muss. Die abgedeckte Abfrage sollte daher alle Felder als Teil des Index haben und folglich sollte das Ergebnis alle diese Felder enthalten.

    Betrachten wir dieses Beispiel:

    {_id: 1, product: { price: 50 }

    Wenn wir einen Index für diese Sammlung erstellen als

    {“product.price”: 1} 

    Betrachtet man eine Suchoperation, deckt dieser Index diese Abfrage ab;

    db.collection.find( {“product.price”: 50}, {“product.price”: 1, _id: 0}  )

    und nur das Feld product.price und den Wert zurückgeben.

  2. Verwenden Sie für eingebettete Dokumente die Punktnotation (.). Die Punktnotation hilft beim Zugriff auf Elemente eines Arrays und Felder eingebetteter Dokumente.

    Zugriff auf ein Array:

    {
       prices: [12, 40, 100, 50, 40]  
    }

    Um beispielsweise das vierte Element anzugeben, können Sie diesen Befehl schreiben:

    “prices.3”

    Zugriff auf ein Objektarray:

    {
    
       vehicles: [{name: toyota, quantity: 50},
                 {name: bmw, quantity: 100},
                 {name: subaru, quantity: 300}                    
    } 

    Um das Namensfeld im Fahrzeug-Array anzugeben, können Sie diesen Befehl verwenden

    “vehicles.name”
  3. Überprüfen Sie, ob eine Abfrage abgedeckt ist. Verwenden Sie dazu db.collection.explain(). Diese Funktion gibt Auskunft über die Ausführung anderer Operationen - z.B. db.collection.explain().aggregate(). Um mehr über die EXPLAIN-Funktion zu erfahren, können Sie sich EXPLAIN() anschauen.

Im Allgemeinen ist die beste Technik in Bezug auf Abfragen die Verwendung von Indizes. Das Abfragen nur eines Indexes ist viel schneller als das Abfragen von Dokumenten außerhalb des Indexes. Sie können in den Speicher passen und sind daher eher im RAM als auf der Festplatte verfügbar. Dies macht es einfach und schnell genug, sie aus dem Speicher abzurufen.