Po dlouhou dobu byla jedním z nejznámějších nedostatků PostgreSQL schopnost paralelizovat dotazy. S vydáním verze 9.6 to již nebude problém. Na tomto tématu byla odvedena skvělá práce, počínaje odevzdáním 80558c1, zavedením paralelního sekvenčního skenování, které uvidíme v průběhu tohoto článku.
Nejprve musíte vzít na vědomí:vývoj této funkce byl nepřetržitý a některé parametry změnily názvy mezi odevzdáním a jiným. Tento článek byl napsán pomocí pokladny provedené 17. června a některé zde znázorněné funkce budou k dispozici pouze ve verzi 9.6 beta2.
Oproti verzi 9.5 byly do konfiguračního souboru zavedeny nové parametry. Jsou to:
- max_parallel_workers_per_gather :počet pracovníků, kteří mohou pomoci sekvenčnímu skenování stolu;
- min_parallel_relation_size :minimální velikost, kterou musí mít vztah, aby plánovač zvážil použití dalších pracovníků;
- parallel_setup_cost :parametr plánovače, který odhaduje náklady na vytvoření instance pracovníka;
- parallel_tuple_cost :parametr plánovače, který odhaduje náklady na převod n-tice od jednoho pracovníka k druhému;
- force_parallel_mode :parametr užitečný pro testování, silný paralelismus a také dotaz, ve kterém by plánovač fungoval jinak.
Podívejme se, jak lze využít další pracovníky k urychlení našich dotazů. Vytvoříme testovací tabulku s polem INT a sto miliony záznamů:
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 má max_parallel_workers_per_gather
standardně nastaveno na 2, pro které budou během sekvenčního skenování aktivováni dva pracovníci.
Jednoduché sekvenční skenování nepředstavuje žádné novinky:
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)
Ve skutečnosti přítomnost WHERE
pro paralelizaci je vyžadována klauzule:
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)
Můžeme se vrátit k předchozí akci a pozorovat rozdíly v nastavení max_parallel_workers_per_gather
na 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)
Čas 2,5krát delší.
Plánovač ne vždy považuje paralelní sekvenční skenování za nejlepší možnost. Pokud dotaz není dostatečně selektivní a existuje mnoho n-tic k přenosu z pracovníka na pracovníka, může preferovat „klasické“ sekvenční skenování:
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)
Ve skutečnosti, pokud se pokusíme vynutit paralelní sekvenční skenování, dostaneme horší výsledek:
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)
Počet pracovníků lze zvýšit až na max_worker_processes
(výchozí:8). Obnovujeme hodnotu parallel_tuple_cost
a uvidíme, co se stane zvýšením max_parallel_workers_per_gather
do 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)
Přestože PostgreSQL mohl používat až 8 pracovníků, vytvořil instanci pouze šesti. Postgres totiž také optimalizuje počet pracovníků podle velikosti stolu a min_parallel_relation_size
. Počet pracovníků zpřístupněných postgresem je založen na geometrickém postupu se 3 jako společný poměr 3 a min_parallel_relation_size
jako faktor měřítka. Zde je příklad. S ohledem na výchozí parametr 8 MB:
Velikost | Pracovník |
---|---|
<8 MB | 0 |
<24 MB | 1 |
<72 MB | 2 |
<216 MB | 3 |
<648 MB | 4 |
<1944 MB | 5 |
<5822 MB | 6 |
… | … |
Velikost naší tabulky je 3458 MB, takže 6 je maximální počet dostupných pracovníků.
postgres=# \dt+ test
List of relations
Schema | Name | Type | Owner | Size | Description
--------+------+-------+----------+---------+-------------
public | test | table | postgres | 3458 MB |
(1 row)
Nakonec uvedu krátkou ukázku vylepšení dosažených prostřednictvím tohoto patche. Spuštěním našeho dotazu s rostoucím počtem rostoucích pracovníků získáme následující výsledky:
Zaměstnanci | Čas |
---|---|
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 |
Vidíme, že časy se dramaticky zlepšují, dokud nedosáhnete třetiny původní hodnoty. Je také jednoduché vysvětlit skutečnost, že nevidíme zlepšení mezi použitím 3 a 6 pracovníků:stroj, na kterém byl test spuštěn, má 4 CPU, takže výsledky jsou stabilní po přidání dalších 3 pracovníků k původnímu procesu .
Konečně, PostgreSQL 9.6 připravil půdu pro paralelizaci dotazů, ve které je paralelní sekvenční skenování pouze prvním skvělým výsledkem. Uvidíme také, že v 9.6 byly agregace paralelizovány, ale to je informace pro další článek, který vyjde v nadcházejících týdnech!