
Im Bericht wird Andrei Borodin erklären, wie sie die Erfahrungen mit der Skalierung von PgBouncer bei der Entwicklung eines Connection Poolers berücksichtigt haben. , wie sie es in die Produktion eingeführt haben. Außerdem werden wir diskutieren, welche Funktionen wir uns in neuen Versionen des Poolers wünschen würden: es ist uns wichtig, nicht nur unsere Bedürfnisse zu erfüllen, sondern auch die Nutzergemeinschaft weiterzuentwickeln. .
Video:


Hallo zusammen! Mein Name ist Andrei.

Bei Yandex beschäftige ich mich mit der Entwicklung von Open-Source-Datenbanken. Heute haben wir das Thema über Connection Pooler.

Wenn Sie wissen, wie man Connection Pooler auf Russisch nennt, lassen Sie es mich wissen. Ich möchte unbedingt einen guten technischen Begriff finden, der sich in der technischen Literatur etablieren sollte.
Das Thema ist ziemlich komplex, da in vielen Datenbanken ein Connection Pooler integriert ist, über den man nicht einmal Bescheid wissen muss. Sicherlich gibt es an einigen Stellen Konfigurationen, aber bei Postgres funktioniert das nicht so einfach. Parallel dazu hält Nikolai Samokhvalov auf der HighLoad++ 2019 eine Präsentation über die Optimierung von Anfragen in Postgres. Ich habe den Eindruck, dass hier Personen sind, die ihre Anfragen bereits perfekt konfiguriert haben und die mit eher seltenen systemtechnischen Problemen zu kämpfen haben, die mit Netzwerken und Ressourcennutzung verbunden sind. An manchen Stellen könnte das recht schwierig gewesen sein, da die Probleme nicht immer offensichtlich sind.

Bei Yandex gibt es Postgres. Im Yandex.Cloud befinden sich viele Yandex-Dienste. Wir verfügen über mehrere Petabyte an Daten, die nicht weniger als eine Million Anfragen pro Sekunde in Postgres generieren.

Wir bieten allen Diensten einen ausreichend typischen Cluster an – dies umfasst die Haupt-Primary-Node und zwei herkömmliche Replikate (eine synchrone und eine asynchrone), Backup und die Skalierung von Leseanfragen auf der Replik.

Jeder Knoten im Cluster ist ein Postgres, auf dem neben Postgres und Systemmonitoring auch ein Connection Pooler installiert ist. Der Connection Pooler wird für das Fencing verwendet und erfüllt seine Hauptfunktion.

Was ist der Hauptzweck eines Connection Poolers?

In Postgres wird ein prozessorientiertes Modell für den Datenbankbetrieb verwendet. Das bedeutet, dass eine Verbindung einem Prozess entspricht, einem Backend von Postgres. In diesem Backend gibt es viele verschiedene Caches, die es kostspielig machen, sie für unterschiedliche Verbindungen zu variieren.

Darüber hinaus gibt es im Postgres-Code ein Array mit dem Namen procArray. Es enthält grundlegende Informationen über die Netzwerkverbindungen. Fast alle Algorithmen zur Verarbeitung des procArray haben eine lineare Komplexität und durchlaufen das gesamte Array der Netzwerkverbindungen. Dies ist ein recht schneller Zyklus, aber bei vielen eingehenden Netzwerkverbindungen kann es etwas kostspieliger werden. Wenn alles ein wenig teurer wird, kann am Ende eine sehr hohe Summe für eine große Anzahl von Netzwerkverbindungen anfallen.

Es gibt 3 mögliche Ansätze:
- Auf der Anwendungsebene.
- Auf der Datenbankebene.
- Und dazwischen, d. h. verschiedene Kombinationen.
Leider befindet sich der integrierte Pooler derzeit in der Entwicklungsphase. Unsere Freunde bei PostgreSQL Professional kümmern sich hauptsächlich darum. Wann er verfügbar sein wird, lässt sich schwer vorhersagen. Tatsächlich stehen dem Architekten zwei Lösungen zur Auswahl: ein Anwendungspool und ein Proxy-Pool.

Der Anwendungspool ist der einfachste Weg. Fast alle Client-Treiber bieten eine Möglichkeit, Millionen Ihrer Verbindungen im Code als einige Dutzend Verbindungen zur Datenbank darzustellen.

