Wir schreiben in PostgreSQL auf Sublight: 1 Host, 1 Tag, 1 TB

Kürzlich habe ich euch erklärt, wie man das mit Standardrezepten macht Erhöhen Sie die Leistung von SQL-Leseabfragen aus der PostgreSQL-Datenbank. Heute werden wir darüber sprechen, wie Die Aufnahme kann effizienter erfolgen in der Datenbank, ohne irgendwelche „Verdrehungen“ in der Konfiguration zu verwenden – einfach durch die korrekte Organisation der Datenflüsse.

Wir schreiben in PostgreSQL auf Sublight: 1 Host, 1 Tag, 1 TB

#1. Schnitt

Ein Artikel darüber, wie und warum es sich lohnt, zu organisieren angewandte Partitionierung „in der Theorie“ Wie es bereits war, werden wir hier über die Praxis der Anwendung einiger Ansätze in unserem sprechen Überwachungsdienst für Hunderte von PostgreSQL-Servern.

„Dinge vergangener Tage…“

Anfangs startete unser Projekt, wie jedes MVP, unter relativ geringer Auslastung – die Überwachung erfolgte nur für die zehn kritischsten Server, alle Tabellen waren relativ kompakt … Doch mit der Zeit wurde die Anzahl der überwachten Hosts immer größer , und wieder einmal haben wir versucht, etwas mit einem davon zu machen Tabellen mit einer Größe von 1.5 TB, wurde uns klar, dass es zwar möglich war, so weiterzuleben, es aber sehr unbequem war.

Die Zeiten waren fast wie epische Zeiten, verschiedene Versionen von PostgreSQL 9.x waren relevant, daher musste die gesamte Partitionierung „manuell“ durchgeführt werden – durch Tabellenvererbung und Trigger Routing mit Dynamik EXECUTE.

Wir schreiben in PostgreSQL auf Sublight: 1 Host, 1 Tag, 1 TB
Die resultierende Lösung erwies sich als universell genug, dass sie auf alle Tabellen übertragen werden konnte:

  • Es wurde eine leere übergeordnete „Header“-Tabelle deklariert, die alles beschreibt notwendige Indizes und Trigger.
  • Der Eintrag aus Sicht des Kunden erfolgte in der „Root“-Tabelle und intern mit Routing-Trigger BEFORE INSERT Der Datensatz wurde „physisch“ in den erforderlichen Abschnitt eingefügt. Wenn es so etwas noch nicht gab, haben wir eine Ausnahme gefangen und ...
  • … mit Hilfe CREATE TABLE ... (LIKE ... INCLUDING ...) wurde basierend auf der Vorlage der übergeordneten Tabelle erstellt Abschnitt mit einer Einschränkung auf das gewünschte Datumsodass beim Abrufen von Daten nur darin gelesen wird.

PG10: erster Versuch

Aber die Partitionierung durch Vererbung war in der Vergangenheit nicht gut für den Umgang mit einem aktiven Schreibstrom oder einer großen Anzahl untergeordneter Partitionen geeignet. Sie können sich beispielsweise daran erinnern, dass es einen Algorithmus zur Auswahl des erforderlichen Abschnitts gab quadratische Komplexität, dass es mit mehr als 100 Abschnitten funktioniert, Sie verstehen selbst, wie ...

In PG10 wurde diese Situation durch die Implementierung von Support erheblich optimiert native Partitionierung. Daher haben wir sofort versucht, es sofort nach der Migration des Speichers anzuwenden, aber ...

Wie sich beim Durchforsten des Handbuchs herausstellte, lautet die nativ partitionierte Tabelle in dieser Version:

  • unterstützt keine Indexbeschreibungen
  • unterstützt keine Trigger darauf
  • kann nicht der „Nachkomme“ von irgendjemandem sein
  • unterstützt nicht INSERT ... ON CONFLICT
  • kann einen Abschnitt nicht automatisch generieren

