MongoDB Sharding: Moderne Ansätze mit C++

Sharding ist ein horizontales Skalierungsverfahren, das große Datenmengen auf mehrere Server verteilt. Dieser Ansatz wird verwendet, um die Leistung und Skalierbarkeit von Datenbanken zu verbessern. Anstatt alle Daten auf einem einzigen Server zu speichern, teilt Sharding Sammlungen in Teile auf, die als Shards bezeichnet werden und auf verschiedene Knoten verteilt sind.

Jeder Shard enthält nur einen Teil der Daten, was es ermöglicht, Abfragen schneller zu verarbeiten, indem parallele Operationen auf mehreren Knoten genutzt werden. Dennoch greifen Benutzer und Anwendungen auf die Daten zu, als handele es sich um ein einziges, einheitliches System. Dies wird durch die Mongos-Komponente ermöglicht, die als Router fungiert: Sie empfängt Abfragen, analysiert sie und leitet sie an die entsprechenden Shards weiter.

Schlüsselelemente des Shardings

Sharded Cluster
Ein Sharded Cluster besteht aus drei Hauptkomponenten:

  • Mongos (Router): Ein Knoten, über den alle Anfragen an den Sharded Cluster geleitet werden. Er bestimmt anhand der Metadaten, welcher Shard eine Anfrage bearbeiten soll.
  • Konfigurationsserver: Diese Server speichern die Cluster-Metadaten, einschließlich Informationen über Shards, Shard-Schlüssel und Datenverteilung. Diese Metadaten sind entscheidend, um Anfragen korrekt zu routen.
  • Shard-Knoten: Diese Knoten speichern die eigentlichen Daten. Jeder Shard kann als eigenständiger replizierter Satz fungieren, um Fehlertoleranz und hohe Verfügbarkeit zu gewährleisten.

Shard-Schlüssel

Ein Shard-Schlüssel ist ein Feld oder eine Kombination von Feldern, die verwendet wird, um zu bestimmen, zu welchem Shard ein Dokument gehört. Zum Beispiel könnte ein user_id Feld in einer Sammlung als Shard-Schlüssel gewählt werden. Die Wahl des Shard-Schlüssels hat direkten Einfluss auf die Leistung, da er bestimmt, wie gleichmäßig die Daten auf die Shards verteilt werden.

Wichtige Anforderungen an einen Shard-Schlüssel:

  • Das Feld muss eine hohe Kardinalität aufweisen (d. h. viele eindeutige Werte haben).
  • Das Feld sollte in Abfragen verwendet werden, um ein effizientes Routing zu ermöglichen.
  • Das Feld muss unveränderlich sein, da es nicht möglich ist, den Shard-Schlüssel für ein bestehendes Dokument zu ändern.

Vorteile des Shardings

  • Horizontale Skalierung: Es können weitere Knoten zum Cluster hinzugefügt werden, um wachsende Datenmengen oder steigende Arbeitslasten zu bewältigen.
  • Lastverteilung: Daten und Abfragen werden gleichmäßig auf die Knoten verteilt, wodurch die Belastung einzelner Server reduziert wird.
  • Fehlertoleranz: Durch Replikation innerhalb jedes Shards wird das Risiko eines Datenverlusts minimiert und eine hohe Verfügbarkeit sichergestellt.

Wie Sharding funktioniert

Wenn ein neues Dokument zu einem Sharded Cluster hinzugefügt wird, verwendet MongoDB den Wert des Shard-Schlüssels, um zu bestimmen, welcher Knoten die Daten speichert. Wenn der Shard-Schlüssel beispielsweise user_id ist und der Cluster so konfiguriert ist, dass die Daten nach Wertebereichen aufgeteilt werden, könnte ein Dokument mit user_id = 500 in Shard A gespeichert werden, während ein Dokument mit user_id = 1500 in Shard B gespeichert wird.

Bei der Ausführung von Abfragen verwendet Mongos Metadaten von den Konfigurationsservern, um zu bestimmen, welche Shards die benötigten Daten enthalten. Wenn eine Abfrage mehrere Shards umfasst, aggregiert Mongos die Ergebnisse von jedem Knoten und liefert eine kombinierte Antwort zurück.

Konfiguration eines Sharded MongoDB Clusters

Ein Sharded MongoDB Cluster ermöglicht die Verteilung von Daten auf mehrere Knoten und bietet dadurch Skalierbarkeit und hohe Leistung. Für die Einrichtung müssen drei Hauptkomponenten bereitgestellt werden: der Router (Mongos), die Konfigurationsserver (Config Servers) und die Shard-Knoten (Shard Nodes).

Konfigurationsserver:

