Datenbankskalierung durch Sharding und Partitionierung



    Datenbankskalierung durch Sharding und Partitionierung


    Denis Ivanov (2GIS)


    Hallo allerseits! Mein Name ist Denis Ivanov und ich werde über das Skalieren von Datenbanken durch Sharding und Partitionierung sprechen. Nach diesem Bericht sollte jeder Lust auf Partys haben, etwas zerbrechen, Sie werden verstehen, dass es sehr einfach ist, überhaupt nicht nach Essen fragt, es funktioniert und alles in Ordnung ist.

    Ich erzähle Ihnen ein wenig über mich selbst - ich arbeite im WebAPI-Team von 2GIS, wir stellen APIs für Organisationen bereit, wir haben viele verschiedene Daten, 8 Länder, in denen wir arbeiten, 250 große Städte, 50.000 Siedlungen. Wir haben eine ziemlich große Auslastung - 25 Millionen aktive Benutzer pro Monat, und die API wird durchschnittlich mit ca. 2000 RPS ausgelastet. All dies befindet sich in drei Rechenzentren.

    Kommen wir zu den Problemen, die wir heute lösen werden. Eines der Probleme ist die große Datenmenge. Wenn Sie ein bestimmtes Projekt entwickeln, kann es jederzeit vorkommen, dass viele Daten vorhanden sind. Wenn das Geschäft funktioniert, bringt es Geld. Dementsprechend gibt es mehr Daten, mehr Geld, und es muss etwas mit diesen Daten getan werden, da diese Anforderungen sehr lange ausgeführt werden und der Server nicht von uns exportiert. Eine Lösung für diese Daten besteht darin, die Datenbank zu skalieren.

    Ich werde mehr über Scherben sprechen. Es ist vertikal und horizontal. Es gibt auch eine Art der Skalierung als Replikation. Der Bericht " Wie MySQL Replication funktioniert " von Andrei Aksenov von Sphinx befasste sich damit. Ich werde dieses Thema kaum behandeln.

    Kommen wir zum Thema Partitionierung (vertikales Sharding). Wie sieht das alles aus?



    Wir haben zum Beispiel einen großen Tisch mit Benutzern - wir haben viele Benutzer. Partitionierung ist, wenn wir eine große Tabelle nach einem Prinzip in viele kleine aufteilen.
    Bei der horizontalen Scherbe ist alles ungefähr gleich, aber zur gleichen Zeit befinden sich unsere Platten an anderen Stellen in unterschiedlichen Basen.



    Der einzige Unterschied zwischen horizontaler und vertikaler Skalierung besteht darin, dass durch horizontale Skalierung die Daten auf verschiedene Instanzen verteilt werden.

    Über die Replikation werde ich nicht aufhören, hier ist alles sehr einfach.



    Wir werden uns eingehender mit diesem Thema befassen, und ich werde fast alles über das Partitionieren am Beispiel von Postgres erzählen.
    Schauen wir uns ein einfaches Tablet an, sicher haben fast 99% der Projekte ein solches Tablet - das sind Neuigkeiten.



    Die Nachrichten haben einen Bezeichner, es gibt eine Kategorie, in der sich diese Nachrichten befinden, es gibt einen Autor der Nachrichten, seine Bewertung und eine Art Überschrift - eine vollständig standardisierte Tabelle, es gibt nichts Kompliziertes.

    Wie teilt man diese Tabelle in mehrere? Wo soll ich anfangen?

    Insgesamt müssen Sie 2 Aktionen auf dem Teller ausführen - dies wird in unseren Shard eingefügt, zum Beispiel news_1, damit er von der Newstabelle geerbt wird. News ist die Basistabelle, sie enthält die gesamte Struktur, und wenn wir die Partition erstellen, geben wir an, dass sie von unserer Basistabelle geerbt wird. Die geerbte Tabelle enthält alle Spalten der übergeordneten Tabelle - die von uns angegebene Basistabelle. Sie kann auch eigene Spalten enthalten, die wir dort hinzufügen. Es handelt sich um eine vollständige Tabelle, die jedoch vom übergeordneten Element geerbt wird, und es gibt keine Einschränkungen, Indizes oder Trigger vom übergeordneten Element. Dies ist sehr wichtig. Wenn Sie Indizes für die Basistabelle erstellen und sie erben, enthält die geerbte Indextabelle keine Indizes, Einschränkungen oder Trigger.

    Die zweite Maßnahme besteht darin, Grenzen zu setzen. Dies ist eine Überprüfung, dass Daten mit diesem Attribut nur in diese Tabelle gelangen.



    In diesem Fall ist das Attribut category_id = 1, d.h. nur Einträge mit category_id = 1 fallen in diese Tabelle.
    Welche Arten von Prüfungen gibt es für partitionierte Tabellen?



    Es gibt eine strenge Bedeutung, d.h. Wir haben ein Feld, das eindeutig einem Feld entspricht. Es gibt eine Werteliste - dies ist ein Eintrag in der Liste. Wir können beispielsweise 3 Nachrichtenautoren in dieser bestimmten Partition haben, und es gibt einen Wertebereich - dies ist, von welchem ​​und zu welchem ​​Wert die Daten gespeichert werden.

    Hier müssen wir näher darauf eingehen, da die Prüfung vom Operator BETWEEN unterstützt wird. Sie alle wissen es.



    Und so können Sie es so einfach machen. Das kannst du aber nicht. Dies ist möglich, da wir dies tun dürfen. PostgreSQL unterstützt dies. Wie Sie sehen, erhalten wir in unserer ersten Partition Daten zwischen 100 und 200 und in der zweiten Partition zwischen 200 und 300. Welche dieser Partitionen erhält einen Rekord mit einer Bewertung von 200? Es ist nicht bekannt, wie viel Glück. Daher kann dies nicht durchgeführt werden, es muss ein strenger Wert angegeben werden, d.h. Streng genommen fallen in der ersten Partition Werte über 100 und unter 200 und in der zweiten Partition mehr als 200, aber nicht 200 und unter 300.



    Dies muss beachtet und nicht getan werden, da Sie nicht wissen, welche Von Partitionen werden die Daten abgerufen. Es müssen alle Überprüfungsbedingungen klar angegeben werden.

    Erstellen Sie auch keine Partitionen in verschiedenen Feldern, d. H. dass wir in der 1. Partition Einträge mit category_id = 1 und in der 2. - mit einer Bewertung von 100 erhalten



    . Wenn wir wieder einen solchen Eintrag erhalten, in dem category_id = 1 und rating = 100, dann ist nicht bekannt, welcher Partitionen erhalten diesen Datensatz. Die Partitionierung basiert auf einer Funktion, auf einem beliebigen Feld - dies ist sehr wichtig.
    Betrachten wir unsere Partition als Ganzes:



    Ihre partitionierte Tabelle sieht folgendermaßen aus, d. H. Dies ist die Tabelle news_1 mit einem Zeichen, dass nur Datensätze mit category_id = 1 dorthin gelangen, und diese Tabelle wird von den Nachrichten der Basistabelle geerbt - alles ist sehr einfach.



    Wir müssen der Basistabelle eine Regel hinzufügen, damit bei der Arbeit mit unseren Haupttabellennachrichten die Einfügung in den Datensatz mit category_id = 1 auf diese Partition und nicht auf die Hauptpartition erfolgt. Wir geben eine einfache Regel an, nennen sie so, wie wir wollen, und sagen, dass wir beim Einfügen von Daten in Nachrichten mit category_id = 1 stattdessen Daten in news_1 einfügen. Auch hier ist alles ganz einfach: Laut Vorlage ändert sich alles und wird wunderbar funktionieren. Diese Regel wird in der Basistabelle erstellt.



    So starten wir die Anzahl der benötigten Partitionen. Zum Beispiel werde ich 2 Partitionen verwenden, um es einfacher zu machen. Das heißt Wir haben alles gleich, außer den Namen dieser Tabelle und den Bedingungen, unter denen die Daten dort ankommen. Wir legen auch die entsprechenden Vorlagenregeln für jede der Tabellen fest.



    Schauen wir uns ein Beispiel für das Einfügen von Daten an:



    Wir fügen Daten wie gewohnt ein, als hätten wir eine gewöhnliche große dicke Tabelle, d.h. wir fügen einen Datensatz mit category_id = 1 mit category_id = 2 ein, wir können sogar Daten mit category_id = 3 einfügen.



    Hier wählen wir die Daten aus, wir haben sie alle:



    Alles, was wir eingefügt haben, obwohl wir nicht die 3. Partition haben, aber wir haben die Daten. Es mag ein bisschen Magie sein, aber nicht wirklich.

    Wir können auch entsprechende Anfragen an bestimmte Partitionen stellen, die unseren Zustand angeben, d. H. Category_id = 1, oder Vorkommen in Zahlen (2, 3).



    Alles wird gut funktionieren, alle Daten werden ausgewählt. Auch hier haben wir keine Partition mit category_id = 3.



    Wir können Daten direkt aus Partitionen auswählen - dies ist das gleiche wie im vorherigen Beispiel, aber wir geben deutlich die Partition an, die wir benötigen. Wenn wir die genaue Bedingung haben, dass wir Daten aus dieser bestimmten Partition auswählen müssen, können wir diese bestimmte Partition direkt angeben und nicht zu anderen wechseln. Wir haben jedoch keine dritte Partition und die Daten werden in die Haupttabelle verschoben.



    Obwohl wir die Partitionierung auf diese Tabelle angewendet haben, ist die Haupttabelle noch vorhanden. Es ist eine echte Tabelle, sie kann Daten speichern, und mit dem Operator ONLY können Sie nur Daten aus dieser Tabelle auswählen, und wir können feststellen, dass dieser Datensatz hier verborgen ist.



    Wie Sie auf der Folie sehen können, können Sie hier Daten direkt in die Partition einfügen. Sie können Daten mithilfe von Regeln in die Haupttabelle, aber auch in die Partition selbst einfügen.



    Wenn wir Daten in eine Partition mit einer fremden Bedingung einfügen, z. B. mit category_id = 4, erhalten wir die Fehlermeldung "Solche Daten können hier nicht eingefügt werden" - dies ist auch sehr praktisch - wir fügen die Daten nur in die Partitionen ein, die wir haben wirklich gebraucht, und wenn etwas mit uns schief geht, werden wir all dies auf der Basisebene fangen.



    Hier ist ein größeres Beispiel. Sie können bulk_insert verwenden, d. H. Fügen Sie mehrere Datensätze gleichzeitig ein, und alle werden nach den Regeln der gewünschten Partition verteilt. Das heißt Wir können uns überhaupt nicht darum kümmern, arbeiten einfach mit unserem Tisch, wie wir früher gearbeitet haben. Die Anwendung wird weiterhin funktionieren, aber gleichzeitig werden die Daten in Partitionen aufgeteilt. All dies wird ohne unsere Teilnahme in den Regalen übersichtlich dargestellt.



    Lassen Sie mich daran erinnern, dass wir Daten aus der Haupttabelle mit der Bedingung auswählen können. Ohne diese Bedingung anzugeben, können wir die Daten aus der Partition auswählen. Wie es von der Seite von EXPLAIN aussieht:



    Wir haben Seq Scan in der gesamten Tabelle, da die Daten immer noch dort ankommen können und es einen Scan nach Partition gibt. Wenn wir die Bedingungen mehrerer Kategorien angeben, werden nur die Tabellen durchsucht, für die Bedingungen vorliegen. Er wird den Rest der Partition nicht anschauen. So funktioniert der Optimierer - das ist richtig und damit wirklich schneller.

    Wir können sehen, wie EXPLAIN auf der Partition selbst aussieht.



    Es wird ein normaler Tisch sein, nur Seq Scan, nichts Übernatürliches. Aktualisierungen und Löschvorgänge funktionieren auf dieselbe Weise. Wir können die Haupttabelle aktualisieren und Aktualisierungen direkt an die Partitionen senden. Löschvorgänge funktionieren auch. Sie müssen nach den gleichen Regeln erstellt werden, die wir mit insert erstellt haben, aber anstelle von insert write update oder delete.

    Gehen wir weiter zu Dingen wie Indizes.




    In der Haupttabelle erstellte Indizes werden nicht in die untergeordnete Tabelle unserer Partition übernommen. Das ist traurig, aber Sie müssen für alle Partitionen denselben Index erstellen. Es gibt etwas zu tun, aber Sie müssen alle Indizes, alle Einschränkungen und alle Trigger für alle Tabellen duplizieren.

    Wie wir zu Hause mit diesem Problem kämpften. Wir haben ein wundervolles Dienstprogramm PartitionMagic erstellt, mit dem Sie Partitionen automatisch verwalten und sich nicht mit der Erstellung von Indizes, Triggern mit nicht vorhandenen Partitionen und Problemen befassen können. Dieses Dienstprogramm ist Open Source. Es wird einen Link unten geben. Wir fügen dieses Dienstprogramm als gespeicherte Prozedur zu unserer Datenbank hinzu, es liegt dort, erfordert keine zusätzlichen Erweiterungen, keine Erweiterungen, nichts muss neu zusammengesetzt werden, d. H. Wir nehmen PostgreSQL, das übliche Verfahren, stecken es in die Datenbank und arbeiten damit.

    Hier ist derselbe Tisch, den wir untersucht haben, nichts Neues, egal.



    Wie partitionieren wir es? Und einfach so:



    Wir rufen die Prozedur auf, geben an, dass die Tabelle neu sein wird, und partitionieren nach category_id. Und es wird weiterhin von alleine funktionieren, wir müssen nichts weiter tun. Wir fügen auch Daten ein.

    Wir haben drei Einträge mit category_id = 1, zwei Einträge mit category_id = 2 und einen mit category_id = 3.



    Nach dem Einfügen fallen die Daten automatisch in die erforderlichen Partitionen, die wir auswählen können.



    Alles, Partitionen wurden bereits erstellt, alle Daten wurden in die Regale gestellt, alles funktioniert hervorragend.
    Was wir durch diesen Vorteil bekommen:

    • Beim Einfügen erstellen wir automatisch eine Partition, falls diese noch nicht vorhanden ist.
    • Wir behalten die aktuelle Struktur bei. Wir können die Basistabelle einfach verwalten, indem wir Indizes aufhängen, überprüfen, Trigger darauf auslösen, Spalten hinzufügen und sie nach erneutem Aufruf dieser Prozedur automatisch in alle Partitionen einfügen.

    Hier haben wir einen großen Vorteil. Hier ist der Link https://github.com/2gis/partition_magic . Damit ist der erste Teil des Berichts abgeschlossen. Wir haben gelernt, wie man Daten partitioniert. Ich möchte Sie daran erinnern, dass die Partitionierung auf eine Instanz angewendet wird - dies ist dieselbe Instanz der Datenbank, auf der Sie eine große, dicke Tabelle hätten, die wir jedoch in kleine Teile aufteilen. Wir können unsere Anwendung nicht komplett ändern - sie funktioniert genauso wie die Haupttabelle - dort Daten einfügen, bearbeiten, löschen. Alles funktioniert auch, aber es funktioniert schneller. Ungefähr im Durchschnitt 3-4 mal schneller.

    Fahren wir mit dem zweiten Teil des Berichts fort - dies ist eine horizontale Scherbe. Lassen Sie mich daran erinnern, dass beim horizontalen Sharding Daten auf mehrere Server verteilt werden. All dies ist ganz einfach, es lohnt sich, es einmal einzurichten, und es wird großartig funktionieren. Ich werde detaillierter erklären, wie dies getan werden kann.

    Wir werden dieselbe Struktur mit zwei Shards betrachten - news_1 und news_2, aber dies werden unterschiedliche Instanzen sein, die dritte Instanz wird die Hauptbasis sein, mit der wir arbeiten werden: Dieselbe



    Tabelle:



    Das einzige, was dort hinzugefügt werden muss, ist CONSTRAINT CHECK, dass Datensätze nur mit category_id = 1 gelöscht werden. Wie im vorherigen Beispiel, aber dies ist keine geerbte Tabelle, sondern eine Tabelle mit einem Shard, den wir auf dem Server erstellen, der als Shard mit category_id = 1 fungiert. Dies muss beachtet werden. Sie müssen nur noch CONSTRAINT hinzufügen.

    Wir können zusätzlich noch einen Index durch category_id erstellen:



    Trotz der Tatsache, dass wir einen Check-Check haben, nennt PostgreSQL diesen Shard immer noch und der Shard kann sehr lange nachdenken, da es viele Daten geben kann, und im Falle des Indexes auch Antworten Sie schnell, da für eine solche Anfrage keine Einträge im Index vorhanden sind. Fügen Sie sie daher besser hinzu.

    Wie konfiguriere ich Sharding auf dem Hauptserver?



    Wir verbinden EXTENSION. EXTENSION geht von der Box zu Postgres, dies geschieht mit dem Befehl CREATE EXTENSION, es heißt postgres_fdw, steht für Foreign Data Wrapper.

    Als nächstes müssen wir einen Remote-Server starten, ihn mit dem Haupt-Server verbinden. Wir nennen ihn wie immer Sie möchten und geben an, dass dieser Server den von uns angegebenen Wrapper für fremde Daten verwenden wird.

    Auf die gleiche Weise kann es für Shards wie MySql, Oracle, Mongo ... verwendet werden. Der Wrapper für fremde Daten ist für so viele Datenbanken, d.h. Sie können einzelne Shards in verschiedenen Datenbanken speichern.

    In den Optionen, die wir hinzufügen, fügen wir den Host, den Port und den Namen der Datenbank hinzu, mit der wir arbeiten werden, müssen Sie nur die Adresse Ihres Servers, den Port (höchstwahrscheinlich Standard) und die Basis angeben, die wir gestartet haben.

    Als nächstes erstellen wir ein Mapping für den Benutzer - anhand dieser Daten wird der Hauptserver für das Kind autorisiert. Wir weisen darauf hin, dass es für den Server news_1 einen postgres-Benutzer mit dem postgres-Passwort gibt. Und es wird der Hauptdatenbank als unser Benutzer postgres zugeordnet.

    Ich habe alles mit Standardeinstellungen gezeigt, Sie können Ihre eigenen Benutzer für Projekte haben, für einzelne Datenbanken, hier müssen Sie sie angeben, damit alles funktioniert.

    Als nächstes starten wir die Platte auf dem Hauptserver:



    Es wird eine Platte mit der gleichen Struktur sein, aber das einzige, was sich unterscheidet, ist das Präfix, dass es sich um eine Fremdtabelle handelt, d. H. Es ist eine Art Fremdsprache für uns, fern, und wir geben an, von welchem ​​Server es genommen wird, und in den Optionen geben Sie das Schema und den Namen der Tabelle an, die wir nehmen müssen.

    Das Standardschema ist public, die von uns erstellte Tabelle heißt news. Auf die gleiche Weise verbinden wir die 2. Tabelle mit dem Hauptserver, d.h. Server hinzufügen, Zuordnung hinzufügen, Tabelle erstellen. Alles was bleibt ist unser Haupttisch zu haben.



    Dies geschieht mit VIEW. In der Präsentation verwenden wir UNION ALL, um Abfragen von Remotetabellen zusammenzufügen und eine große Nachrichtentabelle von Remoteservern abzurufen.

    Wir können dieser Tabelle auch Regeln beim Einfügen, Löschen und Arbeiten mit der Haupttabelle anstelle von Shards hinzufügen, damit es für uns bequemer ist - kein Umschreiben, nichts in der Anwendung zu tun.



    Wir richten eine Grundregel ein, die ausgelöst wird, wenn keine Prüfungen durchgeführt werden, damit nichts passiert. Das heißt wir geben STATT NICHTS TUN an und beginnen die gleichen Überprüfungen wie zuvor, jedoch nur mit der Angabe unseres Zustands, d. h. category_id = 1 und die Tabelle, in die die Daten stattdessen fallen.



    Das heißt Der einzige Unterschied besteht darin, dass in category_id der Name der Tabelle angegeben wird. Schauen Sie sich auch den Dateneinsatz an.



    Ich habe speziell nicht existierende Partitionen hervorgehoben, weil Gemäß unserem Zustand werden diese Daten nirgendwo hinkommen, d. h. wir haben angegeben, dass wir nichts tun werden, wenn es keine Bedingung gibt, da dies VIEW ist, dies keine echte Tabelle ist und dort keine Daten eingefügt werden können. In diesem Zustand können wir schreiben, dass die Daten in eine dritte Tabelle eingefügt werden, d.h. Wir können so etwas wie einen Puffer oder einen Korb bekommen und INSERT INTO macht das in der Tabelle, damit sich dort Daten ansammeln, wenn wir plötzlich keine Partitionen mehr haben und die Daten zu kommen beginnen, für die es keine Shards gibt.

    Daten auswählen




    Achten Sie auf die Sortierung der Bezeichner - wir zeigen zuerst alle Einträge des ersten Shards und dann des zweiten an. Dies liegt an der Tatsache, dass Postgres nacheinander durch VIEW wandert. Wir haben selects über UNION ALL angegeben, und es wird genauso ausgeführt: Es sendet Anforderungen an entfernte Maschinen, sammelt diese Daten und klebt sie zusammen. Sie werden nach dem Prinzip sortiert, nach dem wir diese VIEW erstellt haben, nach dem dieser Server die Daten übermittelt hat.

    Wir stellen Abfragen, die wir zuvor aus der Haupttabelle mit der Kategorie gemacht haben, dann gibt postgres nur Daten von der zweiten Scherbe zurück, oder wir setzen uns direkt mit der Scherbe in Verbindung.



    Genau wie in den obigen Beispielen haben nur wir verschiedene Server, verschiedene Instanzen und alles funktioniert genauso wie zuvor.

    Schauen Sie sich EXPLAIN an.



    Wir haben einen fremden Scan durch news_1 und einen fremden Scan durch news_2, genauso wie bei der Partitionierung, aber anstelle von Seq Scan haben wir einen fremden Scan - dies ist ein Remote-Scan, der auf einem anderen Server ausgeführt wird.



    Partitionierung ist wirklich einfach, es sind nur ein paar Schritte erforderlich, um alles einzurichten, und es wird alles großartig funktionieren, nicht zum Essen auffordern. Sie können auch mit dem Haupttisch arbeiten, wie wir es zuvor getan haben, aber gleichzeitig ist alles wunderschön in unseren Regalen und bereit zur Skalierung, bereit für viele Daten. All dies funktioniert auf einem Server und gleichzeitig erhalten wir eine 3-4-fache Produktivitätssteigerung, da unsere Daten in der Tabelle reduziert sind, weil Dies sind verschiedene Tabellen.

    Scherben - Es ist nur ein bisschen komplizierter als das Partitionieren, da Sie jeden Server einzeln konfigurieren müssen. Dies bietet jedoch den Vorteil, dass wir nur eine unendliche Anzahl von Servern hinzufügen können und alles gut funktioniert.

    Kontaktdaten


    2GIS Unternehmensblog

    Jetzt auch beliebt: