
In dem Bericht wird Andrey Borodin erläutern, wie sie die Erfahrungen mit der Skalierung von PgBouncer bei der Gestaltung des Connection Poolers berücksichtigt haben. , wie sie es in der Produktion ausgerollt haben. Außerdem diskutieren wir, welche Funktionen wir uns im neuen Versionen des Poolers wünschen würden: Es ist uns wichtig, nicht nur unsere eigenen Bedürfnisse zu decken, sondern auch die Gemeinschaft der Benutzer weiterzuentwickeln. .
Video:


Hallo zusammen! Mein Name ist Andrey.

Bei Yandex beschäftige ich mich mit der Entwicklung von Open Source-Datenbanken. Und heute haben wir ein Thema über den Connection Pooler.

Wenn Sie wissen, wie man Connection Pooler auf Russisch nennt, lassen Sie es mich bitte wissen. Ich würde gerne einen guten technischen Begriff finden, der sich in der technischen Literatur etabliert.
Das Thema ist ziemlich kompliziert, weil viele Datenbanken einen eingebauten Connection Pooler haben und man darüber nicht einmal Bescheid wissen muss. Einige Einstellungen gibt es zwar überall, aber so läuft es in Postgres nicht. Parallel dazu hält Nikolai Samokhvalov auf der HighLoad++ 2019 eine Präsentation über die Optimierung von Abfragen in Postgres. Ich verstehe, dass hier Leute sind, die ihre Abfragen bereits perfekt optimiert haben, und das sind Personen, die mit selteneren systemtechnischen Problemen konfrontiert sind, die mit Netzwerken und Ressourcenauslastung zusammenhängen. An einigen Stellen könnte das ziemlich schwierig sein, da die Probleme nicht offensichtlich sind.

Bei Yandex gibt es Postgres. In Yandex.Cloud leben viele Dienste von Yandex. Wir haben mehrere Petabyte Daten, die nicht weniger als eine Million Abfragen pro Sekunde in Postgres generieren.

Und wir stellen allen Diensten ein ziemlich typisches Cluster zur Verfügung – das ist der Haupt-Knoten des Clusters, zwei normale Replikate (ein synchrones und ein asynchrones), Datensicherung und die Skalierung von Leseabfragen auf dem Replikat.

Jeder Knoten des Clusters ist ein Postgres, auf dem außer Postgres und Systemüberwachung auch ein Connection Pooler installiert ist. Der Connection Pooler wird für das Fencing und zu seinem eigentlichen Zweck verwendet.

Was ist der eigentliche Zweck des Connection Pooler?

In Postgres gilt das Prozessmodell für die Arbeit mit Datenbanken. Das bedeutet, dass eine Verbindung ein Prozess ist, ein Backend von Postgres. Und in diesem Backend gibt es viele verschiedene Caches, die es teuer machen, sie für verschiedene Verbindungen unterschiedlich zu gestalten.

Darüber hinaus gibt es im Postgres-Code ein Array namens procArray. Es enthält die grundlegenden Informationen über Netzwerkverbindungen. Fast alle Algorithmen zur Verarbeitung des procArray haben eine lineare Komplexität, da sie das gesamte Array der Netzwerkverbindungen durchlaufen. Das ist ein ziemlich schneller Zyklus, aber mit einer großen Anzahl von eingehenden Netzwerkverbindungen wird es etwas teurer. Wenn es also teurer wird, kann man letztendlich einen sehr hohen Preis für eine große Anzahl von Netzwerkverbindungen zahlen.

Es gibt 3 mögliche Ansätze:
- Auf der Anwendungseite.
- Auf der Datenbankseite.
- Und dazwischen, d. h. verschiedene Kombinationen.
Leider befindet sich der integrierte Pooler derzeit in der Entwicklungsphase. Freunde bei PostgreSQL Professional sind hauptsächlich damit beschäftigt. Wann er erscheinen wird, ist schwer vorherzusagen. Tatsächlich haben wir für die Wahl des Architekten zwei Lösungen: den Pool auf der Anwendungseite und den Proxy-Pool.

Der Pool auf der Anwendungseite ist der einfachste Weg. Fast alle Client-Treiber bieten Ihnen die Möglichkeit, Millionen Ihrer Verbindungen im Code in wenigen Dutzend Datenbankverbindungen darzustellen.

Es gibt jedoch ein Problem, wenn Sie zu einem bestimmten Zeitpunkt den Backend skalieren möchten, wenn Sie es auf viele virtuelle Maschinen bereitstellen möchten.

Dann erkennen Sie auch, dass Sie mehrere Verfügbarkeitszonen und mehrere Rechenzentren haben. Der Ansatz mit dem Client-seitigen Pooling führt zu großen Zahlen. Groß bedeutet etwa 10.000 Verbindungen. Das ist das Maximum, was normal funktionieren kann.

Wenn man über Proxy-Pooler spricht, gibt es zwei Pooler, die eine Menge Fähigkeiten haben. Sie sind nicht nur Pooler. Sie sind Pooler + haben weitere großartige Funktionen. Das sind und .
Aber leider ist diese zusätzliche Funktionalität nicht für alle notwendig. Und das führt dazu, dass Pooler nur sessiönliches Pooling unterstützen, d. h. ein eingehender Client, ein ausgehender Client zur Datenbank.
Für unsere Anforderungen ist das nicht sehr gut geeignet, daher verwenden wir PgBouncer, das Transaktions-Pooling implementiert, d. h. die Serververbindungen werden den Clientverbindungen nur für die Dauer der Transaktion zugeordnet.

Und bei unserer Last ist das tatsächlich der Fall. Aber es gibt einige Probleme..
Die Probleme beginnen, wenn Sie eine Sitzung diagnostizieren möchten, da alle eingehenden Verbindungen lokal sind. Alle kommen über loopback, und es wird schwierig, die Sitzung zu verfolgen.

Natürlich können Sie application_name_add_host verwenden. Dies ist eine Methode auf der Seite von Bouncer, um eine IP-Adresse zu application_name hinzuzufügen. Aber application_name wird über eine zusätzliche Verbindung festgelegt.

In diesem Diagramm stellt die gelbe Linie die tatsächlichen Anfragen dar, während die blaue Linie die Anfragen sind, die in die Datenbank gelangen. Der Unterschied besteht darin, dass application_name eingestellt ist, der nur für das Tracking benötigt wird, aber keineswegs kostenlos ist.

Darüber hinaus kann man in Bouncer keinen einzelnen Pool einschränken, d.h. die Anzahl der Verbindungen zur Datenbank für einen bestimmten Benutzer oder eine bestimmte Datenbank.

Worauf führt das? Sie haben einen stark belasteten Dienst, der in C++ geschrieben ist, und in der Nähe befindet sich ein kleiner Dienst in Node, der der Datenbank nichts antut, aber dessen Treiber verrückt spielt. Er öffnet 20.000 Verbindungen, während alles andere warten muss. Ihr Code ist sogar in Ordnung.

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

Das hätte man auf der Seite von Postgres machen können, d.h. die Rollen in der Datenbank auf eine bestimmte Anzahl von Verbindungen einschränken.

Aber dann verlieren Sie die Möglichkeit zu verstehen, warum Sie keine Verbindungen zum Server haben. PgBouncer gibt keinen Verbindungsfehler durch, er gibt immer die gleiche Information zurück. Und Sie können nicht verstehen: Vielleicht hat sich Ihr Passwort geändert, vielleicht ist die Datenbank einfach abgestürzt, vielleicht stimmt etwas nicht. Aber eine Diagnose gibt es nicht. Wenn eine Sitzung nicht hergestellt werden kann, erfahren Sie nicht, warum das nicht möglich ist.

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

Sie schauen in den Task-Manager und sehen, dass Bouncer einthreaded ist. Das ist der Wendepunkt im Leben des Dienstes. Sie verstehen, dass Sie sich auf die Skalierung der 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 etwas gepatcht.

Und haben es so eingerichtet, dass mehrere Bouncers über TCP-Port-Wiederverwendung gestartet werden können. Und das Betriebssystem verteilt eingehende TCP-Verbindungen automatisch round-robin zwischen ihnen.

Das ist für die Clients transparent, d.h. es sieht so aus, als hätten Sie einen Bouncer, aber es gibt eine Fragmentierung der idle-Verbindungen zwischen den laufenden Bouncers.

Und irgendwann können Sie feststellen, dass diese 3 Bouncers jeweils ihren Kern zu 100 % verbrauchen. Sie benötigen ziemlich viele Bouncers. Warum?

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

Und oben können Sie viele kryptografische Funktionen sehen, die bei der Welle eingehender Verbindungen ausgeführt werden. Da unser primärer Server zwischen Verfügbarkeitszonen wechseln kann, ist eine Welle eingehender Verbindungen eine recht typische Situation. Das bedeutet, dass aus irgendeinem Grund der alte primäre Server nicht verfügbar war und die gesamte Last in ein anderes Rechenzentrum gesendet wurde. Sie werden alle gleichzeitig zur Begrüßung mit TLS ankommen.

Eine große Anzahl von TLS-Handshakes könnte sich jedoch nicht begrüßen, sondern ihn strangulieren. Aufgrund des Timeouts kann die Welle eingehender Verbindungen ununterbrochen werden. Wenn Sie einen Retry in die Datenbank ohne exponentiellen Backoff haben, werden sie immer wieder in einer kohärenten Welle ankommen.

Hier ist ein Beispiel für 16 PgBouncer, die 16 Kerne zu 100 % auslasten.

Wir sind zu einem kaskadierenden PgBouncer gekommen. Dies ist die beste Konfiguration, die wir mit unserer Last auf dem Bouncer erreichen können. Externe Bouncers dienen uns für den TCP-Handshake, während interne Bouncers für das eigentliche Pooling dienen, um externe Verbindungen nicht zu stark zu fragmentieren.

In dieser Konfiguration ist ein sanfter Neustart möglich. Sie können alle 18 Bouncers einzeln neu starten. Aber es ist ziemlich schwierig, eine solche Konfiguration aufrechtzuerhalten. Systemadministratoren, DevOps und die Personen, die tatsächlich für diesen Server verantwortlich sind, werden mit einem solchen Schema nicht besonders erfreut sein.

Es scheint, als könnten wir all unsere Anpassungen in Open Source vorantreiben, aber der Bouncer wird nicht sehr gut unterstützt. Zum Beispiel wurde die Möglichkeit, mehrere PgBouncer auf einem Port auszuführen, vor einem Monat eingepflegt. Der Pull-Request für diese Funktion wurde vor einigen Jahren gestellt.

Oder ein weiteres Beispiel. In Postgres können Sie eine laufende Abfrage abbrechen, indem Sie ein geheimes Kommando über eine andere Verbindung senden, ohne zusätzliche Authentifizierung. Doch einige Clients senden einfach einen TCP-Reset, d. h. sie trennen die Netzwerkverbindung. Was wird Bouncer in diesem Fall tun? Er wird nichts tun. Er wird die Abfrage weiterhin ausführen. Wenn Sie eine enorme Anzahl von Verbindungen haben, die die Datenbank mit kleinen Abfragen überflutet haben, reicht es einfach nicht aus, die Verbindung von Bouncer zu trennen; es ist auch nötig, die laufenden Abfragen, die in der Datenbank ausgeführt werden, zu beenden.
Das wurde gepatcht und dieses Problem wurde immer noch nicht in den upstream Bouncer integriert.

Und so sind wir zu dem Punkt gelangt, dass wir unseren eigenen Connection Pooler benötigen, der sich weiterentwickeln, gepatcht werden kann, in dem wir Probleme schnell beheben können und der natürlich mehrthreading-fähig sein muss.