Hierbei tritt das Problem auf, dass Sie zu einem bestimmten Zeitpunkt Backend skalieren möchten und es auf vielen virtuellen Maschinen bereitstellen wollen.

Später erkennen Sie, dass Sie auch mehrere Verfügbarkeitszonen und verschiedene Rechenzentren haben. Der Ansatz des Client-Side-Poolings führt zu hohen Zahlen – etwa 10.000 Verbindungen. Das ist die Obergrenze, bei der es einigermaßen funktioniert.

Wenn wir über Proxy-Pooler sprechen, gibt es zwei Pooler, die eine Menge Funktionen bieten. Sie sind nicht nur Pooler, sondern auch mit cooler Funktionalität ausgestattet. Das sind und .
Leider ist diese zusätzliche Funktionalität nicht für alle erforderlich. Sie führt dazu, dass Pooler nur sogenanntes Session-Pooling unterstützen, d. h. ein eingehender Client, ein ausgehender Client zur Datenbank.
Für unsere Zwecke ist das nicht optimal, weshalb wir PgBouncer einsetzen, das Transaction-Pooling implementiert, d. h. serverseitige Verbindungen werden lediglich während der Transaktionszeit den Clientverbindungen zugeordnet.

Das ist in unserer Last tatsächlich der Fall. Es gibt jedoch einige Probleme..
Die Probleme beginnen, wenn Sie eine Sitzung diagnostizieren möchten, da alle eingehenden Verbindungen lokal sind. Alle kommen über den Loopback, was die Rückverfolgung der Sitzung erschwert.

Natürlich können Sie application_name_add_host verwenden. Das ist ein Weg, um auf der Seite des Bouncers die IP-Adresse dem application_name hinzuzufügen. Aber application_name wird über eine zusätzliche Verbindung festgelegt.

In diesem Diagramm zeigt die gelbe Linie die tatsächlichen Anfragen und die blaue Linie die Anfragen, die in die Datenbank gelangen. Der Unterschied ist tatsächlich die Festlegung des application_name, der nur für die Rückverfolgung benötigt wird, jedoch keineswegs kostenlos ist.

Außerdem kann im Bouncer kein einzelner Pool eingeschränkt werden, das heißt, die Anzahl der Verbindungen zur Datenbank für einen bestimmten Benutzer oder eine bestimmte Datenbank.

Was bedeutet das? Sie haben einen stark ausgelasteten Service, der in C++ geschrieben ist, und daneben läuft ein kleiner Service in Node, der der Datenbank nichts Schlimmes antut, aber dessen Treiber verrückt spielt. Er öffnet 20.000 Verbindungen, während alles andere warten muss. Ihr Code ist sogar in Ordnung.

Natürlich haben wir einen kleinen Patch für Bouncer geschrieben, der diese Einstellung hinzugefügt hat, d.h. die Begrenzung der Clients auf den Pool.

Man könnte dies auch auf der Seite von Postgres tun, also die Rollen in der Datenbank durch die Anzahl der Verbindungen einzuschränken.

Aber dabei verliert man die Möglichkeit zu verstehen, warum keine Verbindungen zum Server bestehen. PgBouncer gibt keinen Verbindungspfadfehler weiter, sondern liefert immer dieselbe Information zurück. Und man kann nicht herausfinden: Vielleicht hat sich das Passwort geändert, vielleicht ist die Datenbank einfach abgestürzt, vielleicht stimmt etwas nicht. Aber es gibt keine Diagnosen. Wenn die Sitzung nicht hergestellt werden kann, erfahren Sie nicht, warum das nicht funktioniert.

Irgendwann schauen Sie sich die Graphen der Anwendung an und sehen, dass die Anwendung nicht funktioniert.

Sie sehen an der Spitze, dass Bouncer einen einzelnen Thread verwendet. Das ist ein Wendepunkt für den Service. Sie realisieren, dass Sie sich auf die Skalierung Ihrer Datenbank in anderthalb Jahren vorbereitet haben, aber jetzt müssen Sie den Pooler skalieren.

Wir sind zu dem Schluss gekommen, dass wir mehr PgBouncer benötigen.

Wir haben Bouncer ein wenig gepatcht.

Und so eingerichtet, dass mehrere Bouncers mit Wiederverwendung von TCP-Ports aktiviert werden können. Das Betriebssystem verteilt eingehende TCP-Verbindungen zwischen ihnen automatisch im Round-Robin-Verfahren.

