Parallele Abfragen in PostgreSQL

Parallele Abfragen in PostgreSQL
Moderne CPUs haben viele Kerne. Seit Jahren senden Anwendungen parallel Anfragen an Datenbanken. Wenn es sich um eine Berichtsabfrage über mehrere Zeilen in einer Tabelle handelt, läuft sie bei Verwendung mehrerer CPUs schneller, und PostgreSQL ist seit Version 9.6 dazu in der Lage.

Die Implementierung der parallelen Abfragefunktion dauerte drei Jahre – wir mussten den Code in verschiedenen Phasen der Abfrageausführung neu schreiben. Mit PostgreSQL 3 wurde eine Infrastruktur eingeführt, um den Code weiter zu verbessern. In nachfolgenden Versionen werden andere Arten von Abfragen parallel ausgeführt.

Einschränkungen

  • Aktivieren Sie die parallele Ausführung nicht, wenn alle Kerne bereits ausgelastet sind, da sonst andere Anforderungen langsamer werden.
  • Am wichtigsten ist, dass die parallele Verarbeitung mit hohen WORK_MEM-Werten viel Speicher verbraucht – jeder Hash-Join oder jede Hash-Sortierung belegt Work_Mem-Speicher.
  • OLTP-Abfragen mit geringer Latenz können durch parallele Ausführung nicht beschleunigt werden. Und wenn die Abfrage eine Zeile zurückgibt, wird sie durch die Parallelverarbeitung nur verlangsamt.
  • Entwickler nutzen gerne den TPC-H-Benchmark. Vielleicht haben Sie ähnliche Fragen zur perfekten parallelen Ausführung.
  • Nur SELECT-Abfragen ohne Prädikatsperre werden parallel ausgeführt.
  • Manchmal ist eine ordnungsgemäße Indizierung besser als das sequentielle Scannen von Tabellen im Parallelmodus.
  • Pausierende Abfragen und Cursor werden nicht unterstützt.
  • Fensterfunktionen und Aggregatfunktionen für geordnete Mengen sind nicht parallel.
  • Sie gewinnen nichts an der E/A-Arbeitslast.
  • Es gibt keine parallelen Sortieralgorithmen. Aber Abfragen mit Sortierungen können in manchen Aspekten parallel ausgeführt werden.
  • Ersetzen Sie CTE (WITH ...) durch ein verschachteltes SELECT, um die Parallelverarbeitung zu ermöglichen.
  • Datenwrapper von Drittanbietern unterstützen die Parallelverarbeitung noch nicht (sie könnten es aber!).
  • FULL OUTER JOIN wird nicht unterstützt.
  • max_rows deaktiviert die Parallelverarbeitung.
  • Wenn eine Abfrage eine Funktion hat, die nicht als PARALLEL SAFE gekennzeichnet ist, handelt es sich um eine Single-Threaded-Abfrage.
  • Die Transaktionsisolationsstufe SERIALIZABLE deaktiviert die Parallelverarbeitung.

Test Umgebung

PostgreSQL-Entwickler haben versucht, die Antwortzeit von TPC-H-Benchmark-Abfragen zu verkürzen. Laden Sie den Benchmark herunter und Passen Sie es an PostgreSQL an. Dies ist eine inoffizielle Verwendung des TPC-H-Benchmarks – nicht für Datenbank- oder Hardwarevergleiche.

  1. Laden Sie TPC-H_Tools_v2.17.3.zip (oder eine neuere Version) herunter. von TPC Offsite.
  2. Benennen Sie makefile.suite in Makefile um und ändern Sie es wie hier beschrieben: https://github.com/tvondra/pg_tpch . Kompilieren Sie den Code mit dem Befehl make.
  3. Daten generieren: ./dbgen -s 10 Erstellt eine 23 GB große Datenbank. Dies reicht aus, um den Unterschied in der Leistung paralleler und nicht paralleler Abfragen zu erkennen.
  4. Konvertieren von Dateien tbl в csv с for и sed.
  5. Klonen Sie das Repository pg_tpch und kopieren Sie die Dateien csv в pg_tpch/dss/data.
  6. Erstellen Sie Abfragen mit einem Befehl qgen.
  7. Laden Sie mit dem Befehl Daten in die Datenbank ./tpch.sh.

Paralleles sequentielles Scannen