Multithreading haben wir als Hauptaufgabe festgelegt. Wir müssen gut mit der Welle eingehender TLS-Verbindungen umgehen können.
Zu diesem Zweck mussten wir eine separate Bibliothek entwickeln, die Machinarium heißt und die dazu dient, die Maschinenzustände einer Netzwerkverbindung als sequenziellen Code zu beschreiben. Wenn Sie sich den Quellcode von libpq ansehen, werden Sie ziemlich komplexe Aufrufe sehen, die Ihnen ein Ergebnis zurückgeben und sagen: „Ruf mich später nochmal an. Momentan habe ich IO, aber wenn das IO vorbei ist, werde ich CPU-Belastung haben." Und das ist ein mehrschichtiges Schema. Die Netzwerkinteraktionen werden normalerweise durch eine Zustandsmaschine beschrieben. Eine Vielzahl von Regeln wie „Wenn ich zuvor einen Paket-Header der Größe N erhalten habe, erwarte ich jetzt N Bytes“, „Wenn ich ein SYNC-Paket gesendet habe, warte ich jetzt auf ein Paket mit Metadaten zum Ergebnis“. Das ergibt einen ziemlich schwierigen, kontra-intuitiven Code, als ob ein Labyrinth in eine zeilenweise Entfaltung umgewandelt wurde. Wir haben es so gestaltet, dass der Programmierer anstelle einer Zustandsmaschine den Hauptweg der Interaktion in Form gewöhnlichen imperativen Codes beschreibt. Einfach muss in diesem imperativen Code an den Stellen, an denen die Ausführungssequenz aufgrund der Netzwerkinformationen unterbrochen werden muss, der Kontext der Ausführung an eine andere Koroutine (Green Thread) übergeben werden. Dieser Ansatz ist analog dazu, dass wir den am häufigsten erwarteten Weg im Labyrinth nacheinander aufzeichnen und dann Verzweigungen hinzufügen.

Am Ende haben wir einen Prozess, der TCP-acceptiert und das TCP-Verbindung über Round-Robin an mehrere Worker weitergibt.
Dabei arbeitet jede Client-Verbindung immer auf einem einzigen Prozessor. Das macht sie cache-freundlich.
Außerdem haben wir die Zusammenstellung kleiner Pakete zu einem großen Paket überarbeitet, um den System-TCP-Stack zu entlasten.

Zudem haben wir das transaktionale Pooling verbessert, sodass Odyssey bei aktivierter Einstellung ein CANCEL und ROLLBACK senden kann, falls die Netzwerkverbindung unterbrochen wird, d. h. wenn niemand auf die Anfrage wartet. Odyssey wird der Datenbank mitteilen, dass sie sich nicht anstrengen soll, die Anfrage auszuführen, die wertvolle Ressourcen verbrauchen könnte.
Wenn möglich, behalten wir die Verbindungen zu demselben Client bei. Das ermöglicht es, application_name_add_host nicht neu zu installieren. Wenn dies möglich ist, haben wir keine zusätzliche Neuinstallation der Parameter, die für die Diagnose benötigt werden.

Wir arbeiten im Interesse von Yandex.Cloud. Wenn Sie managed PostgreSQL verwenden und ein Connection Pooler eingerichtet haben, können Sie eine logische Replikation extern einrichten, d. h. Sie können uns verlassen, wenn Sie möchten, durch logische Replikation. Der Bouncer wird die logische Replikation extern nicht weitergeben.

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

Außerdem haben wir Unterstützung für die physische Replikation nach außen. In der Cloud ist es selbstverständlich nicht möglich, da Sie sonst dem Cluster zu viele Informationen über sich selbst geben würden. Aber in Ihren Installationen ist es möglich, physische Replikation über den Connection Pooler in Odyssey zu nutzen.

Odyssey hat ein voll kompatibles Monitoring mit PgBouncer. Wir haben eine ähnliche Konsole, die fast alle selben Befehle ausführt. Wenn etwas fehlt, senden Sie einen Pull Request oder zumindest ein Issue auf GitHub, und wir werden die benötigten Befehle ergänzen. Aber die Hauptfunktionen der PgBouncer-Konsole sind bereits vorhanden.

Und natürlich haben wir Error-Forwarding. Wir geben den Fehler zurück, den die Datenbank gemeldet hat. Sie erhalten Informationen, warum Sie nicht in die Datenbank gelangen, und nicht einfach nur, dass Sie es nicht schaffen.

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