Für die Kunden ist das transparent, das heißt, alles sieht so aus, als hätten Sie einen einzigen Bouncer, aber es gibt eine Fragmentierung der idle-Verbindungen zwischen den aktiven Bouncers.

Und zu einem bestimmten Zeitpunkt werden Sie feststellen, dass diese 3 Bouncers jeweils 100 % ihrer Kerne beanspruchen. Sie benötigen eine beträchtliche Menge an Bouncers. Warum?

Weil Sie TLS haben. Sie verfügen über eine verschlüsselte Verbindung. Und wenn Sie Postgres mit und ohne TLS benchmarken, werden Sie feststellen, dass die Anzahl der etablierten Verbindungen fast um zwei Größenordnungen sinkt, wenn die Verschlüsselung aktiviert ist, da der TLS-Handshake CPU-Ressourcen verbraucht.

Im Dashboard sehen Sie eine Vielzahl von kryptografischen Funktionen, die bei einer Welle eingehender Verbindungen ausgeführt werden. Da unser primärer Server zwischen Verfügbarkeitszonen wechseln kann, ist eine Welle eingehender Verbindungen eine übliche Situation. Das bedeutet, dass aus irgendeinem Grund der alte primäre Server nicht verfügbar war und die gesamte Last an ein anderes Rechenzentrum umgeleitet wurde. Alle werden gleichzeitig mit TLS winken.

Eine große Anzahl von TLS-Handshake-Requests könnte jedoch nicht mehr mit dem Bouncer interagieren, sondern ihn stattdessen unter Druck setzen. Aufgrund von Timeouts könnte die Welle eingehender Verbindungen anhalten. Wenn Sie einen Retry zur Datenbank ohne exponentiellen Backoff haben, kommen sie immer wieder in einer kohärenten Welle.

Hier ist ein Beispiel mit 16 PgBouncern, die 16 Kerne zu 100 % auslasten.

Wir haben uns für einen kaskadierenden PgBouncer entschieden. Dies ist die optimale Konfiguration für unsere Last mit Bouncer. Externe Bouncer sind für den TCP-Handshake zuständig, während interne Bouncer für das tatsächliche Pooling verwendet werden, um externe Verbindungen nicht zu stark zu fragmentieren.

In dieser Konfiguration ist ein reibungsloser Neustart möglich. Man kann alle 18 Bouncers nacheinander neustarten. Allerdings ist es recht kompliziert, eine solche Konfiguration zu unterstützen. Systemadministratoren, DevOps und Personen, die tatsächlich für diesen Server verantwortlich sind, werden mit einem solchen Setup nicht sehr zufrieden sein.

Es scheint, als könnten wir all unsere Weiterentwicklungen in Open Source vorantreiben, aber der Bouncer wird nicht besonders gut unterstützt. Zum Beispiel wurde vor einem Monat die Möglichkeit, mehrere PgBouncers auf einem Port auszuführen, implementiert. Der Pull-Request für diese Funktion wurde jedoch vor mehreren Jahren eingereicht.

Ein weiteres Beispiel: In Postgres können Sie eine laufende Abfrage abbrechen, indem Sie einen Secret über eine andere Verbindung ohne zusätzliche Authentifizierung senden. Einige Clients senden jedoch einfach einen TCP-Reset, also reißen sie die Netzwerkverbindung ab. Was macht der Bouncer dabei? Er macht nichts. Er führt die Abfrage weiter aus. Wenn Sie eine große Anzahl von Verbindungen haben, die kleine Abfragen ausführen, die die Datenbank belasten, ist es nicht genug, nur die Verbindung vom Bouncer zu trennen; Sie müssen auch die Abfragen beenden, die in der Datenbank laufen.
Das wurde gepatcht, und dieses Problem wurde immer noch nicht in das Upstream des Bouncers integriert.

Damit sind wir zu dem Punkt gekommen, dass wir unseren eigenen Connection-Pooler benötigen, der sich weiterentwickelt, gepatcht werden kann und schnell auf Probleme reagiert. Natürlich muss er auch mehrstufig sein.

