
Die Skalierung eines DBMS ist eine sich stÀndig weiterentwickelnde Zukunft. DBMS verbessern und skalieren besser auf Hardwareplattformen, wÀhrend die Hardwareplattformen selbst die ProduktivitÀt, die Anzahl der Kerne und den Speicher erhöhen - Achilles holt die Schildkröte ein, hat es aber immer noch nicht. Das Problem der Skalierung von DBMS ist in vollem Gange.
Postgres Professional hatte nicht nur theoretisch, sondern auch praktisch ein Problem mit der Skalierung: bei seinen Kunden. Und mehr als einmal. Einer dieser FĂ€lle wird in diesem Artikel behandelt.
PostgreSQL lÀsst sich auf NUMA-Systemen gut skalieren, wenn es sich um ein einzelnes Motherboard mit mehreren Prozessoren und mehreren Datenbussen handelt. Einige Optimierungen können 
hier und 
hier gelesen 
werden . Es gibt jedoch eine andere Klasse von Systemen, sie haben mehrere Motherboards, deren Datenaustausch ĂŒber Interconnect erfolgt, wĂ€hrend eine Instanz des Betriebssystems daran arbeitet, und fĂŒr den Benutzer sieht dieses Design wie eine einzelne Maschine aus. Und obwohl solche Systeme formal auch NUMA zugeordnet werden können, sind sie im Wesentlichen nĂ€her an Supercomputern als Der Zugriff auf den lokalen Speicher des Knotens und der Zugriff auf den Speicher des benachbarten Knotens unterscheiden sich radikal. Die PostgreSQL-Community ist der Ansicht, dass die einzige Postgres-Instanz, die auf solchen Architekturen ausgefĂŒhrt wird, eine Problemquelle darstellt, und es gibt noch keinen systematischen Lösungsansatz.
Dies liegt daran, dass die Softwarearchitektur, die Shared Memory verwendet, grundsĂ€tzlich darauf ausgelegt ist, dass die Zugriffszeit verschiedener Prozesse auf ihren eigenen und Remote-Speicher mehr oder weniger vergleichbar ist. Wenn wir mit vielen Knoten arbeiten, rechtfertigt sich die Wette auf gemeinsam genutzten Speicher als schnellen Kommunikationskanal nicht mehr, da es aufgrund der Latenz viel âbilligerâ ist, eine Anforderung zum AusfĂŒhren einer bestimmten Aktion an den Knoten (Knoten) zu senden, an dem interessante Daten als diese Daten auf dem Bus senden. Daher sind fĂŒr Supercomputer und im Allgemeinen Systeme mit vielen Knoten Clusterlösungen relevant.
Dies bedeutet nicht, dass die Kombination von Mehrknotensystemen und einer typischen Postgres-Shared-Memory-Architektur beendet werden muss. Wenn Postgres-Prozesse den gröĂten Teil ihrer Zeit mit komplexen Berechnungen vor Ort verbringen, ist diese Architektur sogar sehr effizient. In unserer Situation hatte der Client bereits einen leistungsstarken Multi-Node-Server gekauft, und wir mussten die Probleme von PostgreSQL darauf lösen.
Die Probleme waren jedoch schwerwiegend: Die einfachsten Schreibanforderungen (Ăndern mehrerer Feldwerte in einem Datensatz) wurden in einem Zeitraum von mehreren Minuten bis zu einer Stunde ausgefĂŒhrt. Wie spĂ€ter bestĂ€tigt wurde, zeigten sich diese Probleme gerade aufgrund der groĂen Anzahl von Kernen und dementsprechend der radikalen ParallelitĂ€t bei der AusfĂŒhrung von Anforderungen mit einem relativ langsamen Austausch zwischen Knoten in ihrer ganzen Pracht.
Daher wird sich der Artikel sozusagen fĂŒr zwei Zwecke herausstellen:
- Erfahrungsaustausch: Was tun, wenn in einem System mit mehreren Knoten die Datenbank ernsthaft verlangsamt wird? Wo soll ich anfangen, wie kann ich diagnostizieren, wohin ich mich bewegen soll?
- Beschreiben Sie, wie die Probleme des PostgreSQL-DBMS selbst mit einem hohen Grad an ParallelitĂ€t gelöst werden können. EinschlieĂlich der Auswirkungen der Ănderung des Algorithmus zum Aufheben von Sperren auf die Leistung von PostgreSQL.
Server und DB
Das System bestand aus 8 Klingen mit jeweils 2 Sockeln. Insgesamt mehr als 300 Kerne (ohne Hypertreading). Ein schneller Reifen (proprietĂ€re Herstellertechnologie) verbindet die Klingen. Nicht, dass es sich um einen Supercomputer handelt, aber fĂŒr eine Instanz des DBMS ist die Konfiguration beeindruckend.
Die Last ist auch ziemlich groĂ. Mehr als 1 Terabyte Daten. Ăber 3000 Transaktionen pro Sekunde. Ăber 1000 Verbindungen zu Postgres.
Nachdem wir begonnen hatten, uns mit den stĂŒndlichen Aufnahmeerwartungen zu befassen, war das erste, was wir taten, als Ursache fĂŒr Verzögerungen auf die CD zu schreiben. Sobald unverstĂ€ndliche Verzögerungen einsetzten, wurden Tests ausschlieĂlich mit 
tmpfs . Das Bild hat sich nicht verÀndert. Die Festplatte hat nichts damit zu tun.
Erste Schritte mit Diagnosen: Ansichten
Da die Probleme höchstwahrscheinlich auf die starke Konkurrenz von Prozessen zurĂŒckzufĂŒhren sind, die auf dieselben Objekte âklopfenâ, mĂŒssen zunĂ€chst die Sperren ĂŒberprĂŒft werden. In PostgreSQL gibt es fĂŒr eine solche PrĂŒfung die Ansichten 
pg.catalog.pg_locks und 
pg_stat_activity . Die zweite, bereits in Version 9.6, fĂŒgte Informationen darĂŒber hinzu, worauf der Prozess wartet ( 
Amit Kapila, Ildus Kurbangaliev ) - 
wait_event_type . Mögliche Werte fĂŒr dieses Feld werden 
hier beschrieben.
Aber zuerst zÀhlen:
 postgres= 