Zum Beispiel gibt es 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 pausieren, ein pg_upgrade durchführen und danach wieder fortsetzen. Aus Sicht des Clients wird es so aussehen, als ob die Datenbank einfach nur langsam war. Diese Funktionalität wurde uns von Personen aus der Community bereitgestellt. Sie ist noch nicht zusammengeführt, wird aber bald verfügbar 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 einer Person gebracht wurde, die nicht bei Yandex.Cloud arbeitet. Beides ist eine komplexe und wichtige Funktionalität.

Deshalb möchte ich Ihnen erzählen, woraus Odyssey besteht, vielleicht möchten Sie auch ein wenig Code schreiben.
Sie haben die Basis von Odyssey, die auf zwei Hauptbibliotheken basiert. Die Kiwi-Bibliothek ist eine Implementierung des Postgres-Nachrichtenprotokolls. Das heißt, das native Proto 3 bei Postgres sind die Standardnachrichten, mit denen Frontends und Backends kommunizieren können. Diese sind in der Kiwi-Bibliothek implementiert.
Die Machinarium-Bibliothek ist eine Bibliothek zur Implementierung von Threads. Ein kleiner Teil von Machinarium ist in Assembler geschrieben. Aber keine Sorge, es sind nur 15 Zeilen.

Die Architektur von Odyssey. Es gibt die Hauptmaschine, in der Coroutines ausgeführt werden. In dieser Maschine erfolgt die Annahme eingehender TCP-Verbindungen und die Verteilung auf Worker.
Innerhalb eines Workers kann ein Handler für mehrere Clients arbeiten. Zudem laufen im Hauptthread die Konsole und die Verarbeitung von Cron-Jobs zur Entfernung von Verbindungen, die im Pool nicht mehr benötigt werden.

Zum Testen von Odyssey wird die standardmäßige Testsuite von Postgres verwendet. Wir führen einfach den install-check über Bouncer und über Odyssey aus und erhalten ein Null-Div. Einige Tests, die mit der Formatierung von Daten zu tun haben, bestehen nicht ganz gleich in Bouncer und Odyssey.
Darüber hinaus gibt es zahlreiche Treiber, die ihre eigenen Tests haben. Deren Tests verwenden wir, um Odyssey zu testen.

Außerdem müssen wir aufgrund unserer gestaffelten Konfiguration verschiedene Kombinationen testen: Postgres + Odyssey, PgBouncer + Odyssey, Odyssey + Odyssey, um sicherzustellen, dass, wenn Odyssey in einem der Teile der Kaskade ist, es weiterhin so funktioniert, wie wir es erwarten.
Grapple

Wir verwenden Odyssey in der Produktion. Es wäre unfair zu sagen, dass alles einfach funktioniert. Ja, so ist es, aber nicht immer. Zum Beispiel hat es in der Produktion anfangs einfach funktioniert, dann kamen unsere Freunde von PostgreSQL Professional und sagten, dass wir ein Memory Leak haben. Sie hatten recht, wir haben es behoben. Aber das war einfach.

Dann stellten wir fest, dass im Connection Pooler eingehende TLS-Verbindungen und ausgehende TLS-Verbindungen vorhanden sind. Und für die Verbindungen sind Client-Zertifikate und Server-Zertifikate erforderlich.
Die Server-Zertifikate von Bouncer und Odyssey lesen ihr pcache erneut ein, aber die Client-Zertifikate müssen nicht erneut aus dem pcache gelesen werden, weil unser skalierbares Odyssey schließlich auf die Systemleistung beim Lesen dieses Zertifikats stößt. Das war eine Überraschung für uns, denn das Problem trat nicht sofort auf. Zuerst skalierte es linear, und nach 20.000 gleichzeitigen eingehenden Verbindungen zeigte sich dieses Problem.