Die Mehrstufigkeit haben wir als primäre Aufgabe definiert. Wir müssen in der Lage sein, eine hohe Welle eingehender TLS-Verbindungen zuverlässig zu bewältigen.
Dazu mussten wir eine eigene Bibliothek entwickeln, die Machinarium heißt. Sie dient dazu, die Maschinenzustände einer Netzwerkverbindung als sequentiellen Code zu beschreiben. Wenn Sie den Quellcode von libpq betrachten, werden Sie ziemlich komplexe Aufrufe sehen, die Ihnen ein Ergebnis zurückgeben und sagen: „Ruf mich später noch einmal an. Im Moment habe ich IO, aber wenn das IO vorbei ist, habe ich Last für die CPU.“ Das ist ein mehrstufiges Schema. Netzwerkinteraktionen werden normalerweise als Zustandsmaschine beschrieben. Mit vielen Regeln wie "Wenn ich zuvor einen Paketheader der Größe N erhalten habe, erwarte ich jetzt N Bytes" oder "Wenn ich ein SYNC-Paket gesendet habe, warte ich jetzt auf ein Paket mit Metadaten des Ergebnisses." Es entsteht ein ziemlich schwieriger, kontraintuitiver Code, als ob ein Labyrinth in eine lineare Entfaltung transformiert würde. Wir haben es so gemacht, dass der Programmierer anstelle einer Zustandsmaschine den Hauptinteraktionspfad als normalen imperativen Code beschreibt. In diesem imperativen Code müssen einfach die Stellen eingefügt werden, an denen die Ausführungssequenz unterbrochen werden muss, um auf Daten aus dem Netzwerk zu warten, und den Ausführungskontext an eine andere Koroutine (green thread) zu übergeben. Dieser Ansatz ist ähnlich, als würden wir den am häufigsten erwarteten Weg im Labyrinth aufschreiben und dann Abzweigungen hinzufügen.

Am Ende haben wir einen Stream, der TCP-Akzeptanz durchführt und über Round-Robin mehrere Worker mit TPC-Verbindungen versorgt.
Dabei arbeitet jede Client-Verbindung immer auf einem Prozessor, was sie cache-freundlich macht.
Zusätzlich haben wir die Zusammenstellung kleiner Pakete zu einem großen Paket optimiert, um den System-TCP-Stack zu entlasten.

Zudem haben wir das transaktionale Pooling verbessert, indem Odyssey bei entsprechender Konfiguration CANCEL und ROLLBACK senden kann, wenn die Netzwerkverbindung unterbrochen wird, d.h. wenn auf die Anfrage niemand wartet. Odyssey informiert die Datenbank, dass sie die Anfrage nicht weiterverfolgen soll, um wertvolle Ressourcen zu sparen.
Wann immer möglich, behalten wir die Verbindungen zum gleichen Client bei. Das verhindert, dass application_name_add_host neu eingerichtet wird. Wenn es möglich ist, benötigen wir keine zusätzliche Neukonfiguration von Parametern, die für die Diagnostik erforderlich sind.

Wir arbeiten im Sinne von Yandex.Cloud. Wenn Sie managed PostgreSQL verwenden und einen Connection Pooler eingerichtet haben, können Sie eine logische Replikation nach außen erstellen, d. h. Sie können uns verlassen, wenn Sie möchten, mithilfe der logischen Replikation. Der Bouncer gibt den Datenstrom der logischen Replikation nach außen nicht weiter.

Dies ist ein Beispiel für die Konfiguration der logischen Replikation.

Außerdem unterstützen wir die physische Replikation nach außen. In der Cloud ist dies natürlich nicht möglich, da dann Ihr Cluster zu viele Informationen über sich selbst preisgibt. In Ihren Installationen ist jedoch, falls Sie physische Replikation über den Connection Pooler in Odyssey benötigen, dies möglich.

Odyssey bietet eine vollständig kompatible Überwachung mit PgBouncer. Wir haben eine ähnliche Konsole, die fast alle gleichen Befehle ausführt. Wenn etwas fehlt, senden Sie uns einen Pull-Request oder zumindest ein Issue auf GitHub, und wir werden die benötigten Befehle fertigstellen. Aber die grundlegende Funktionalität der PgBouncer-Konsole ist bereits vorhanden.

Und natürlich haben wir auch ein Error Forwarding. Wir geben den Fehler zurück, den die Datenbank gemeldet hat. Sie erhalten Informationen darüber, warum Sie nicht auf die Datenbank zugreifen können, und nicht nur, dass Sie nicht darauf zugreifen können.

Diese Funktion wird deaktiviert, falls Sie 100 % Kompatibilität mit PgBouncer benötigen. Wir können uns ähnlich wie Bouncer verhalten, nur für den Fall.
Entwicklung
Einige Worte über den Quellcode von Odyssey.

