PostgreSQL
 sql >> база данни >  >> RDS >> PostgreSQL

PostgreSQL 9.6:Паралелно последователно сканиране

Дълго време един от най-известните недостатъци на PostgreSQL беше способността за паралелизиране на заявки. С пускането на версия 9.6 това вече няма да е проблем. Беше свършена страхотна работа по този въпрос, като се започне от комит 80558c1, въвеждането на паралелно последователно сканиране, което ще видим в хода на тази статия.

Първо, трябва да вземете под внимание:развитието на тази функция е непрекъснато и някои параметри са променили имената между комит и друг. Тази статия е написана чрез плащане, направено на 17 юни, и някои функции, илюстрирани тук, ще присъстват само във версия 9.6 beta2.

В сравнение с версията 9.5, в конфигурационния файл са въведени нови параметри. Това са:

  • max_parallel_workers_per_gather :броят на работниците, които могат да помогнат за последователно сканиране на таблица;
  • min_parallel_relation_size :минималният размер, който трябва да има една връзка, за да може планиращият да обмисли използването на допълнителни работници;
  • parallel_setup_cost :параметърът за планиране, който оценява разходите за създаване на екземпляр на работник;
  • parallel_tuple_cost :параметърът за планиране, който оценява разходите за прехвърляне на кортеж от един работник към друг;
  • force_parallel_mode :параметър, полезен за тестване, силен паралелизъм и също така заявка, в която плановникът би работил по други начини.

Нека видим как допълнителните работници могат да бъдат използвани за ускоряване на нашите заявки. Създаваме тестова таблица с поле INT и сто милиона записа:

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 има max_parallel_workers_per_gather зададено на 2 по подразбиране, за което двама работници ще бъдат активирани по време на последователно сканиране.

Обикновеното последователно сканиране не представлява никакви новости:

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)

Всъщност наличието на WHERE за паралелизиране е необходима клауза:

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)

Можем да се върнем към предишното действие и да наблюдаваме настройката на разликите max_parallel_workers_per_gather до 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)

Време 2,5 пъти по-голямо.

Програмистът не винаги смята, че паралелното последователно сканиране е най-добрият вариант. Ако заявката не е достатъчно селективна и има много кортежи за прехвърляне от работник на работник, тя може да предпочете „класическо“ последователно сканиране:

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)

Всъщност, ако се опитаме да принудим паралелно последователно сканиране, получаваме по-лош резултат:

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)

Броят на работниците може да бъде увеличен до max_worker_processes (по подразбиране:8). Възстановяваме стойността на parallel_tuple_cost и виждаме какво се случва чрез увеличаване на max_parallel_workers_per_gather до 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)

Въпреки че PostgreSQL може да използва до 8 работници, той е инстанцирал само шест. Това е така, защото Postgres също оптимизира броя на работниците според размера на таблицата и min_parallel_relation_size . Броят на работниците, предоставени от postgres, се основава на геометрична прогресия с 3 като общо съотношение 3 и min_parallel_relation_size като мащабен фактор. Ето един пример. Като се има предвид 8MB параметър по подразбиране:

Размер Работник
<8MB 0
<24MB 1
<72MB 2
<216MB 3
<648MB 4
<1944MB 5
<5822MB 6

Размерът на нашата таблица е 3458 MB, така че 6 е максималният брой налични работници.

postgres=# \dt+ test
                    List of relations
 Schema | Name | Type  |  Owner   |  Size   | Description
--------+------+-------+----------+---------+-------------
 public | test | table | postgres | 3458 MB |
(1 row)

Накрая ще дам кратка демонстрация на подобренията, постигнати чрез този пластир. Изпълнявайки нашата заявка с нарастващ брой нарастващи работници, получаваме следните резултати:

Работници Време
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

Виждаме, че времената драстично се подобряват, докато достигнете една трета от първоначалната стойност. Също така е лесно да се обясни фактът, че не виждаме подобрения между използването на 3 и 6 работници:машината, на която е извършен тестът, има 4 CPU, така че резултатите са стабилни след добавяне на още 3 работници към първоначалния процес .

И накрая, PostgreSQL 9.6 постави началото на паралелизиране на заявки, при което паралелното последователно сканиране е само първият страхотен резултат. Ще видим също, че в 9.6 агрегиранията са успоредни, но това е информация за друга статия, която ще бъде пусната през следващите седмици!


  1. Database
  2.   
  3. Mysql
  4.   
  5. Oracle
  6.   
  7. Sqlserver
  8.   
  9. PostgreSQL
  10.   
  11. Access
  12.   
  13. SQLite
  14.   
  15. MariaDB
  1. Съхраняване на дълги двоични (сурови) низове

  2. Съветни заключване или NOWAIT, за да избегнете чакането на заключени редове?

  3. Намерете посоченото име на таблица, като използвате име на таблица, поле и схема

  4. PostgreSQL:текущ брой редове за заявка „по минута“

  5. Как да мигрирам PostgreSQL база данни в SQLServer?