Lesen Sie [c] parallele PostgreSQL-Pläne richtig

In der Vergangenheit sah das PostgreSQL-Servermodell wie eine Reihe unabhängiger Prozesse mit teilweise gemeinsam genutztem Speicher aus. Jeder von ihnen bedient jeweils nur eine Clientverbindung und eine Anfrage - und kein Multithreading.



Daher gibt es in jedem einzelnen Prozess keine traditionellen "seltsamen" Probleme mit paralleler Codeausführung, Sperren, Rennbedingungen usw. Und die Entwicklung des DBMS selbst ist angenehm und einfach.



Die gleiche Einfachheit führt jedoch zu einer erheblichen Einschränkung. Da der Prozess nur einen Worker-Thread enthält, kann er nicht mehr als einen CPU-Kern zum Ausführen einer Anforderung verwenden . Dies bedeutet, dass die Geschwindigkeit des Servers direkt von der Häufigkeit und Architektur eines separaten Kerns abhängt.



In unserer Zeit des beendeten "Megahertz-Rennens" und der siegreichen Multicore- und Multiprozessorsysteme ist ein solches Verhalten ein inakzeptabler Luxus und eine Verschwendung. Ab PostgreSQL 9.6 können daher bei der Verarbeitung einer Abfrage einige Vorgänge von mehreren Prozessen gleichzeitig ausgeführt werden.





Die Funktionsweise einiger paralleler Knoten können Sie im Artikel "Parallelität in PostgreSQL" von Ibrar Ahmed kennenlernen, aus dem dieses Bild stammt.
In diesem Fall wird es jedoch nicht trivial, die Pläne zu lesen.



Kurz gesagt sieht die Chronologie der Implementierung der parallelen Ausführung von Planoperationen folgendermaßen aus:





Wenn Sie eine der neuesten PostgreSQL-Versionen verwenden, ist die Wahrscheinlichkeit Parallel ...sehr hoch, dass sie im Plan angezeigt wird. Und mit ihm kommen sie und ...



Seltsamkeiten im Laufe der Zeit



Werfen wir einen Plan von PostgreSQL 9.6 :





[Blick auf explain.tensor.ru]



Nur einer wurde Parallel Seq Scanausgeführt 153,621 ms innerhalb eines Unterbaums, und Gatherzusammen mit allen untergeordneten Knoten - nur 104,867 ms.



Wieso das? Ist die Gesamtzeit "oben" kürzer geworden? ..



Schauen wir uns den GatherKnoten genauer an:



Gather (actual time=0.969..104.867 rows=333333 loops=1)
  Workers Planned: 2
  Workers Launched: 2
  Buffers: shared hit=4425


Workers Launched: 2sagt uns, dass zusätzlich zu dem Hauptprozess unter dem Baum 2 weitere beteiligt waren - insgesamt 3. Daher ist alles, was innerhalb des GatherTeilbaums passiert ist, die Gesamtkreativität aller 3 Prozesse gleichzeitig.



Nun wollen wir sehen, was da drin ist Parallel Seq Scan:



Parallel Seq Scan on tst (actual time=0.024..51.207 rows=111111 loops=3)
  Filter: ((i % 3) = 0)
  Rows Removed by Filter: 222222
  Buffers: shared hit=4425


Aha! loops=3Ist eine Zusammenfassung aller 3 Prozesse. Und im Durchschnitt dauerte jeder dieser Zyklen 51,207 ms. Das heißt, der Server hat 51.207 x 3 = 153.621Millisekunden Prozessorzeit benötigt , um diesen Knoten fertigzustellen . Das heißt, wenn wir verstehen wollen, "was der Server getan hat", hilft uns diese Nummer zu verstehen.

Beachten Sie, dass Sie zum Verständnis der "tatsächlichen" Ausführungszeit die Gesamtzeit durch die Anzahl der Mitarbeiter dividieren müssen [actual time] x [loops] / [Workers Launched].