Das sind reelle Zahlen. Erreichte bis zu 200.000 Sperren.
Gleichzeitig hingen solche Schlösser an der unglĂŒcklichen Bitte:
 SELECT COUNT(mode), mode FROM pg_locks WHERE pid =580707 GROUP BY mode; count | mode â 
Beim Lesen des Puffers verwendet das DBMS beim Schreiben die 
share - 
exclusive . Das heiĂt, Schreibsperren machten weniger als 1% aller Anforderungen aus.
In der Ansicht 
pg_locks sehen 
pg_locks nicht immer so aus, wie 
in der Benutzerdokumentation beschrieben.
Hier ist die Streichholzplatte:
 AccessShareLock = LockTupleKeyShare RowShareLock = LockTupleShare ExclusiveLock = LockTupleNoKeyExclusive AccessExclusiveLock = LockTupleExclusive 
Die Abfrage SELECT mode FROM pg_locks zeigte, dass CREATE INDEX (ohne CONCURRENTLY) auf 234 INSERTs und 390 INSERTs auf die 
buffer content lock Pufferinhalts warten wĂŒrde. Eine mögliche Lösung besteht darin, INSERTs aus verschiedenen Sitzungen so zu âlehrenâ, dass sie sich in Puffern weniger ĂŒberschneiden.
Es ist Zeit, perf zu verwenden
Das Dienstprogramm 
perf sammelt viele Diagnoseinformationen. Im 
record ... werden Statistiken von Systemereignissen in Dateien geschrieben (standardmĂ€Ăig befinden sie sich in 
./perf_data ), und im 
report werden die gesammelten Daten analysiert. Sie können beispielsweise Ereignisse filtern, die nur 
postgres oder eine bestimmte 
postgres betreffen:
 $ perf record -u postgres  $ perf record -p 76876  ,  $ perf report > ./my_results 