Es ist möglicherweise nicht wegen des parallelen Lesens schneller, sondern weil die Daten auf viele CPU-Kerne verteilt sind. In modernen Betriebssystemen werden PostgreSQL-Datendateien gut zwischengespeichert. Mit Vorauslesen ist es möglich, einen größeren Block aus dem Speicher abzurufen, als der PG-Daemon anfordert. Daher wird die Abfrageleistung nicht durch Festplatten-E/A eingeschränkt. Es verbraucht CPU-Zyklen, um:

  • Zeilen einzeln von Tabellenseiten lesen;
  • Vergleichen Sie Zeichenfolgenwerte und -bedingungen WHERE.

Lassen Sie uns eine einfache Abfrage ausführen select:

tpch=# explain analyze select l_quantity as sum_qty from lineitem where l_shipdate <= date '1998-12-01' - interval '105' day;
QUERY PLAN
--------------------------------------------------------------------------------------------------------------------------
Seq Scan on lineitem (cost=0.00..1964772.00 rows=58856235 width=5) (actual time=0.014..16951.669 rows=58839715 loops=1)
Filter: (l_shipdate <= '1998-08-18 00:00:00'::timestamp without time zone)
Rows Removed by Filter: 1146337
Planning Time: 0.203 ms
Execution Time: 19035.100 ms

Der sequentielle Scan erzeugt zu viele Zeilen ohne Aggregation, sodass die Abfrage von einem einzelnen CPU-Kern ausgeführt wird.

Wenn Sie hinzufügen SUM()können Sie sehen, dass zwei Workflows dabei helfen, die Abfrage zu beschleunigen:

explain analyze select sum(l_quantity) as sum_qty from lineitem where l_shipdate <= date '1998-12-01' - interval '105' day;
QUERY PLAN
----------------------------------------------------------------------------------------------------------------------------------------------------
Finalize Aggregate (cost=1589702.14..1589702.15 rows=1 width=32) (actual time=8553.365..8553.365 rows=1 loops=1)
-> Gather (cost=1589701.91..1589702.12 rows=2 width=32) (actual time=8553.241..8555.067 rows=3 loops=1)
Workers Planned: 2
Workers Launched: 2
-> Partial Aggregate (cost=1588701.91..1588701.92 rows=1 width=32) (actual time=8547.546..8547.546 rows=1 loops=3)
-> Parallel Seq Scan on lineitem (cost=0.00..1527393.33 rows=24523431 width=5) (actual time=0.038..5998.417 rows=19613238 loops=3)
Filter: (l_shipdate <= '1998-08-18 00:00:00'::timestamp without time zone)
Rows Removed by Filter: 382112
Planning Time: 0.241 ms
Execution Time: 8555.131 ms

Parallele Aggregation

Der Parallel Seq Scan-Knoten erzeugt Zeilen für die teilweise Aggregation. Der Knoten „Partial Aggregate“ schneidet diese Linien mit SUM(). Am Ende wird der SUM-Zähler jedes Arbeitsprozesses vom „Gather“-Knoten gesammelt.

Das Endergebnis wird vom Knoten „Finalize Aggregate“ berechnet. Wenn Sie über eigene Aggregationsfunktionen verfügen, vergessen Sie nicht, diese als „parallel sicher“ zu kennzeichnen.

Anzahl der Worker-Prozesse

Die Anzahl der Worker-Prozesse kann ohne Neustart des Servers erhöht werden:

explain analyze select sum(l_quantity) as sum_qty from lineitem where l_shipdate <= date '1998-12-01' - interval '105' day;
QUERY PLAN
----------------------------------------------------------------------------------------------------------------------------------------------------
Finalize Aggregate (cost=1589702.14..1589702.15 rows=1 width=32) (actual time=8553.365..8553.365 rows=1 loops=1)
-> Gather (cost=1589701.91..1589702.12 rows=2 width=32) (actual time=8553.241..8555.067 rows=3 loops=1)
Workers Planned: 2
Workers Launched: 2
-> Partial Aggregate (cost=1588701.91..1588701.92 rows=1 width=32) (actual time=8547.546..8547.546 rows=1 loops=3)
-> Parallel Seq Scan on lineitem (cost=0.00..1527393.33 rows=24523431 width=5) (actual time=0.038..5998.417 rows=19613238 loops=3)
Filter: (l_shipdate <= '1998-08-18 00:00:00'::timestamp without time zone)
Rows Removed by Filter: 382112
Planning Time: 0.241 ms
Execution Time: 8555.131 ms