Es gibt zum Beispiel die Befehle "Pause / Fortsetzen". Diese werden normalerweise verwendet, um die Datenbank zu aktualisieren. Wenn Sie Postgres aktualisieren müssen, können Sie es im Connection Pooler anhalten, ein pg_upgrade durchführen und anschließend wieder fortsetzen. Aus Sicht des Clients wird es so aussehen, als ob die Datenbank einfach kurz angehalten wurde. Diese Funktionalität wurde uns von Mitgliedern der Community bereitgestellt. Sie ist noch nicht zusammengeführt, aber bald wird alles bereit sein. (Bereits zusammengeführt)

— bereits zusammengeführt
Darüber hinaus ist eines der neuen Features in PgBouncer die Unterstützung der SCRAM-Authentifizierung, die uns ebenfalls von jemandem gebracht wurde, der nicht bei Yandex.Cloud arbeitet. Beides ist komplexe und wichtige Funktionalitäten.

Deshalb möchte ich erzählen, woraus Odyssey besteht, falls Sie auch gerade ein wenig Code schreiben möchten.
Sie haben eine ursprüngliche Odyssey-Datenbank, die auf zwei Hauptbibliotheken basiert. Die Kiwi-Bibliothek ist eine Implementierung des Postgres-Nachrichtenprotokolls. Das bedeutet, dass das native Proto 3 in Postgres die Standardnachrichten sind, über die Front-Ends und Back-Ends miteinander kommunizieren können. Diese sind in der Kiwi-Bibliothek implementiert.
Die Machinarium-Bibliothek ist eine Implementierung von Streaming. Ein kleiner Teil dieses Machinarium ist in Assembler geschrieben. Aber keine Sorge, es sind nur 15 Zeilen.

Architektur von Odyssey. Es gibt eine Hauptmaschine, in der Coroutines ausgeführt werden. In dieser Maschine werden eingehende TCP-Verbindungen akzeptiert und an die Worker verteilt.
Innerhalb eines Workers kann ein Handler mehrere Clients bearbeiten. Außerdem laufen im Hauptthread die Konsole und die Verarbeitung von Cronaufgaben zur Entfernung von Verbindungen, die im Pool nicht mehr benötigt werden.

Für die Tests von Odyssey wird das Standard-Testset von Postgres verwendet. Wir führen einfach install-check über den Bouncer und Odyssey aus und erhalten ein Null-Div. Einige Tests, die mit der Datumsformatierung verbunden sind, schlagen sowohl beim Bouncer als auch bei Odyssey völlig gleichmäßig fehl.
Darüber hinaus gibt es zahlreiche Treiber, die entsprechend getestet werden. Diese Tests nutzen wir, um Odyssey zu prüfen.

Zudem müssen wir aufgrund unserer Kaskaden-Konfiguration verschiedene Kombinationen testen: Postgres + Odyssey, PgBouncer + Odyssey, Odyssey + Odyssey, um sicherzustellen, dass Odyssey auch in einem der Teile der Kaskade weiterhin wie erwartet funktioniert.
Rätsel

Wir setzen Odyssey in der Produktion ein. Es wäre nicht fair zu sagen, dass alles reibungslos funktioniert. Ja, es funktioniert, aber nicht immer. Zum Beispiel lief in der Produktion alles gut, bis unsere Freunde von PostgreSQL Professional sagten, dass wir einen Memory Leak haben. Diese waren tatsächlich vorhanden, wir haben sie behoben. Aber es war einfach.

Dann haben wir festgestellt, dass im Connection Pooler eingehende TLS-Verbindungen und ausgehende TLS-Verbindungen vorhanden sind. Und für die Verbindungen werden sowohl Client- als auch Serverzertifikate benötigt.
Die Bouncer- und Odyssey-Server-Zertifikate lesen ihren pcache neu, aber es ist nicht notwendig, die Client-Zertifikate aus dem pcache zu lesen, da unser skalierbarer Odyssey letztendlich an der Systemleistung der Zertifikatslesung scheitert. Das war für uns überraschend, denn das Problem trat nicht sofort auf. Zunächst skalierte es linear, aber nach 20.000 gleichzeitigen eingehenden Verbindungen trat die Problematik auf.