Pluggable Authentication Method ist die Möglichkeit, sich mit integrierten Linux-Mitteln zu authentifizieren. In PgBouncer wird es so implementiert, dass es einen separaten Thread gibt, der auf die Antwort von PAM wartet, und einen Haupt-Thread von PgBouncer, der die aktuelle Verbindung bedient und sie bitten kann, in dem PAM-Thread zu leben.
Wir haben das aus einem einfachen Grund nicht umgesetzt. Wir haben viele Threads. Warum brauchen wir das?
Letztendlich kann das Probleme verursachen, denn wenn Sie PAM-Authentifizierung und keine PAM-Authentifizierung haben, kann eine große Welle von PAM-Authentifizierung die Nicht-PAM-Authentifizierung erheblich verzögern. Das ist eine der 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. Und dann werden sie an den Worker-Pool übergeben, wo der TLS-Handschlag stattfinden wird.
Wenn Sie also eine kohärente Welle von 20.000 Netzwerkverbindungen haben, werden sie alle akzeptiert. Und auf der Client-Seite beginnt libpq mit dem Timeout-Count. Standardmäßig beträgt das anscheinend 3 Sekunden.
Wenn sie nicht alle gleichzeitig auf die Datenbank zugreifen können, können sie nicht auf die Datenbank zugreifen, weil das alles nicht mit einem exponentiellen Retry abgedeckt sein kann.
Wir haben beschlossen, hier das Schema von PgBouncer zu kopieren, um eine Begrenzung der Anzahl von TCP-Verbindungen zu haben, die wir akzeptieren.
Wenn wir sehen, dass wir eine Verbindung akzeptieren, diese aber letztendlich nicht den Handshake schaffen, setzen wir sie in eine Warteschlange, damit sie die Ressourcen der Zentraleinheit nicht verbrauchen. Dies führt dazu, dass der gleichzeitige Handshake nicht für alle eingehenden Verbindungen durchgeführt werden kann. Aber zumindest kann jemand auf die Datenbank zugreifen, selbst wenn die Last stark ist.
Roadmap
Was würden wir in Zukunft in Odyssey gerne sehen? Was sind wir bereit, selbst zu entwickeln und was erwarten wir von der Community?

Stand August 2019.
So sah die Roadmap von Odyssey im August aus:
- Wir wollten SCRAM- und PAM-Authentifizierung.
- Wir wollten Forward-Leseanfragen an Standby.
- Wir hätten gerne einen Online-Neustart.
- Und die Möglichkeit, auf dem Server eine Pause einzulegen.

Die Hälfte dieser Roadmap wurde erfüllt, und zwar nicht von uns. Und das ist gut. Lassen Sie uns also das besprechen, was noch übrig ist, und noch etwas hinzufügen.

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

Im Grunde 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 zur Datenbank gehen: um zu schreiben oder nur zu lesen. Der Treiber wählt selbst den ersten Host in der Liste aus, der ihm am besten gefällt und die Anforderungen von session_attrs erfüllt.

Das Problem dieses Ansatzes ist jedoch, dass er das Replikationsdelay nicht überwacht. Sie könnten eine Replik haben, die an einem für Ihren Dienst inakzeptablen Punkt zurückliegt. Um eine vollständige Verarbeitung von Leseanfragen auf der Replik zu ermöglichen, müssen wir im Grunde genommen sicherstellen, dass Odyssey nicht arbeitet, wenn das Lesen nicht möglich ist.
Odyssey sollte gelegentlich in die Datenbank gehen und das Replikationsabstand vom Primärserver abfragen. Und wenn dieses einen Grenzwert erreicht hat, sollten keine neuen Abfragen an die Datenbank zugelassen werden, und dem Client sollte gesagt werden, dass er die Verbindungen neu initiieren und möglicherweise einen anderen Host für die Ausführung der Abfragen wählen muss. Dies ermöglicht es der Datenbank, das Replikationsdelay schneller wiederherzustellen und erneut auf Anfragen zu antworten.
Es ist schwierig, einen Zeitrahmen für die Umsetzung zu nennen, da es Open Source ist. Aber ich hoffe, dass es nicht 2,5 Jahre dauern wird wie bei den Kollegen von PgBouncer. Diese Funktion würden wir gerne in Odyssey sehen.