Als Ergebnis werden wir so etwas sehen

Die Verwendung von 
perf zur Diagnose von PostgreSQL wird beispielsweise 
hier sowie im 
pg-Wiki beschrieben .
In unserem Fall gab selbst der einfachste Modus 
perf top wichtige Informationen - 
perf top , was natĂŒrlich im Geiste des 
top Betriebssystems funktioniert. Bei 
perf top wir 
perf top dass der Prozessor die meiste Zeit in den 
PinBuffer() sowie in den Funktionen 
PinBuffer() und 
LWLockAttemptLock(). .
PinBuffer() ist eine Funktion, die den ZĂ€hler fĂŒr Verweise auf den Puffer erhöht (Zuordnung einer Datenseite zum RAM), dank derer Postgres-Prozesse wissen, welche Puffer herausgedrĂŒckt werden können und welche nicht.
LWLockAttemptLock() - Die 
LWLock . 
LWLock ist eine Art Sperre mit zwei Ebenen von 
shared und 
exclusive , ohne 
deadlock zu definieren. Sperren werden dem 
shared memory vorab zugewiesen, wartende Prozesse warten in einer Warteschlange.
Diese Funktionen wurden bereits in PostgreSQL 9.5 und 9.6 ernsthaft optimiert. Die darin enthaltenen Spinlocks wurden durch den direkten Einsatz atomarer Operationen ersetzt.
Flammengraphen
Ohne sie ist es unmöglich: Selbst wenn sie nutzlos wĂ€ren, wĂ€re es immer noch wert, ĂŒber sie zu erzĂ€hlen - sie sind ungewöhnlich schön. Aber sie sind nĂŒtzlich. Hier ist eine Illustration von 
github , nicht von unserem Fall (weder wir noch der Kunde sind noch bereit, Details 
github ).

Diese schönen Bilder zeigen sehr deutlich, was die Prozessorzyklen dauern. 
perf kann Daten erfassen, aber das 
flame graph visualisiert die Daten auf intelligente Weise und erstellt BĂ€ume basierend auf den gesammelten Anrufstapeln. Sie können hier beispielsweise mehr ĂŒber die Profilerstellung mit Flammengraphen lesen und 
hier alles herunterladen, was Sie benötigen.
In unserem Fall war auf den Flammengraphen eine groĂe Menge 
nestloop sichtbar. Anscheinend verursachten die JOINs einer groĂen Anzahl von Tabellen in zahlreichen gleichzeitigen Leseanforderungen eine groĂe Anzahl von 
access share .
Die von 
perf gesammelten Statistiken zeigen, wohin die Prozessorzyklen gehen. Und obwohl wir gesehen haben, dass die meiste Prozessorzeit fĂŒr Sperren vergeht, haben wir nicht gesehen, was genau zu so langen Erwartungen an Sperren fĂŒhrt, da wir nicht genau sehen, wo Sperrenerwartungen auftreten, weil CPU-Zeit wird nicht mit Warten verschwendet.
Um die Erwartungen selbst zu sehen, können Sie eine Anforderung an die Systemansicht 
pg_stat_activity .
 SELECT wait_event_type, wait_event, COUNT(*) FROM pg_stat_activity GROUP BY wait_event_type, wait_event; 