In unserem Beispiel hat jeder Worker daher nur einen Zyklus durch den Knoten ausgeführt 153.621 / 3 = 51.207. Und ja, jetzt ist es nichts Seltsames, dass der einzige Gatherim Kopfprozess "sozusagen in kürzerer Zeit" abgeschlossen wurde.



Gesamt: Sehen Sie sich EXPLAIN.tensor.ru die Gesamtknotenzeit (für alle Prozesse) an, um zu verstehen, mit welcher Art von Last Ihr Server beschäftigt war, und um zu optimieren, welcher Teil der Abfrage Zeit wert ist.



In diesem Sinne erscheint das Verhalten derselben EXPLAIN.DEPESZ.com , das die "reale durchschnittliche" Zeit auf einmal anzeigt , für Debugging-Zwecke weniger nützlich: Stimmen







Sie nicht zu? Willkommen zu den Kommentaren!



Gather Merge verliert alles



Lassen Sie uns nun dieselbe Abfrage für PostgreSQL 10- Versionen ausführen :





[siehe EXPLAIN.tensor.ru]



Beachten Sie, dass wir Gatherjetzt einen Knoten anstelle eines Knotens im Plan haben Gather Merge. Das Handbuch dazu sagt Folgendes :

Wenn sich ein Knoten über dem parallelen Teil des Plans befindet Gather Merge, Gatherbedeutet dies, dass alle Prozesse, die die Teile des parallelen Plans ausführen, Tupel in sortierter Reihenfolge ausgeben und dass der führende Prozess eine auftragserhaltende Zusammenführung durchführt. Der Knoten Gatherhingegen empfängt Tupel von untergeordneten Prozessen in einer für ihn geeigneten willkürlichen Reihenfolge, wodurch die möglicherweise vorhandene Sortierreihenfolge verletzt wird.


Aber im dänischen Königreich ist nicht alles in Ordnung:



Limit (actual time=110.740..113.138 rows=10000 loops=1)
  Buffers: shared hit=888 read=801, temp read=18 written=218
  I/O Timings: read=9.709
  ->  Gather Merge (actual time=110.739..117.654 rows=10000 loops=1)
        Workers Planned: 2
        Workers Launched: 2
        Buffers: shared hit=2943 read=1578, temp read=24 written=571
        I/O Timings: read=17.156


Beim Übergeben von Attributen Buffersund I/O Timingsim Baum gingen einige Daten vorzeitig verloren . Wir können die Größe dieses Verlusts auf nur etwa 2/3 schätzen , die durch Hilfsprozesse gebildet werden.



Leider gibt es im Plan selbst keinen Ort, an dem diese Informationen abgerufen werden können - daher die "Minuspunkte" auf dem darüber liegenden Knoten. Wenn Sie sich die weitere Entwicklung dieses Plans in PostgreSQL 12 ansehen , ändert sich dies nicht grundlegend, außer dass für jeden Worker auf dem Sort-node einige Statistiken hinzugefügt werden :



Limit (actual time=77.063..80.480 rows=10000 loops=1)
  Buffers: shared hit=1764, temp read=223 written=355
  ->  Gather Merge (actual time=77.060..81.892 rows=10000 loops=1)
        Workers Planned: 2
        Workers Launched: 2
        Buffers: shared hit=4519, temp read=575 written=856
        ->  Sort (actual time=72.630..73.252 rows=4278 loops=3)
              Sort Key: i
              Sort Method: external merge  Disk: 1832kB
              Worker 0:  Sort Method: external merge  Disk: 1512kB
              Worker 1:  Sort Method: external merge  Disk: 1248kB
              Buffers: shared hit=4519, temp read=575 written=856
              ->  Parallel Seq Scan on tst (actual time=0.014..44.970 rows=111111 loops=3)
                    Filter: ((i % 3) = 0)
                    Rows Removed by Filter: 222222
                    Buffers: shared hit=4425
Planning Time: 0.142 ms
Execution Time: 83.884 ms


Insgesamt: Vertraue den obigen Knotendaten nicht Gather Merge.



All Articles