Was ist denn hier los? Es gab 2-mal mehr Arbeitsprozesse und die Anfrage wurde nur 1,6599-mal schneller. Die Berechnungen sind interessant. Wir hatten zwei Arbeitsprozesse und einen Leiter. Nach dem Wechsel stand es 2+1.

Unsere maximale Beschleunigung durch Parallelverarbeitung: 5/3 = 1,66(6)-mal.

Wie funktioniert es?

Prozesse

Die Anforderungsausführung beginnt immer mit dem führenden Prozess. Der Leiter erledigt alles Nicht-Parallele und einige Parallelverarbeitungen. Andere Prozesse, die dieselben Anforderungen ausführen, werden als Worker-Prozesse bezeichnet. Die parallele Verarbeitung nutzt Infrastruktur dynamische Hintergrund-Worker-Prozesse (ab Version 9.4). Da andere Teile von PostgreSQL Prozesse anstelle von Threads verwenden, könnte eine Abfrage mit drei Arbeitsprozessen viermal schneller sein als die herkömmliche Verarbeitung.

Interaktion

Arbeitsprozesse kommunizieren mit dem Leiter über eine Nachrichtenwarteschlange (basierend auf gemeinsam genutztem Speicher). Jeder Prozess verfügt über zwei Warteschlangen: für Fehler und für Tupel.

Wie viele Workflows werden benötigt?

Die Mindestgrenze wird durch den Parameter angegeben max_parallel_workers_per_gather. Der Anforderungsläufer entnimmt dann Arbeitsprozesse aus dem durch den Parameter begrenzten Pool max_parallel_workers size. Die letzte Einschränkung ist max_worker_processes, also die Gesamtzahl der Hintergrundprozesse.

Konnte kein Worker-Prozess zugeordnet werden, erfolgt die Verarbeitung als Einzelprozess.

Der Abfrageplaner kann Arbeitsabläufe abhängig von der Größe der Tabelle oder des Index reduzieren. Dafür gibt es Parameter min_parallel_table_scan_size и min_parallel_index_scan_size.

set min_parallel_table_scan_size='8MB'
8MB table => 1 worker
24MB table => 2 workers
72MB table => 3 workers
x => log(x / min_parallel_table_scan_size) / log(3) + 1 worker

Jedes Mal ist der Tisch dreimal größer als min_parallel_(index|table)_scan_size, Postgres fügt einen Arbeitsprozess hinzu. Die Anzahl der Arbeitsabläufe richtet sich nicht nach den Kosten. Zirkuläre Abhängigkeiten erschweren komplexe Implementierungen. Stattdessen verwendet der Planer einfache Regeln.

In der Praxis sind diese Regeln nicht immer für die Produktion geeignet, daher können Sie die Anzahl der Worker-Prozesse für eine bestimmte Tabelle ändern: ALTER TABLE ... SET (parallel_workers = N).

Warum wird keine Parallelverarbeitung verwendet?

Neben der langen Liste der Einschränkungen gibt es auch Kostenprüfungen:

parallel_setup_cost - um eine parallele Bearbeitung kurzer Anfragen zu vermeiden. Dieser Parameter schätzt die Zeit für die Vorbereitung des Speichers, den Start des Prozesses und den ersten Datenaustausch.

parallel_tuple_cost: Die Kommunikation zwischen dem Leiter und den Arbeitern kann sich proportional zur Anzahl der Tupel aus Arbeitsprozessen verzögern. Dieser Parameter berechnet die Kosten des Datenaustauschs.

Verschachtelte Schleifenverknüpfungen

PostgreSQL 9.6+ может выполнять вложенные циклы параллельно — это простая операция.

explain (costs off) select c_custkey, count(o_orderkey)
                from    customer left outer join orders on
                                c_custkey = o_custkey and o_comment not like '%special%deposits%'
                group by c_custkey;
                                      QUERY PLAN
--------------------------------------------------------------------------------------
 Finalize GroupAggregate
   Group Key: customer.c_custkey
   ->  Gather Merge
         Workers Planned: 4
         ->  Partial GroupAggregate
               Group Key: customer.c_custkey
               ->  Nested Loop Left Join
                     ->  Parallel Index Only Scan using customer_pkey on customer
                     ->  Index Scan using idx_orders_custkey on orders
                           Index Cond: (customer.c_custkey = o_custkey)
                           Filter: ((o_comment)::text !~~ '%special%deposits%'::text)