Diese Server speichern Metadaten über die Datenverteilung und werden mit dem Parameter –configsvr gestartet. Für Fehlertoleranz wird empfohlen, drei Konfigurationsserver zu verwenden.

Mongos Router:

Die mongos-Router, die für das Routing von Abfragen verantwortlich sind, verbinden sich über den Parameter –configdb mit den Konfigurationsservern.

Shard-Knoten:

Shard-Knoten, die die Daten speichern, werden mit dem Parameter –shardsvr gestartet und mit dem Befehl sh.addShard in der MongoDB-Shell zum Cluster hinzugefügt.

Nachdem diese Komponenten eingerichtet wurden, kann Sharding für die gewünschte Datenbank mit dem Befehl sh.enableSharding aktiviert werden. Sammlungen werden anschließend mithilfe eines Shard-Schlüssels, der mit dem Befehl sh.shardCollection festgelegt wird, auf die Knoten verteilt. Dieser Shard-Schlüssel bestimmt, wie die Daten auf die Shards verteilt werden.

Überwachung des Clusters:

Der Betrieb des Clusters kann mit dem Befehl sh.status überwacht werden. Dieser liefert Informationen über die Datenverteilung, den Status der Knoten und die Shard-Schlüssel. Diese Architektur bildet eine zuverlässige Grundlage für die Verwaltung großer Datenmengen in MongoDB.

Sharding MongoDB und Arbeiten mit C++: Verbindungen und Abfragen

Beim Arbeiten mit MongoDB aus C++ sollte der MongoDB C++ Driver verwendet werden, da er Werkzeuge zur Interaktion mit einem Sharded Cluster bereitstellt.

Beispiel für die Verbindung mit einem Sharded Cluster:

#include <mongocxx/client.hpp>
#include <mongocxx/instance.hpp>
#include <mongocxx/uri.hpp>
 
int main() {
    mongocxx::instance instance{};
    // Connect via URI to mongos
    auto uri = mongocxx::uri{"mongodb://mongos1:27017,mongos2:27017/?replicaSet=shardedCluster"};
    mongocxx::client client{uri};
 
    auto db = client["my_database"];
    auto collection = db["my_collection"];
 
    auto result = collection.find_one({});
    if (result) {
    	std::cout << bsoncxx::to_json(*result) << std::endl;
    }
    return 0;
}

 

Verarbeitung von Daten in einem Sharded Cluster:

Alle Abfragen werden über mongos gesendet, der sie an die entsprechenden Shards weiterleitet.
Der Treiber übernimmt die Routingsprozesse transparent auf Anwendungsebene.

Abfrageempfehlungen für einen Sharded Cluster:

  • Verwenden Sie Filter, die den Shard-Schlüssel enthalten, um das Routing zu optimieren.
  • Vermeiden Sie Scatter-Gather-Abfragen, die alle Shards gleichzeitig ansprechen.
  • Nutzen Sie Indizes, insbesondere auf den Shard-Schlüsseln, um die Abfrageleistung zu verbessern.

Empfehlungen für die Auswahl eines Shard-Schlüssels

Die Wahl eines geeigneten Shard-Schlüssels ist ein entscheidender Schritt, der die Leistung und die Datenbalance direkt beeinflusst. Hier sind die wichtigsten Empfehlungen:

  1. Hohe Kardinalität:
    Der Shard-Schlüssel sollte eine große Anzahl eindeutiger Werte enthalten, um die Daten gleichmäßig auf die Shards zu verteilen. Gute Beispiele sind Benutzer-IDs (user_id) oder UUIDs.
  2. Abfragehäufigkeit:
    Wählen Sie Felder, die häufig in Abfragefiltern verwendet werden. Dadurch können Abfragen gezielt an spezifische Shards geleitet werden, was die Anzahl der Operationen reduziert.
  3. Datenverteilung:
    Stellen Sie sicher, dass die Daten gleichmäßig auf die Shards verteilt sind. Zum Beispiel können auto-generierte IDs zu einer ungleichmäßigen Lastverteilung führen, da neue Datensätze häufig in denselben Bereich fallen.
  4. Unveränderlichkeit:
    Shard-Schlüsselfelder dürfen sich nicht ändern, da MongoDB die Aktualisierung des Shard-Schlüssels eines Dokuments nach der Einfügung nicht unterstützt.

  5. Zusammengesetzte Shard-Schlüssel:
    In einigen Fällen ist es sinnvoll, einen zusammengesetzten Shard-Schlüssel zu verwenden, der aus mehreren Feldern besteht. Zum Beispiel:
    sh.shardCollection("datenbank_name.sammlung_name", { region: 1, user_id: 1 });

  6. Hotspots vermeiden:
    Stellen Sie sicher, dass der Shard-Schlüssel nicht zu Hotspots führt. Beispielsweise könnte die Verwendung des aktuellen Datums als Shard-Schlüssel einen einzelnen Shard überlasten.

