Under lång tid var en av de mest kända bristerna med PostgreSQL förmågan att parallellisera frågor. Med releasen av version 9.6 kommer detta inte längre att vara ett problem. Ett bra jobb har gjorts i detta ämne, med början från commit 80558c1, införandet av parallell sekventiell skanning, som vi kommer att se under den här artikeln.
Först måste du notera:utvecklingen av den här funktionen har varit kontinuerlig och vissa parametrar har bytt namn mellan en commit och en annan. Den här artikeln har skrivits med hjälp av en kassa som togs den 17 juni och vissa funktioner som visas här kommer endast att finnas i version 9.6 beta2.
Jämfört med version 9.5 har nya parametrar införts i konfigurationsfilen. Dessa är:
- max_parallel_workers_per_gather :antalet arbetare som kan hjälpa till med en sekventiell genomsökning av en tabell;
- min_parallel_relation_size :den minsta storlek som en relation måste ha för att planeraren ska överväga användningen av ytterligare arbetare;
- parallell_setup_cost :planeringsparametern som uppskattar kostnaden för att instansiera en arbetare;
- parallel_tuple_cost :planeringsparametern som uppskattar kostnaden för att överföra en tupel från en arbetare till en annan;
- force_parallel_mode :parameter användbar för testning, stark parallellitet och även en fråga där planeraren skulle fungera på andra sätt.
Låt oss se hur de ytterligare arbetarna kan användas för att snabba på våra frågor. Vi skapar en testtabell med ett INT-fält och hundra miljoner poster:
postgres=# CREATE TABLE test (i int);
CREATE TABLE
postgres=# INSERT INTO test SELECT generate_series(1,100000000);
INSERT 0 100000000
postgres=# ANALYSE test;
ANALYZE
PostgreSQL har max_parallel_workers_per_gather
inställd på 2 som standard, för vilka två arbetare kommer att aktiveras under en sekventiell skanning.
En enkel sekventiell skanning presenterar inga nyheter:
postgres=# EXPLAIN ANALYSE SELECT * FROM test;
QUERY PLAN
------------------------------------------------------------------------------------------------------------------------
Seq Scan on test (cost=0.00..1442478.32 rows=100000032 width=4) (actual time=0.081..21051.918 rows=100000000 loops=1)
Planning time: 0.077 ms
Execution time: 28055.993 ms
(3 rows)
Faktum är att förekomsten av en WHERE
klausul krävs för parallellisering:
postgres=# EXPLAIN ANALYZE SELECT * FROM test WHERE i=1;
QUERY PLAN
------------------------------------------------------------------------------------------------------------------------
Gather (cost=1000.00..964311.60 rows=1 width=4) (actual time=3.381..9799.942 rows=1 loops=1)
Workers Planned: 2
Workers Launched: 2
-> Parallel Seq Scan on test (cost=0.00..963311.50 rows=0 width=4) (actual time=6525.595..9791.066 rows=0 loops=3)
Filter: (i = 1)
Rows Removed by Filter: 33333333
Planning time: 0.130 ms
Execution time: 9804.484 ms
(8 rows)
Vi kan gå tillbaka till föregående åtgärd och observera skillnadsinställningen max_parallel_workers_per_gather
till 0:
postgres=# SET max_parallel_workers_per_gather TO 0;
SET
postgres=# EXPLAIN ANALYZE SELECT * FROM test WHERE i=1;
QUERY PLAN
--------------------------------------------------------------------------------------------------------
Seq Scan on test (cost=0.00..1692478.40 rows=1 width=4) (actual time=0.123..25003.221 rows=1 loops=1)
Filter: (i = 1)
Rows Removed by Filter: 99999999
Planning time: 0.105 ms
Execution time: 25003.263 ms
(5 rows)
En tid 2,5 gånger längre.
Planeraren anser inte alltid att en parallell sekventiell skanning är det bästa alternativet. Om en fråga inte är tillräckligt selektiv och det finns många tuplar att överföra från arbetare till arbetare, kanske den föredrar en "klassisk" sekventiell skanning:
postgres=# SET max_parallel_workers_per_gather TO 2;
SET
postgres=# EXPLAIN ANALYZE SELECT * FROM test WHERE i<90000000;
QUERY PLAN
----------------------------------------------------------------------------------------------------------------------
Seq Scan on test (cost=0.00..1692478.40 rows=90116088 width=4) (actual time=0.073..31410.276 rows=89999999 loops=1)
Filter: (i < 90000000)
Rows Removed by Filter: 10000001
Planning time: 0.133 ms
Execution time: 37939.401 ms
(5 rows)
Faktum är att om vi försöker tvinga fram en parallell sekventiell skanning får vi ett sämre resultat:
postgres=# SET parallel_tuple_cost TO 0;
SET
postgres=# EXPLAIN ANALYZE SELECT * FROM test WHERE i<90000000;
QUERY PLAN
-------------------------------------------------------------------------------------------------------------------------------------
Gather (cost=1000.00..964311.50 rows=90116088 width=4) (actual time=0.454..75546.078 rows=89999999 loops=1)
Workers Planned: 2
Workers Launched: 2
-> Parallel Seq Scan on test (cost=0.00..1338795.20 rows=37548370 width=4) (actual time=0.088..20294.670 rows=30000000 loops=3)
Filter: (i < 90000000)
Rows Removed by Filter: 3333334
Planning time: 0.128 ms
Execution time: 83423.577 ms
(8 rows)
Antalet arbetare kan ökas upp till max_worker_processes
(standard:8). Vi återställer värdet för parallel_tuple_cost
och vi ser vad som händer genom att öka max_parallel_workers_per_gather
till 8.
postgres=# SET parallel_tuple_cost TO DEFAULT ;
SET
postgres=# SET max_parallel_workers_per_gather TO 8;
SET
postgres=# EXPLAIN ANALYZE SELECT * FROM test WHERE i=1;
QUERY PLAN
------------------------------------------------------------------------------------------------------------------------
Gather (cost=1000.00..651811.50 rows=1 width=4) (actual time=3.684..8248.307 rows=1 loops=1)
Workers Planned: 6
Workers Launched: 6
-> Parallel Seq Scan on test (cost=0.00..650811.40 rows=0 width=4) (actual time=7053.761..8231.174 rows=0 loops=7)
Filter: (i = 1)
Rows Removed by Filter: 14285714
Planning time: 0.124 ms
Execution time: 8250.461 ms
(8 rows)
Även om PostgreSQL kunde använda upp till 8 arbetare, har det bara instansierats sex. Detta beror på att Postgres också optimerar antalet arbetare enligt tabellens storlek och min_parallel_relation_size
. Antalet arbetare som görs tillgängliga av postgres baseras på en geometrisk progression med 3 som gemensamt förhållande 3 och min_parallel_relation_size
som skalfaktor. Här är ett exempel. Med tanke på 8 MB standardparameter:
Storlek | Arbetare |
---|---|
<8MB | 0 |
<24MB | 1 |
<72MB | 2 |
<216MB | 3 |
<648MB | 4 |
<1944MB | 5 |
<5822MB | 6 |
... | ... |
Vår bordsstorlek är 3458 MB, så 6 är det maximala antalet tillgängliga arbetare.
postgres=# \dt+ test
List of relations
Schema | Name | Type | Owner | Size | Description
--------+------+-------+----------+---------+-------------
public | test | table | postgres | 3458 MB |
(1 row)
Slutligen kommer jag att ge en kort demonstration av de förbättringar som uppnåtts genom denna patch. När vi kör vår fråga med ett växande antal växande arbetare får vi följande resultat:
Arbetare | Tid |
---|---|
0 | 24767.848 ms |
1 | 14855.961 ms |
2 | 10415.661 ms |
3 | 8041.187 ms |
4 | 8090.855 ms |
5 | 8082.937 ms |
6 | 8061.939 ms |
Vi kan se att tiderna förbättras dramatiskt, tills du når en tredjedel av initialvärdet. Det är också enkelt att förklara det faktum att vi inte ser förbättringar mellan användningen av 3 och 6 arbetare:maskinen som testet kördes på har 4 processorer, så resultaten är stabila efter att ha lagt till ytterligare 3 arbetare till den ursprungliga processen .
Slutligen har PostgreSQL 9.6 satt scenen för frågeparallellisering, där parallell sekventiell skanning bara är det första bra resultatet. Vi kommer också att se att i 9.6 har aggregationer parallelliserats, men det är information för en annan artikel som kommer att släppas under de kommande veckorna!