Das Pluggable Authentication Method ermöglicht die Authentifizierung über integrierte Linux-Methoden. In PgBouncer wird dies so umgesetzt, dass es einen separaten Thread zur Beantwortung von PAM-Anfragen gibt und einen Hauptthread, der die aktuelle Verbindung bedient und um eine vorübergehende Aussetzung in den PAM-Thread bitten kann.
Wir haben das nicht umgesetzt aus einem einfachen Grund. Wir haben viele Threads. Warum sollten wir das benötigen?
Das kann letztendlich Probleme verursachen, denn wenn Sie eine PAM-Authentifizierung und keine PAM-Authentifizierung haben, kann eine große Welle von PAM-Authentifizierungen die nicht PAM-Authentifizierung erheblich verzögern. Das ist eines jener Dinge, die wir nicht behoben haben. Aber wenn Sie das beheben möchten, können Sie sich damit beschäftigen.

Ein weiteres Problem war, dass wir einen Thread haben, der alle eingehenden Verbindungen akzeptiert. Diese werden dann an einen Worker-Pool weitergeleitet, wo der TLS-Handshake erfolgt.
Das bedeutet, wenn Sie eine kohärente Welle von 20.000 Netzwerkverbindungen haben, werden sie alle akzeptiert. Auf der Client-Seite beginnt libpq dann mit dem Timeout-Countdown. Standardmäßig ist dies anscheinend auf 3 Sekunden eingestellt.
Wenn sie nicht gleichzeitig auf die Datenbank zugreifen können, bedeutet das, dass sie nicht auf die Datenbank zugreifen können, da alles nicht durch exponentielles Retry abgedeckt ist.
Wir haben die Strategie von PgBouncer übernommen und implementiert, dass wir die Anzahl der TCP-Verbindungen, die wir akzeptieren, drosseln.
Wenn wir sehen, dass wir Verbindungen akzeptieren, diese aber letztendlich nicht den Handshake schaffen, setzen wir sie in eine Warteschlange, um Ressourcen des Hauptprozessors zu schonen. Das führt dazu, dass der gleichzeitige Handshake nicht für alle eingehenden Verbindungen durchgeführt werden kann. Aber zumindest wird jemand auf die Datenbank zugreifen, auch wenn die Last hoch ist.
Roadmap
Was würden wir in Zukunft in Odyssey gerne sehen? Dass wir selbst entwickeln können und was wir von der Gemeinschaft erwarten?

Stand August 2019.
So sah der Odyssey-Fahrplan im August aus:
- Wir wollten SCRAM- und PAM-Authentifizierung.
- Wir wollten, dass Leseanfragen an den Standby weitergeleitet werden.
- Wir hätten gerne ein Online-Neustart.
- Und die Möglichkeit, den Server zu pausieren.

Die Hälfte dieses Fahrplans ist erledigt, und zwar nicht von uns. Das ist gut. Lassen Sie uns also über das sprechen, was noch übrig ist, und etwas hinzufügen.

? У нас есть реплики, которые без выполнения запросов будут просто греть воздух. Они нам необходимы для обеспечения failover и switchover. В случае проблем в одном из дата-центре хотелось бы их занять какой-то полезной работой. Потому что те же самые центральные процессоры, ту же самую память мы не можем сконфигурировать по-другому, потому что иначе не будет работать репликация.

Grundsätzlich gibt es in Postgres seit Version 10 die Möglichkeit, bei der Verbindung auch session_attrs anzugeben. Sie können in der Verbindung alle Datenbank-Hosts auflisten und angeben, warum Sie die Datenbank ansprechen: um zu schreiben oder nur zu lesen. Der Treiber wählt selbst den ersten Host in der Liste aus, der am besten passt und die Anforderungen an die session_attrs erfüllt.

Das Problem dieses Ansatzes ist jedoch, dass er den Lag der Replikation nicht kontrolliert. Es kann eine Replikation geben, die sich in einem für Ihren Dienst inakzeptablen Zeitrahmen verzögert. Um eine vollständige Ausführung von Leseanfragen auf der Replikation zu ermöglichen, müssen wir in Odyssey die Möglichkeit unterstützen, nicht zu funktionieren, wenn das Lesen nicht möglich ist.
Odyssey muss gelegentlich die Datenbank abfragen, um den Replikationsabstand vom Primary zu ermitteln. Wenn dieser das Grenzwert erreicht hat, dürfen keine neuen Anfragen an die Datenbank gestellt werden. Der Kunde sollte darüber informiert werden, dass er die Verbindungen neu initiieren und möglicherweise einen anderen Host für die Anfragen auswählen muss. Dies ermöglicht es der Datenbank, den Replikationslag schneller zu beheben und wieder für Anfragen bereit zu sein.
Es ist schwierig, einen genauen Zeitrahmen anzugeben, da dies Open Source ist. Ich hoffe jedoch, dass es nicht wie bei den Kollegen von PgBouncer 2,5 Jahre dauert. Diese Funktion würde ich mir in Odyssey wünschen.

Derzeit können Sie Prepared Statements auf zwei Arten erstellen. Erstens können Sie den SQL-Befehl "prepared" ausführen. Um diesen SQL-Befehl zu verstehen, müssen wir SQL auf der Seite von Bouncer verstehen lernen. Das wäre übertrieben, denn wir benötigen einen vollständigen Parser. Wir können nicht jede SQL-Anweisung parsen.
Es gibt jedoch ein Prepared Statement auf der Ebene des Nachrichtenprotokolls in proto3. Und das ist der Punkt, an dem die Information über die Erstellung des Prepared Statements in strukturierter Form ankommt. Wir könnten das Verständnis unterstützen, dass ein Client über eine serverseitige Verbindung um die Erstellung von Prepared Statements gebeten hat. Selbst wenn die Transaktion abgeschlossen ist, müssen wir die Konsistenz zwischen Server und Client aufrechterhalten.
Hier entsteht jedoch ein Missverständnis im Dialog, da jemand darüber spricht, dass es wichtig ist zu verstehen, welches spezifische Prepared Statement der Client erstellt hat und die serverseitige Verbindung zwischen allen Clients zu teilen, die diese serverseitige Verbindung hergestellt haben, also die, die ein solches Prepared Statement erstellt haben.
Andres Freund sagte, dass, wenn ein Kunde zu Ihnen kommt, der bereits in einer anderen serverseitigen Verbindung ein solches Prepared Statement erstellt hat, Sie es für ihn erstellen sollten. Aber es scheint ein wenig falsch zu sein, Datenbankanfragen anstelle des Clients auszuführen. Aus der Sicht eines Entwicklers, der ein Protokoll für die Interaktion mit der Datenbank schreibt, wäre es jedoch bequem, wenn ihm einfach eine Netzwerkverbindung gegeben würde, die ein solches vorbereitetes Statement enthält.

Und noch eine Funktion, die wir umsetzen müssen. Momentan haben wir Monitoring, das mit PgBouncer kompatibel ist. Wir können die durchschnittliche Ausführungszeit einer Anfrage zurückgeben. Aber die durchschnittliche Zeit ist wie die Durchschnittstemperatur im Krankenhaus: Einige sind kalt, einige sind warm – im Durchschnitt sind alle gesund. Das ist nicht wahr.
Wir müssen die Unterstützung für Perzentile implementieren, die darauf hinweisen, dass es langsame Anfragen gibt, die Ressourcen verbrauchen, und die das Monitoring akzeptabler machen würden.

Das Wichtigste ist, dass wir Version 1.0 haben wollen (Version 1.1 ist bereits veröffentlicht). Der Punkt ist, dass Odyssey derzeit in der Version 1.0rc ist, also als Release-Candidate. Und all die Probleme, die ich aufgezählt habe, wurden genau mit dieser Version behoben, abgesehen von der Speicherleckage.
Was bedeutet Version 1.0 für uns? Wir setzen Odyssey auf unseren Datenbanken ein. Es funktioniert bereits auf unseren Datenbanken, aber wenn es den Punkt von 1.000.000 Anfragen pro Sekunde erreicht, können wir sagen, dass es sich um die Release-Version handelt und dass dies die Version ist, die wir 1.0 nennen können.
In der Community haben einige Personen um die Integration einer Pause und von SCRAM in Version 1.0 gebeten. Das würde jedoch bedeuten, dass wir bereits die nächste Version in die Produktion bringen müssten, da weder SCRAM noch die Pause bis jetzt zusammengeführt wurden. Dieses Thema wird jedoch wahrscheinlich zeitnah geklärt werden.

Ich erwarte eure Pull Requests. Zudem würde ich gerne erfahren, welche Probleme ihr mit Bouncer habt. Lasst uns darüber sprechen. Vielleicht können wir einige Funktionen implementieren, die für euch wichtig sind.
An dieser Stelle bin ich fertig, und ich würde gerne von euch hören. Vielen Dank!
Fragen
Wenn ich meinen application_name festlege, wird er dann korrekt übergeben, auch im Transaction Pooling in Odyssey?
In Odyssey oder in Bouncer?
In Odyssey. In Bouncer wird er übergeben.
Wir werden das Set machen.
Und wenn meine echte Verbindung zu anderen Verbindungen wechselt, wird sie dann übertragen?
Wir werden alle Parameter, die in der Liste aufgeführt sind, setzen. Ich kann nicht sagen, ob application_name in dieser Liste enthalten ist. Ich habe ihn dort gesehen. Wir werden all die gleichen Parameter festlegen. Mit einer Anfrage wird alles gesetzt, was der Kunde beim Start festgelegt hat.
Vielen Dank, Andrei, für den Vortrag! Ein guter Vortrag! Ich freue mich, dass Odyssey sich mit jeder Minute schneller weiterentwickelt. Ich wünsche weiterhin viel Erfolg. Wir haben uns bereits bei Ihnen nach einer Multi-Data-Source-Verbindung erkundigt, damit Odyssey gleichzeitig mit verschiedenen Datenbanken verbunden werden kann, d. h. Master-Slave, und danach automatisch nach einem Failover mit einem neuen Master verbunden werden kann.
Ja, ich erinnere mich an diese Diskussion. Momentan gibt es mehrere Speicherlösungen. Aber einen Wechsel zwischen ihnen gibt es nicht. Wir müssen auf unserer Seite den Server abfragen, ob er noch aktiv ist und feststellen, dass ein Failover stattgefunden hat. Wer wird pg_recovery aufrufen? Ich habe eine Standardmethode, um zu erkennen, dass wir nicht mit dem Master verbunden sind. Wir sollten auch aus Fehlern lernen oder? Die Idee ist interessant und wird besprochen. Geben Sie mehr Kommentare. Wenn Sie Leute haben, die sich mit C auskennen, wäre das großartig.
Das Thema Skalierung von Replikaten interessiert uns ebenfalls, da wir die Übernahme von replizierten Clustern für Anwendungsentwickler so einfach wie möglich gestalten möchten. Hier hätten wir gerne weitere Kommentare, d. h. wie genau man das am besten umsetzt.
Die Frage betrifft auch die Replikate. Es ergibt sich, dass Sie einen Master und mehrere Replikate haben. Und es ist klar, dass auf die Replikate seltener zugegriffen wird als auf den Master, da es Unterschiede geben kann. Sie haben gesagt, dass diese Unterschiede in den Daten so gravierend sein können, dass sie Ihr Geschäft nicht unterstützen, und Sie werden nicht darauf zugreifen, bis die Replikation vollständig ist. Wenn Sie jedoch lange nicht auf die Replikate zugegriffen haben und dann wieder anfangen, werden die benötigten Daten möglicherweise nicht sofort verfügbar sein. Das heißt, wenn wir ständig auf den Master zugreifen, ist der Cache dort aufgewärmt, während der Cache in den Replikaten etwas hinterherhinkt.
Ja, das stimmt. In pcache werden keine Datenblöcke vorhanden sein, die Sie benötigen, im real cache wird es keine Informationen über die Tabellen geben, die Sie suchen, und in den Plänen werden keine geparsten Abfragen vorhanden sein, es wird überhaupt nichts geben.
Und wenn Sie einen Cluster haben und ein neues Replikat hinzufügen, dann ist alles schlecht, solange es gestartet wird, das heißt, es baut seinen Cache auf.
Ich verstehe die Idee. Der richtige Ansatz wäre, zunächst einen kleinen Prozentsatz der Anfragen an ein Replica zu senden, um den Cache aufzuwärmen. Grob gesagt, haben wir die Bedingung, dass wir nicht mehr als 10 Sekunden hinter dem Master liegen dürfen. Und diese Bedingung sollte nicht auf einmal, sondern schrittweise für einige Kunden aktiviert werden.
Ja, das Gewicht erhöhen.
Das ist eine gute Idee. Aber zuerst muss diese Deaktivierung implementiert werden. Zuerst müssen wir uns abschalten, und dann überlegen wir, wie wir wieder aktiv werden. Eine großartige Funktion, um sanft zu starten.
In Nginx gibt es eine solche Option sanfter Start im Cluster für den Server. Und er erhöht allmählich die Last.
Ja, großartige Idee, wir werden es ausprobieren, wenn wir dazu kommen.
Quelle: habr.com