Jetzt können Sie ein Prepared Statement auf zwei Arten erstellen. Erstens können Sie den SQL-Befehl, nämlich „prepared“, ausführen. Um diesen SQL-Befehl zu verstehen, müssen wir lernen, SQL auf der Bouncer-Seite zu verstehen. Es wäre übertrieben, denn wir benötigen einen vollständigen Parser. Wir können nicht jeden SQL-Befehl parsen.
Aber es gibt ein Prepared Statement auf der Ebene des Nachrichtenprotokolls in proto3. Und das ist der Punkt, an dem die Informationen darüber, dass ein Prepared Statement erstellt wird, in strukturierter Form eintreffen. Wir könnten das Verständnis unterstützen, dass ein Client auf einer bestimmten Serververbindung gebeten hat, Prepared Statements zu erstellen. Und selbst wenn die Transaktion beendet ist, müssen wir die Konsistenz zwischen Server und Client weiterhin aufrechterhalten.
Aber hier entsteht ein Inkonsistenz im Dialog, denn jemand sagt, dass wir verstehen müssen, welche spezifischen Prepared Statements der Client erstellt hat und die Serververbindung zwischen allen Clients, die diese Serververbindung erstellt haben, zu trennen, d.h. die, die ein solches Prepared Statement geschaffen haben.
Andres Freund sagte, dass, wenn ein Client zu Ihnen kommt, der bereits in einer anderen Serververbindung ein solches Prepared Statement erstellt hat, Sie es für ihn erstellen sollten. Aber es scheint etwas problematisch zu sein, Anfragen an die Datenbank im Namen des Clients auszuführen; jedoch wäre es aus der Sicht eines Entwicklers, der ein Protokoll für die Interaktion mit der Datenbank schreibt, praktisch, wenn ihm einfach eine Netzwerkverbindung zur Verfügung gestellt würde, in der eine solche vorbereitete Anfrage vorhanden ist.

Und eine weitere Funktion, die wir implementieren müssen. Wir haben jetzt ein Monitoring, das mit PgBouncer kompatibel ist. Wir können die durchschnittliche Ausführungszeit einer Anfrage zurückgeben. Doch die durchschnittliche Zeit ist wie die durchschnittliche Temperatur im Krankenhaus: jemand ist kalt, jemand ist warm – durchschnittlich sind alle gesund. Das ist nicht wahr.
Wir müssen die Unterstützung für Perzentile implementieren, die anzeigen, dass es langsame Anfragen gibt, die Ressourcen verbrauchen, und das Monitoring akzeptabler machen würden.

Das Wichtigste ist, dass wir Version 1.0 wollen (Version 1.1 ist bereits veröffentlicht). Das Problem ist, dass Odyssey sich jetzt in der Version 1.0rc befindet, d.h. als Release Candidate. Und all die Stolpersteine, die ich aufgezählt habe, wurden genau mit dieser Version behebt, abgesehen von dem Memory Leak.
Was bedeutet Version 1.0 für uns? Wir bringen Odyssey in unsere Datenbanken. Es funktioniert bereits jetzt in unseren Datenbanken, aber wenn es die Marke von 1.000.000 Anfragen pro Sekunde erreicht, können wir sagen, dass dies die Release-Version ist und dass dies die Version ist, die man 1.0 nennen kann.
In der Community haben mehrere Personen darum gebeten, dass in Version 1.0 auch eine Pause und SCRAM enthalten sind. Aber das würde bedeuten, dass wir die nächste Version bereits in die Produktion bringen müssten, da weder SCRAM noch die Pause bisher zusammengeführt wurden. Aber höchstwahrscheinlich wird dieses Thema relativ schnell gelöst werden.