Nachdem wir mit einem Rechen einen schmerzhaften Schlag auf die Stirn erlitten hatten, wurde uns klar, dass es unmöglich wäre, ohne eine Änderung der Anwendung auszukommen, und haben die weitere Forschung um sechs Monate verschoben.

PG10: zweite Chance

Also begannen wir, die aufgetretenen Probleme nach und nach zu lösen:

  1. Denn Auslöser und ON CONFLICT Wir stellten fest, dass wir sie hier und da noch brauchten, also machten wir einen Zwischenschritt, um sie auszuarbeiten Proxy-Tabelle.
  2. „Routing“ losgeworden in Triggern - also von EXECUTE.
  3. Sie haben es separat herausgenommen Vorlagentabelle mit allen Indizessodass sie gar nicht erst in der Proxy-Tabelle vorhanden sind.

Wir schreiben in PostgreSQL auf Sublight: 1 Host, 1 Tag, 1 TB
Schließlich haben wir nach all dem die Haupttabelle nativ partitioniert. Die Erstellung eines neuen Abschnitts bleibt weiterhin dem Gewissen der Anwendung überlassen.

Wörterbücher zum Thema „Sägen“.

Wie in jedem analytischen System hatten wir auch „Fakten“ und „Schnitte“ (Wörterbücher). In unserem Fall handelten sie in dieser Funktion beispielsweise Vorlagenkörper ähnliche langsame Abfragen oder der Text der Abfrage selbst.

„Fakten“ waren schon lange nach Tagen unterteilt, daher haben wir veraltete Abschnitte in aller Ruhe gelöscht, und sie haben uns nicht gestört (Protokolle!). Aber es gab ein Problem mit Wörterbüchern ...

Um nicht zu sagen, dass es viele davon gab, aber ungefähr 100 TB „Fakten“ ergaben ein 2.5 TB großes Wörterbuch. Man kann aus einer solchen Tabelle nichts bequem löschen, man kann sie nicht rechtzeitig komprimieren und das Schreiben darauf wurde allmählich langsamer.

Wie in einem Wörterbuch... darin sollte jeder Eintrag genau einmal vorkommen... und das ist richtig, aber!... Niemand hält uns davon ab ein eigenes Wörterbuch für jeden Tag! Ja, das bringt eine gewisse Redundanz mit sich, ermöglicht aber:

  • schneller schreiben/lesen aufgrund der kleineren Abschnittsgröße
  • verbrauchen weniger Speicher durch die Arbeit mit kompakteren Indizes
  • weniger Daten speichern aufgrund der Fähigkeit, veraltete Dateien schnell zu entfernen

Als Ergebnis des gesamten Maßnahmenkomplexes CPU-Auslastung um ca. 30 % und Festplattenauslastung um ca. 50 % gesunken:

Wir schreiben in PostgreSQL auf Sublight: 1 Host, 1 Tag, 1 TB
Gleichzeitig haben wir weiterhin genau das Gleiche in die Datenbank geschrieben, nur mit weniger Last.

#2. Datenbankentwicklung und Refactoring

Also haben wir uns für das entschieden, was wir haben Jeder Tag hat seinen eigenen Abschnitt mit Daten. Eigentlich, CHECK (dt = '2018-10-12'::date) – und es gibt einen Partitionierungsschlüssel und die Bedingung dafür, dass ein Datensatz in einen bestimmten Abschnitt fällt.

Da alle Berichte in unserem Service im Kontext eines bestimmten Datums erstellt werden, sind die Indizes für sie seit „nicht-partitionierten Zeiten“ alle Typen (Server, Datum, Planvorlage), (Server, Datum, Planknoten), (Datum, Fehlerklasse, Server), ...

Aber jetzt leben sie in jedem Abschnitt Ihre Kopien jeder dieser Indexe... Und innerhalb jedes Abschnitts Das Datum ist eine Konstante... Es stellt sich heraus, dass wir uns jetzt in jedem dieser Indexe befinden Geben Sie einfach eine Konstante ein als eines der Felder, was sowohl sein Volumen als auch die Suchzeit dafür erhöht, aber kein Ergebnis bringt. Sie haben den Rechen sich selbst überlassen, ups...