Leistungsoptimierung in C++ für MongoDB

Wenn eine C++ Anwendung mit einem MongoDB-Sharded-Cluster interagiert, ist die Optimierung der Leistung entscheidend, insbesondere wenn der Cluster skaliert wird, um große Datenmengen oder hohe Abfragelasten zu bewältigen. Nachfolgend finden Sie bewährte Strategien zur Leistungssteigerung:

Batch-Insertion von Dokumenten

Das Batchen von Dokumenten-Insertions reduziert die Anzahl der Netzwerkaufrufe zwischen dem Client und MongoDB. Anstatt mehrere insert_one-Operationen auszuführen, verwenden Sie die Methode insert_many, um mehrere Dokumente in einer einzigen Anfrage zu senden. Dies reduziert den Netzwerkaufwand und verbessert die Gesamtleistung.

std::vector<bsoncxx::document::value> documents = {
    bsoncxx::builder::stream::document{} << "name" << "Alice" << bsoncxx::builder::stream::finalize,
    bsoncxx::builder::stream::document{} << "name" << "Bob" << bsoncxx::builder::stream::finalize
};
 
auto result = collection.insert_many(documents);
if (result) {
    std::cout << "Inserted " << result->inserted_count() << " documents." << std::endl;
}

Dieser Ansatz schont nicht nur die Netzwerkressourcen, sondern optimiert auch die Nutzung des MongoDB-Servers, indem Insertions als ein einzelnes Batch verarbeitet werden.

Minimierung von Metadaten-Abfragen

Bei der Arbeit mit Sharded Clustern können Metadaten-Abfragen (z. B. Informationen zum Shard-Schlüssel) zum Engpass werden. Metadaten wie Shard-Konfigurationen und Indexstrukturen werden auf den Konfigurationsservern gespeichert. Jedes Mal, wenn ein Client Informationen darüber benötigt, zu welchem Shard eine Abfrage weitergeleitet werden soll, ruft er diese Metadaten ab.

Um diese Abfragen zu minimieren:

  • Shard-Schlüssel im Speicher cachen: Wenn Ihr Shard-Schlüssel fest und im Voraus bekannt ist, speichern Sie ihn in Ihrer Anwendung und verwenden Sie ihn wieder, anstatt MongoDB wiederholt abzufragen.
  • Lesepräferenzen verwenden: Leiten Sie Metadaten-Abfragen an Replikat-Knoten statt an Primärknoten weiter, um die Belastung der Primärserver zu reduzieren.
  • Metadaten nur bei Änderungen der Shard-Konfiguration aktualisieren: Aktualisieren Sie den Metadaten-Cache nur, wenn sich die Shard-Konfigurationen ändern.

Shard-Level Aggregation

Um die übertragenen Daten zwischen Shards und dem Client zu minimieren, führen Sie Aggregationen auf Shard-Ebene durch. Die Aggregations-Pipeline von MongoDB ermöglicht das Filtern und Gruppieren von Daten direkt auf den Shards, wodurch Netzwerk- und CPU-Last reduziert werden.

auto pipeline = bsoncxx::builder::stream::array{}
    << bsoncxx::builder::stream::document{}
   	<< "$match" << bsoncxx::builder::stream::open_document
   	<< "shardKey" << 123
   	<< bsoncxx::builder::stream::close_document
   	<< bsoncxx::builder::stream::finalize
    << bsoncxx::builder::stream::document{}
   	<< "$group" << bsoncxx::builder::stream::open_document
   	<< "_id" << "$groupField"
   	<< "count" << bsoncxx::builder::stream::open_document
   	<< "$sum" << 1
      << bsoncxx::builder::stream::close_document
   	<< bsoncxx::builder::stream::close_document
   	<< bsoncxx::builder::stream::finalize;
 
auto cursor = collection.aggregate(pipeline);
for (auto&& doc : cursor) {
    std::cout << bsoncxx::to_json(doc) << std::endl;
}

Dieses Beispiel filtert Daten nach Shard-Schlüssel ($match) und gruppiert Daten ($group). Alle Berechnungen erfolgen auf der MongoDB-Server-Seite, wodurch unnötige Datenübertragungen vermieden werden.

Daten Vorfiltern