enthĂŒllte, dass:
 LWLockTranche | buffer_content | UPDATE ************* LWLockTranche | buffer_content | INSERT INTO ******** LWLockTranche | buffer_content | \r | | insert into B4_MUTEX | | values (nextval('hib | | returning ID Lock | relation | INSERT INTO B4_***** LWLockTranche | buffer_content | UPDATE ************* Lock | relation | INSERT INTO ******** LWLockTranche | buffer_mapping | INSERT INTO ******** LWLockTranche | buffer_content | \r 
(Die Sternchen hier ersetzen einfach Anforderungsdetails, die wir nicht offenlegen.)
Sie können die Werte 
buffer_content (Blockieren des Inhalts von Puffern) und 
buffer_mapping (Blockieren der Komponenten der Hash-Platte 
shared_buffers ) 
shared_buffers .
FĂŒr Hilfe zu gdb
Aber warum gibt es so viele Erwartungen an diese Art von Schlössern? FĂŒr detailliertere Informationen zu den Erwartungen musste ich den 
GDB Debugger verwenden. Mit 
GDB wir einen Aufrufstapel spezifischer Prozesse erhalten. Durch Anwenden von Abtastung, d.h. Nachdem Sie eine bestimmte Anzahl von zufÀlligen Anrufstapeln gesammelt haben, können Sie sich ein Bild davon machen, welche Stapel die lÀngsten Erwartungen haben.
Betrachten Sie den Prozess der Erstellung von Statistiken. Wir werden die "manuelle" Sammlung von Statistiken betrachten, obwohl im wirklichen Leben spezielle Skripte verwendet werden, die dies automatisch tun.
ZunÀchst muss 
gdb an den PostgreSQL-Prozess angehÀngt werden. Suchen Sie dazu die 
pid Serverprozesses, z. B. von
 $ ps aux | grep postgres 
Nehmen wir an, wir haben gefunden:
 postgres 2025 0.0 0.1 172428 1240 pts/17  S   23  0:00 /usr/local/pgsql/bin/postgres -D /usr/local/pgsql/data 
und fĂŒgen Sie nun die 
pid in den Debugger ein:
 igor_le:~$gdb -p 2025 
Sobald wir uns im Debugger befinden, schreiben wir 
bt [ 
bt backtrace ] oder 
where . Und wir bekommen viele Informationen ĂŒber diesen Typ:
 (gdb) bt #0 0x00007fbb65d01cd0 in __write_nocancel () from /lib64/libc.so.6 #1 0x00000000007c92f4 in write_pipe_chunks ( data=0x110e6e8 "2018â06â01 15:35:38 MSK [524647]: [392â1] db=bp,user=bp,app=[unknown],client=192.168.70.163 (http://192.168.70.163) LOG: relation 23554 new block 493: 248.389503\n2018â06â01 15:35:38 MSK [524647]: [393â1] db=bp,user=bp,app=["..., len=409, dest=dest@entry=1) at elog.c:3123 #2 0x00000000007cc07b in send_message_to_server_log (edata=0xc6ee60 <errordata>) at elog.c:3024 #3 EmitErrorReport () at elog.c:1479 
Nachdem wir Statistiken gesammelt hatten, einschlieĂlich Aufrufstapel aus allen Postgres-Prozessen, die wiederholt zu verschiedenen Zeitpunkten gesammelt wurden, stellten wir fest, dass die 
buffer partition lock der 
buffer partition lock innerhalb der 
buffer partition lock der 
relation extension lock 3706 Sekunden (ungefÀhr eine Stunde) dauerte, dh ein Teil der Hash-Tabelle des Puffers 
relation extension lock Manager, der erforderlich war, um den alten Puffer zu ersetzen, um ihn anschlieĂend durch einen neuen zu ersetzen, der dem erweiterten Teil der Tabelle entspricht. Es war auch eine bestimmte Anzahl von Sperren fĂŒr Pufferinhalte erkennbar, die der Erwartung entsprachen, die Seiten des 
B-tree Index zum EinfĂŒgen zu sperren.

ZunĂ€chst kamen zwei ErklĂ€rungen fĂŒr eine solch ungeheure Wartezeit:
- Jemand anderes nahm dieses LWLockund steckte fest. Dies ist jedoch unwahrscheinlich. Weil innerhalb der Pufferpartitionssperre nichts Kompliziertes passiert.
- Wir haben ein pathologisches Verhalten von LWLock. Das heiĂt, trotz der Tatsache, dass niemand das Schloss zu lange nahm, hielt seine Erwartung unangemessen lange an.
Diagnosepflaster und Baumbehandlung
Durch die Reduzierung der Anzahl gleichzeitiger Verbindungen wĂŒrden wir wahrscheinlich den Strom von Anforderungen an Sperren entladen. Aber das wĂ€re wie Kapitulation. Stattdessen schlug 
Alexander Korotkov , der Chefarchitekt von Postgres Professional (natĂŒrlich half er bei der Vorbereitung dieses Artikels), eine Reihe von Patches vor.
ZunÀchst war es notwendig, sich ein detaillierteres Bild von der Katastrophe zu machen. UnabhÀngig davon, wie gut die fertigen Werkzeuge sind, sind auch Diagnose-Patches aus eigener Herstellung hilfreich.
Es wurde ein Patch geschrieben, der eine detaillierte Protokollierung der in der 
RelationAddExtraBlocks() -Funktion verbrachten Zeit in 
RelationAddExtraBlocks(). hinzufĂŒgt. Wir finden also heraus, wie viel Zeit in 
RelationAddExtraBlocks().Und zur UnterstĂŒtzung von ihm wurde in 
pg_stat_activity weiterer Patch geschrieben, der darĂŒber berichtet, was wir jetzt in 
relation extension auf die 
relation extension tun. Dies wurde folgendermaĂen durchgefĂŒhrt: Wenn die 
relation erweitert wird, wird 
application_name zu 
RelationAddExtraBlocks . Dieser Prozess wird jetzt bequem mit maximalen Details unter Verwendung von 
gdb bt und 
perf analysiert.
TatsÀchlich wurden zwei medizinische (und nicht diagnostische) Patches geschrieben. Der erste Patch hat das Verhalten von 
Bâtree Blattsperren geĂ€ndert: FrĂŒher wurde das Blatt bei der Aufforderung zum EinfĂŒgen als 
share blockiert und danach 
exclusive . Jetzt wird er sofort 
exclusive . Jetzt wurde dieser Patch 
bereits fĂŒr 
PostgreSQL 12 festgeschrieben . GlĂŒcklicherweise erhielt 
Alexander Korotkov in diesem Jahr den 
Status eines Committers - des zweiten PostgreSQL-Committers in Russland und des zweiten im Unternehmen.
Der Wert fĂŒr 
NUM_BUFFER_PARTITIONS wurde ebenfalls von 128 auf 512 erhöht, um die Belastung der Zuordnungssperren zu verringern: Die Puffermanager-Hash-Tabelle wurde in kleinere Teile unterteilt, in der Hoffnung, dass die Belastung fĂŒr jedes bestimmte Teil verringert wird.
Nach dem Anwenden dieses Patches waren die Sperren fĂŒr den Inhalt der Puffer weg, aber trotz der Zunahme von 
buffer_mapping blieb 
NUM_BUFFER_PARTITIONS , 
buffer_mapping wir erinnern Sie daran, Teile der Puffermanager-Hash-Tabelle zu blockieren:
 locks_count | active_session | buffer_content | buffer_mapping ----âââ--âââ+â------âââââââââ+âââ------âââââââ+ââ------âââ 12549 | 1218 | 0 | 15 
Und selbst das ist nicht viel. B - Baum ist kein Engpass mehr. Die 
heap- Erweiterung trat in den Vordergrund.
Behandlung des Gewissens
Als nÀchstes stellte Alexander die folgende Hypothese und Lösung vor:
Wir warten viel Zeit auf die 
buffer parittion lock wenn wir den 
buffer parittion lock . Vielleicht gibt es auf derselben 
buffer parittion lock eine sehr gefragte Seite, zum Beispiel die Wurzel eines 
Bâtree . Zu diesem Zeitpunkt gibt es einen kontinuierlichen Fluss von Anforderungen fĂŒr die 
shared lock von Leseanforderungen.
Die Warteschlange bei 
LWLock "nicht fair". Da 
shared lock so viele wie nötig gleichzeitig verwendet werden können, werden nachfolgende 
shared lock ohne Warteschlange ĂŒbergeben, wenn die 
shared lock bereits aktiviert ist. Wenn der Strom gemeinsam genutzter Sperren so intensiv ist, dass sich keine âFensterâ zwischen ihnen befinden, ist das Warten auf eine 
exclusive lock fast unendlich.
Um dies zu beheben, können Sie versuchen, einen Patch fĂŒr "Gentleman" -Verhalten von Schlössern anzubieten. Es weckt das Gewissen 
shared locker und sie stehen ehrlich in der Warteschlange, wenn es bereits ein 
exclusive lock (interessanterweise haben schwere Schlösser - 
hwlock - keine Probleme mit dem Gewissen: Sie stehen immer ehrlich in der Warteschlange)
 locks_count | active_session | buffer_content | buffer_mapping | reladdextra | inserts>30sec ââââââ-âââââ+ââââââââââââââââ+ââââââââââââââââ+âââââââââââ--â-â+ââââââ-ââââââ+ââââ------ 173985 | 1802 | 0 | 569 | 0 | 0 
Alles ist gut! Es gibt keine langen 
insert . Obwohl die Schlösser an den StĂŒcken der Hash-Platten erhalten blieben. Aber was zu tun ist, das sind die Eigenschaften der Reifen unseres kleinen Supercomputers.
Dieser Patch wurde auch 
der Community angeboten . UnabhÀngig davon, wie sich das Schicksal dieser Patches in der Community entwickelt, hindert nichts sie daran, in die nÀchsten Versionen von 
Postgres Pro Enterprise zu gelangen , die speziell fĂŒr Kunden mit stark ausgelasteten Systemen entwickelt wurden.
Moral
share leichte 
share Locks - 
exclusive Blöcke, die die Warteschlange ĂŒberspringen - haben das Problem der stĂŒndlichen Verzögerungen in einem System mit mehreren Knoten gelöst. Das Hash-Tag des 
buffer manager funktionierte nicht, da zu viel Fluss von 
share lock die Sperren keine Chance hatten, alte Puffer zu ersetzen und neue zu laden. Probleme mit der Erweiterung des Puffers fĂŒr die Datenbanktabellen waren nur eine Folge davon. Zuvor war es möglich, den Engpass mit Zugriff auf die 
B-tree Wurzel zu erweitern.
PostgreSQL wurde nicht fĂŒr NUMA-Architekturen und Supercomputer entwickelt. Die Anpassung an solche Postgres-Architekturen ist eine groĂe Aufgabe, die die koordinierten Anstrengungen vieler Menschen und sogar Unternehmen erfordern wĂŒrde (und möglicherweise erfordern wĂŒrde). Die unangenehmen Folgen dieser architektonischen Probleme können jedoch gemildert werden. Und wir mĂŒssen: Die Lasttypen, die zu Ă€hnlichen Verzögerungen wie den beschriebenen fĂŒhrten, sind recht typisch, Ă€hnliche Notsignale von anderen Orten kommen weiterhin zu uns. Ăhnliche Probleme traten frĂŒher auf - bei Systemen mit weniger Kernen waren nur die Folgen nicht so ungeheuerlich, und die Symptome wurden mit anderen Methoden und anderen Patches behandelt. Jetzt ist eine andere Medizin erschienen - nicht universell, aber eindeutig nĂŒtzlich.
Wenn PostgreSQL also mit dem Speicher des gesamten Systems als lokal arbeitet, kann kein Hochgeschwindigkeitsbus zwischen Knoten mit der Zugriffszeit auf den lokalen Speicher verglichen werden. Aufgaben entstehen aufgrund dieser schwierigen, oft dringenden, aber interessanten. Und die Erfahrung, sie zu lösen, ist nicht nur fĂŒr die Entscheidenden, sondern auch fĂŒr die gesamte Gemeinschaft nĂŒtzlich.