Wir schreiben in PostgreSQL auf Sublight: 1 Host, 1 Tag, 1 TB
Die Richtung der Optimierung ist klar – einfach Entfernen Sie das Datumsfeld aus allen Indizes auf partitionierten Tabellen. Angesichts unserer Volumina liegt der Gewinn bei ca 1 TB/Woche!

Beachten wir nun, dass dieses Terabyte noch irgendwie aufgezeichnet werden musste. Das heißt, wir auch Die Festplatte sollte jetzt weniger geladen werden! Dieses Bild zeigt deutlich die Wirkung der Reinigung, der wir eine Woche gewidmet haben:

Wir schreiben in PostgreSQL auf Sublight: 1 Host, 1 Tag, 1 TB

#3. Spitzenlast „verteilen“.

Eines der großen Probleme geladener Systeme ist redundante Synchronisation einige Vorgänge, die dies nicht erfordern. Manchmal „weil sie es nicht gemerkt haben“, manchmal „war es so einfacher“, aber früher oder später muss man es loswerden.

Vergrößern wir das vorherige Bild und sehen wir, dass wir eine Festplatte haben „Pumpt“ unter der Last mit doppelter Amplitude zwischen benachbarten Stichproben, was „statistisch gesehen“ bei einer solchen Anzahl von Operationen eindeutig nicht passieren sollte:

Wir schreiben in PostgreSQL auf Sublight: 1 Host, 1 Tag, 1 TB

Dies ist ganz einfach zu erreichen. Wir haben bereits mit der Überwachung begonnen fast 1000 Server, jeder wird von einem separaten logischen Thread verarbeitet, und jeder Thread setzt die gesammelten Informationen zurück, um sie in einer bestimmten Häufigkeit an die Datenbank zu senden, etwa so:

setInterval(sendToDB, interval)

Das Problem liegt hier genau darin Alle Threads beginnen ungefähr zur gleichen Zeit, daher stimmen ihre Sendezeiten fast immer „auf den Punkt“ überein. Ups #2...

Glücklicherweise lässt sich das ganz einfach beheben, Hinzufügen eines „zufälligen“ Anlaufs pünktlich:

setInterval(sendToDB, interval * (1 + 0.1 * (Math.random() - 0.5)))

#4. Wir zwischenspeichern, was wir brauchen

Das dritte traditionelle Hochlastproblem ist kein Cache wo er ist könnte zu sein.

Beispielsweise haben wir die Analyse im Hinblick auf Planknoten (alle diese) möglich gemacht Seq Scan on users), denken aber sofort, dass sie größtenteils gleich sind – sie haben es vergessen.

Nein, natürlich wird nichts erneut in die Datenbank geschrieben, dadurch wird der Trigger mit abgeschnitten INSERT ... ON CONFLICT DO NOTHING. Aber diese Daten gelangen trotzdem in die Datenbank und sind unnötig Lesen, um nach Konflikten zu suchen zu tun haben. Ups #3...

Der Unterschied in der Anzahl der Datensätze, die vor/nach der Aktivierung des Cachings an die Datenbank gesendet werden, ist offensichtlich:

Wir schreiben in PostgreSQL auf Sublight: 1 Host, 1 Tag, 1 TB

Und das ist der damit einhergehende Rückgang der Speicherlast:

Wir schreiben in PostgreSQL auf Sublight: 1 Host, 1 Tag, 1 TB

Insgesamt

„Terabyte pro Tag“ klingt einfach beängstigend. Wenn Sie alles richtig machen, dann ist das gerecht 2^40 Bytes / 86400 Sekunden = ~12.5 MB/sdass sogar Desktop-IDE-Schrauben hielten. 🙂

Aber im Ernst, selbst bei einer zehnfachen „Verzerrung“ der Last im Laufe des Tages können Sie die Fähigkeiten moderner SSDs problemlos ausschöpfen.

Wir schreiben in PostgreSQL auf Sublight: 1 Host, 1 Tag, 1 TB

Source: habr.com

Kommentar hinzufügen