Ich warte auf eure Pull Requests. Und ich würde auch gerne hören, welche Probleme ihr mit Bouncer habt. Lasst uns darüber diskutieren. Vielleicht können wir einige Funktionen umsetzen, die ihr benötigt.
An dieser Stelle ist mein Teil beendet, ich würde gerne euch zuhören. Vielen Dank!
Fragen
Wenn ich mein application_name festlege, wird es korrekt weitergegeben, auch im Transaction Pooling in Odyssey?
In Odyssey oder in Bouncer?
In Odyssey. In Bouncer wird es weitergegeben.
Wir werden ein Set machen.
Und wenn meine echte Verbindung springt und sich mit anderen Verbindungen verbindet, wird sie dann weitergegeben?
Wir werden ein Set aller Parameter machen, die in der Liste aufgeführt sind. Ich kann nicht sagen, ob application_name in dieser Liste enthalten ist. Ich habe es dort zu sehen geglaubt. Wir werden alle gleichen Parameter festlegen. Mit einem einzigen Request wird dort alles gesetzt, was beim Start vom Client festgelegt wurde.
Danke, Andrey, für den Bericht! Guter Bericht! Ich freue mich, dass sich Odyssey mit jeder Minute schneller entwickelt. Ich wünsche mir, dass es so weitergeht. Wir haben uns bereits an euch gewandt mit der Bitte um eine Multi-Datenquelle-Verbindung, damit Odyssey gleichzeitig mit verschiedenen Datenbanken verbunden werden kann, also Master-Slave, und dann automatisch nach einem Failover mit dem neuen Master verbunden wird.
Ja, ich erinnere mich, dass ich diese Diskussion hatte. Derzeit gibt es mehrere Storages. Aber es gibt keinen Wechsel zwischen ihnen. Wir müssen auf unserer Seite den Server abfragen, ob er noch lebt und verstehen, dass ein Failover stattgefunden hat, wer pg_recovery aufruft. Ich habe eine Standardmethode, um zu verstehen, dass wir nicht auf dem Master sind. Und wir müssen es aus Fehlern heraus irgendwie erkennen, oder? Das heißt, die Idee ist interessant, sie wird diskutiert. Schreibt mehr Kommentare. Wenn ihr Arbeitskräfte habt, die C kennen, wäre das großartig.
Die Frage der Skalierung von Replikaten interessiert uns ebenfalls, da wir die Einführung replizierter Cluster für Entwickler so einfach wie möglich gestalten möchten. Hier wären jedoch zusätzliche Kommentare hilfreich, d. h. wie genau man es umsetzen kann und wie man es optimal macht.
Die Frage betrifft ebenfalls Replikate. Es ist so, 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 Unterschiede in den Daten so groß sein können, dass sie Ihr Geschäft nicht zufriedenstellen und Sie nicht darauf zugreifen werden, bis sie synchronisiert sind. Wenn Sie lange nicht darauf zugegriffen haben und dann wieder beginnen, werden die benötigten Daten sofort nicht verfügbar sein. Das heißt, wenn wir ständig auf den Master zugreifen, ist der Cache dort
Ja, das stimmt. Im pcache werden keine Datensätze vorhanden sein, die Sie möchten, im real cache werden keine Informationen zu den Tabellen vorhanden sein, die Sie benötigen, und in den Plänen werden keine analysierten Abfragen vorhanden sein, überhaupt nichts wird vorhanden sein.
Und wenn Sie einen Cluster haben und dort ein neues Replikat hinzufügen, ist es während des Starts nicht gut, d. h. es baut seinen Cache langsam auf.
Ich habe die Idee verstanden. Der richtige Ansatz wäre, zunächst einen kleinen Prozentsatz der Anfragen auf das Replikat zu starten, um den Cache aufzuwärmen. Vereinfacht gesagt, haben wir die Bedingung, dass wir nicht mehr als 10 Sekunden hinter dem Master zurückbleiben dürfen. Und diese Bedingung sollten wir nicht auf einmal, sondern schrittweise für einige Kunden aktivieren.
Ja, das Gewicht erhöhen.
Das ist eine gute Idee. Aber zuerst müssen wir diese Abschaltung implementieren. Zuerst müssen wir uns abschalten, dann überlegen wir, wie wir uns wieder einschalten können. Das ist eine großartige Funktion, um schrittweise wieder aktiv zu werden.
In Nginx gibt es eine solche Option slowly start im Cluster für den Server. Und es erhöht allmählich die Last.
Ja, großartige Idee, wir werden es ausprobieren, wenn wir dazu kommen.
Quelle: habr.com