Die Erfassung erfolgt in der letzten Phase, sodass Nested Loop Left Join ein paralleler Vorgang ist. Der parallele Index-Only-Scan wurde erst in Version 10 eingeführt. Er funktioniert ähnlich wie der parallele serielle Scan. Zustand c_custkey = o_custkey liest eine Bestellung pro Client-Zeichenfolge. Es ist also nicht parallel.

Hash-Beitritt

Bis PostgreSQL 11 erstellt jeder Arbeitsprozess seine eigene Hash-Tabelle. Und wenn es mehr als vier dieser Prozesse gibt, verbessert sich die Leistung nicht. In der neuen Version wird die Hash-Tabelle gemeinsam genutzt. Jeder Arbeitsprozess kann WORK_MEM verwenden, um eine Hash-Tabelle zu erstellen.

select
        l_shipmode,
        sum(case
                when o_orderpriority = '1-URGENT'
                        or o_orderpriority = '2-HIGH'
                        then 1
                else 0
        end) as high_line_count,
        sum(case
                when o_orderpriority <> '1-URGENT'
                        and o_orderpriority <> '2-HIGH'
                        then 1
                else 0
        end) as low_line_count
from
        orders,
        lineitem
where
        o_orderkey = l_orderkey
        and l_shipmode in ('MAIL', 'AIR')
        and l_commitdate < l_receiptdate
        and l_shipdate < l_commitdate
        and l_receiptdate >= date '1996-01-01'
        and l_receiptdate < date '1996-01-01' + interval '1' year
group by
        l_shipmode
order by
        l_shipmode
LIMIT 1;
                                                                                                                                    QUERY PLAN
-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
 Limit  (cost=1964755.66..1964961.44 rows=1 width=27) (actual time=7579.592..7922.997 rows=1 loops=1)
   ->  Finalize GroupAggregate  (cost=1964755.66..1966196.11 rows=7 width=27) (actual time=7579.590..7579.591 rows=1 loops=1)
         Group Key: lineitem.l_shipmode
         ->  Gather Merge  (cost=1964755.66..1966195.83 rows=28 width=27) (actual time=7559.593..7922.319 rows=6 loops=1)
               Workers Planned: 4
               Workers Launched: 4
               ->  Partial GroupAggregate  (cost=1963755.61..1965192.44 rows=7 width=27) (actual time=7548.103..7564.592 rows=2 loops=5)
                     Group Key: lineitem.l_shipmode
                     ->  Sort  (cost=1963755.61..1963935.20 rows=71838 width=27) (actual time=7530.280..7539.688 rows=62519 loops=5)
                           Sort Key: lineitem.l_shipmode
                           Sort Method: external merge  Disk: 2304kB
                           Worker 0:  Sort Method: external merge  Disk: 2064kB
                           Worker 1:  Sort Method: external merge  Disk: 2384kB
                           Worker 2:  Sort Method: external merge  Disk: 2264kB
                           Worker 3:  Sort Method: external merge  Disk: 2336kB
                           ->  Parallel Hash Join  (cost=382571.01..1957960.99 rows=71838 width=27) (actual time=7036.917..7499.692 rows=62519 loops=5)
                                 Hash Cond: (lineitem.l_orderkey = orders.o_orderkey)
                                 ->  Parallel Seq Scan on lineitem  (cost=0.00..1552386.40 rows=71838 width=19) (actual time=0.583..4901.063 rows=62519 loops=5)
                                       Filter: ((l_shipmode = ANY ('{MAIL,AIR}'::bpchar[])) AND (l_commitdate < l_receiptdate) AND (l_shipdate < l_commitdate) AND (l_receiptdate >= '1996-01-01'::date) AND (l_receiptdate < '1997-01-01 00:00:00'::timestamp without time zone))
                                       Rows Removed by Filter: 11934691
                                 ->  Parallel Hash  (cost=313722.45..313722.45 rows=3750045 width=20) (actual time=2011.518..2011.518 rows=3000000 loops=5)
                                       Buckets: 65536  Batches: 256  Memory Usage: 3840kB
                                       ->  Parallel Seq Scan on orders  (cost=0.00..313722.45 rows=3750045 width=20) (actual time=0.029..995.948 rows=3000000 loops=5)
 Planning Time: 0.977 ms
 Execution Time: 7923.770 ms