Filtern Sie bei Abfragen immer mit Shard-Schlüsseln. Abfragen, die einen Filter auf den Shard-Schlüssel enthalten, werden an den entsprechenden Shard weitergeleitet, was die Clusterlast erheblich reduziert. Ohne einen Filter auf den Shard-Schlüssel muss MongoDB die Abfrage an alle Shards senden, was die Latenz erhöht.

auto filter = bsoncxx::builder::stream::document{}
    << "shardKey" << 123
    << bsoncxx::builder::stream::finalize;
 
auto result = collection.find_one(filter);
if (result) {
    std::cout << "Found document: " << bsoncxx::to_json(*result) << std::endl;
}
 

Verwendung von Indizierten Abfragen

Stellen Sie sicher, dass häufig verwendete Abfragen mit Indizes optimiert werden. In einem Sharded Cluster sind Indizes auf Shard-Schlüsseln besonders wichtig, da sie direkt die Abfrageweiterleitung beeinflussen.

auto index = bsoncxx::builder::stream::document{}
    << "shardKey" << 1
    << bsoncxx::builder::stream::finalize;
 
collection.create_index(index);

Indizes reduzieren die Anzahl der zu verarbeitenden Dokumente und verringern die Abfrage-Latenz.

Benchmarking und Testen von Sharded-Lösungen

Um die Leistung eines Sharded Clusters zu bewerten, ist es wichtig, regelmäßige Tests durchzuführen, die sowohl synthetische Lasten als auch realistische Abfrageszenarien umfassen. Tools wie der MongoDB Atlas Performance Advisor oder benutzerdefinierte C++ Lasttestscripte helfen dabei, Leistungsengpässe zu identifizieren. Das Benchmarking sollte Lese-, Schreib- und Aggregationsabfragen unter Bedingungen der Ressourcen-Konkurrenz abdecken.

Das Testen des Sharded Clusters auf Anwendungsebene hilft, Probleme mit Shard-Schlüsseln, ungleichmäßiger Datenverteilung und Latenz im Zusammenhang mit der Abfrageweiterleitung aufzudecken. Es ist entscheidend zu verstehen, wie Daten über die Knoten verteilt sind: Homogene Daten verbessern die Leistung, während eine ungleiche Verteilung zu Überlastungen auf bestimmten Knoten führen kann.

Es wird außerdem empfohlen, Abfrage-Profiling zu verwenden, indem der Abfrageprofiler von MongoDB aktiviert wird. Dies hilft, langsame Abfragen zu identifizieren und durch die Modifikation von Indizes oder die Überarbeitung von Shard-Schlüsseln zu optimieren. In einer C++ Anwendung können Leistungskennzahlen mit externen Bibliotheken wie Prometheus oder gRPC integriert werden, um Latenz, Fehler und Durchsatz zu überwachen.

Fazit

Die effektive Nutzung von MongoDB Sharding erfordert eine sorgfältige Cluster-Konfiguration und die richtige Wahl des Shard-Schlüssels, der entscheidend für Skalierbarkeit und hohe Leistung ist. Eine falsche Auswahl des Shard-Schlüssels kann zu Lastenungleichgewicht und verminderter Leistung führen.

Für C++ Entwickler ist es wichtig, die Interaktionen mit einem Sharded Cluster zu optimieren, beispielsweise durch die Verwendung von Batch-Dokumenten-Insertions (insert_many), das Caching von Metadaten und die Durchführung von Aggregationen auf Shard-Ebene, um den Datenverkehr zu minimieren. Diese Ansätze reduzieren die Serverlast und beschleunigen die Anwendungsleistung.

Besondere Aufmerksamkeit sollte der Verwendung von Indizes, asynchronen Operationen und Fehlerbehandlung zur Gewährleistung der Fehlertoleranz gewidmet werden. Um die Leistung weiter zu steigern, können Verbindungspooling und asynchrone Abfragen angewendet werden.

MongoDB Sharding, kombiniert mit C++, hilft dabei, Systeme effektiv zu skalieren und die Fehlertoleranz zu gewährleisten. Regelmäßige Überwachung und Tests helfen, Engpässe zu identifizieren und eine hohe Datenbank- und Anwendungsleistung im gesamten Lebenszyklus des Systems aufrechtzuerhalten.

Möchten Sie effektives Sharding in MongoDB für Ihr Unternehmen implementieren? Wir bieten professionelle Outsourcing-Entwicklung und Unterstützung bei der Einrichtung skalierbarer Lösungen. Kontaktieren Sie uns hier.

Kontakt
Kontakt


    Insert math as
    Block
    Inline
    Additional settings
    Formula color
    Text color
    #333333
    Type math using LaTeX
    Preview
    \({}\)
    Nothing to preview
    Insert