Abfrage 12 von TPC-H zeigt deutlich eine parallele Hash-Verbindung. Jeder Arbeitsprozess trägt zur Erstellung einer gemeinsamen Hash-Tabelle bei.

Zusammenführen

Ein Merge-Join ist von Natur aus nicht parallel. Machen Sie sich keine Sorgen, wenn dies der letzte Schritt der Abfrage ist – er kann weiterhin parallel ausgeführt werden.

-- Query 2 from TPC-H
explain (costs off) select s_acctbal, s_name, n_name, p_partkey, p_mfgr, s_address, s_phone, s_comment
from    part, supplier, partsupp, nation, region
where
        p_partkey = ps_partkey
        and s_suppkey = ps_suppkey
        and p_size = 36
        and p_type like '%BRASS'
        and s_nationkey = n_nationkey
        and n_regionkey = r_regionkey
        and r_name = 'AMERICA'
        and ps_supplycost = (
                select
                        min(ps_supplycost)
                from    partsupp, supplier, nation, region
                where
                        p_partkey = ps_partkey
                        and s_suppkey = ps_suppkey
                        and s_nationkey = n_nationkey
                        and n_regionkey = r_regionkey
                        and r_name = 'AMERICA'
        )
order by s_acctbal desc, n_name, s_name, p_partkey
LIMIT 100;
                                                QUERY PLAN
----------------------------------------------------------------------------------------------------------
 Limit
   ->  Sort
         Sort Key: supplier.s_acctbal DESC, nation.n_name, supplier.s_name, part.p_partkey
         ->  Merge Join
               Merge Cond: (part.p_partkey = partsupp.ps_partkey)
               Join Filter: (partsupp.ps_supplycost = (SubPlan 1))
               ->  Gather Merge
                     Workers Planned: 4
                     ->  Parallel Index Scan using <strong>part_pkey</strong> on part
                           Filter: (((p_type)::text ~~ '%BRASS'::text) AND (p_size = 36))
               ->  Materialize
                     ->  Sort
                           Sort Key: partsupp.ps_partkey
                           ->  Nested Loop
                                 ->  Nested Loop
                                       Join Filter: (nation.n_regionkey = region.r_regionkey)
                                       ->  Seq Scan on region
                                             Filter: (r_name = 'AMERICA'::bpchar)
                                       ->  Hash Join
                                             Hash Cond: (supplier.s_nationkey = nation.n_nationkey)
                                             ->  Seq Scan on supplier
                                             ->  Hash
                                                   ->  Seq Scan on nation
                                 ->  Index Scan using idx_partsupp_suppkey on partsupp
                                       Index Cond: (ps_suppkey = supplier.s_suppkey)
               SubPlan 1
                 ->  Aggregate
                       ->  Nested Loop
                             Join Filter: (nation_1.n_regionkey = region_1.r_regionkey)
                             ->  Seq Scan on region region_1
                                   Filter: (r_name = 'AMERICA'::bpchar)
                             ->  Nested Loop
                                   ->  Nested Loop
                                         ->  Index Scan using idx_partsupp_partkey on partsupp partsupp_1
                                               Index Cond: (part.p_partkey = ps_partkey)
                                         ->  Index Scan using supplier_pkey on supplier supplier_1
                                               Index Cond: (s_suppkey = partsupp_1.ps_suppkey)
                                   ->  Index Scan using nation_pkey on nation nation_1
                                         Index Cond: (n_nationkey = supplier_1.s_nationkey)

Der Knoten „Merge Join“ befindet sich oberhalb des Knotens „Gather Merge“. Beim Zusammenführen kommt also keine Parallelverarbeitung zum Einsatz. Der Knoten „Parallel Index Scan“ hilft jedoch weiterhin bei der Segmentierung part_pkey.

Verbindung nach Abschnitten

In PostgreSQL 11 Verbindung nach Abschnitten Standardmäßig deaktiviert: Die Planung ist sehr aufwändig. Tabellen mit ähnlicher Partitionierung können Partition für Partition zusammengefügt werden. Auf diese Weise verwendet Postgres kleinere Hash-Tabellen. Jede Verbindung von Abschnitten kann parallel sein.

tpch=# set enable_partitionwise_join=t;
tpch=# explain (costs off) select * from prt1 t1, prt2 t2
where t1.a = t2.b and t1.b = 0 and t2.b between 0 and 10000;
                    QUERY PLAN
---------------------------------------------------
 Append
   ->  Hash Join
         Hash Cond: (t2.b = t1.a)
         ->  Seq Scan on prt2_p1 t2
               Filter: ((b >= 0) AND (b <= 10000))
         ->  Hash
               ->  Seq Scan on prt1_p1 t1
                     Filter: (b = 0)
   ->  Hash Join
         Hash Cond: (t2_1.b = t1_1.a)
         ->  Seq Scan on prt2_p2 t2_1
               Filter: ((b >= 0) AND (b <= 10000))
         ->  Hash
               ->  Seq Scan on prt1_p2 t1_1
                     Filter: (b = 0)
tpch=# set parallel_setup_cost = 1;
tpch=# set parallel_tuple_cost = 0.01;
tpch=# explain (costs off) select * from prt1 t1, prt2 t2
where t1.a = t2.b and t1.b = 0 and t2.b between 0 and 10000;
                        QUERY PLAN
-----------------------------------------------------------
 Gather
   Workers Planned: 4
   ->  Parallel Append
         ->  Parallel Hash Join
               Hash Cond: (t2_1.b = t1_1.a)
               ->  Parallel Seq Scan on prt2_p2 t2_1
                     Filter: ((b >= 0) AND (b <= 10000))
               ->  Parallel Hash
                     ->  Parallel Seq Scan on prt1_p2 t1_1
                           Filter: (b = 0)
         ->  Parallel Hash Join
               Hash Cond: (t2.b = t1.a)
               ->  Parallel Seq Scan on prt2_p1 t2
                     Filter: ((b >= 0) AND (b <= 10000))
               ->  Parallel Hash
                     ->  Parallel Seq Scan on prt1_p1 t1
                           Filter: (b = 0)

Hauptsache, die abschnittsweise Verbindung erfolgt nur dann parallel, wenn diese Abschnitte groß genug sind.

Paralleles Anhängen

Paralleles Anhängen können anstelle verschiedener Blöcke in verschiedenen Arbeitsabläufen verwendet werden. Dies geschieht normalerweise bei UNION ALL-Abfragen. Der Nachteil ist die geringere Parallelität, da jeder Arbeitsprozess nur eine Anfrage verarbeitet.

Hier laufen zwei Arbeitsprozesse, obwohl vier aktiviert sind.

tpch=# explain (costs off) select sum(l_quantity) as sum_qty from lineitem where l_shipdate <= date '1998-12-01' - interval '105' day union all select sum(l_quantity) as sum_qty from lineitem where l_shipdate <= date '2000-12-01' - interval '105' day;
                                           QUERY PLAN
------------------------------------------------------------------------------------------------
 Gather
   Workers Planned: 2
   ->  Parallel Append
         ->  Aggregate
               ->  Seq Scan on lineitem
                     Filter: (l_shipdate <= '2000-08-18 00:00:00'::timestamp without time zone)
         ->  Aggregate
               ->  Seq Scan on lineitem lineitem_1
                     Filter: (l_shipdate <= '1998-08-18 00:00:00'::timestamp without time zone)

Die wichtigsten Variablen

  • WORK_MEM begrenzt den Speicher pro Prozess, nicht nur Abfragen: work_mem Prozesse Verbindungen = viel Speicher.
  • max_parallel_workers_per_gather — wie viele Worker-Prozesse das ausführende Programm für die Parallelverarbeitung aus dem Plan verwenden wird.
  • max_worker_processes – Passt die Gesamtzahl der Arbeitsprozesse an die Anzahl der CPU-Kerne auf dem Server an.
  • max_parallel_workers - das Gleiche, aber für parallele Arbeitsprozesse.

Ergebnisse

Ab Version 9.6 kann die Parallelverarbeitung die Leistung komplexer Abfragen, die viele Zeilen oder Indizes scannen, erheblich verbessern. In PostgreSQL 10 ist die Parallelverarbeitung standardmäßig aktiviert. Denken Sie daran, es auf Servern mit einer großen OLTP-Arbeitslast zu deaktivieren. Sequentielle Scans oder Indexscans verbrauchen viele Ressourcen. Wenn Sie keinen Bericht für den gesamten Datensatz ausführen, können Sie die Abfrageleistung verbessern, indem Sie einfach fehlende Indizes hinzufügen oder die richtige Partitionierung verwenden.

Referenzen

Source: habr.com

Kommentar hinzufügen