PostgreSQL
 sql >> Base de Dados >  >> RDS >> PostgreSQL

PostgreSQL 9.6:Varredura Sequencial Paralela




Por muito tempo, uma das deficiências mais conhecidas do PostgreSQL foi a capacidade de paralelizar consultas. Com o lançamento da versão 9.6, isso não será mais um problema. Um grande trabalho tem sido feito neste assunto, começando pelo commit 80558c1, a introdução do scan sequencial paralelo, que veremos no decorrer deste artigo.



Primeiro, você deve tomar nota:o desenvolvimento deste recurso tem sido contínuo e alguns parâmetros mudaram de nome entre um commit e outro. Este artigo foi escrito usando um checkout realizado em 17 de junho e alguns recursos aqui ilustrados estarão presentes apenas na versão 9.6 beta2.

Comparado com a versão 9.5, novos parâmetros foram introduzidos dentro do arquivo de configuração. Esses são:
  • max_parallel_workers_per_gather :o número de trabalhadores que podem auxiliar uma varredura sequencial de uma tabela;
  • min_parallel_relation_size :o tamanho mínimo que uma relação deve ter para que o planejador considere o uso de trabalhadores adicionais;
  • parallel_setup_cost :o parâmetro do planejador que estima o custo de instanciar um trabalhador;
  • parallel_tuple_cost :o parâmetro do planejador que estima o custo de transferência de uma tupla de um trabalhador para outro;
  • force_parallel_mode :parâmetro útil para testes, paralelismo forte e também uma consulta na qual o planejador operaria de outras maneiras.

Vamos ver como os trabalhadores adicionais podem ser usados ​​para acelerar nossas consultas. Criamos uma tabela de teste com um campo INT e cem milhões de registros:
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 tem max_parallel_workers_per_gather definido como 2 por padrão, para o qual dois trabalhadores serão ativados durante uma varredura sequencial.

Uma simples varredura sequencial não apresenta nenhuma novidade:
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)

Na verdade, a presença de um WHERE cláusula é necessária para paralelização:
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)

Podemos voltar para a ação anterior e observar a configuração de diferenças max_parallel_workers_per_gather para 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)

Um tempo 2,5 vezes maior.

O planejador nem sempre considera uma varredura sequencial paralela como a melhor opção. Se uma consulta não for seletiva o suficiente e houver muitas tuplas para transferir de trabalhador para trabalhador, ela pode preferir uma varredura sequencial “clássica”:
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)

De fato, se tentarmos forçar uma varredura sequencial paralela, obteremos um resultado pior:
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)

O número de trabalhadores pode ser aumentado até max_worker_processes (padrão:8). Restauramos o valor de parallel_tuple_cost e vemos o que acontece aumentando max_parallel_workers_per_gather a 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)

Embora o PostgreSQL pudesse usar até 8 trabalhadores, ele instancia apenas seis. Isso ocorre porque o Postgres também otimiza o número de trabalhadores de acordo com o tamanho da tabela e o min_parallel_relation_size . O número de trabalhadores disponibilizados pelo postgres é baseado em uma progressão geométrica com 3 como razão comum 3 e min_parallel_relation_size como fator de escala. Aqui está um exemplo. Considerando os 8 MB de parâmetro padrão:
Tamanho Trabalhador
<8MB 0
<24MB 1
<72MB 2
<216MB 3
<648 MB 4
<1944MB 5
<5822MB 6

O tamanho da nossa tabela é 3458 MB, então 6 é o número máximo de trabalhadores disponíveis.
postgres=# \dt+ test
                    List of relations
 Schema | Name | Type  |  Owner   |  Size   | Description
--------+------+-------+----------+---------+-------------
 public | test | table | postgres | 3458 MB |
(1 row)

Por fim, farei uma breve demonstração das melhorias alcançadas por meio deste patch. Executando nossa consulta com um número crescente de trabalhadores em crescimento, obtemos os seguintes resultados:
Trabalhadores Hora
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

Podemos ver que os tempos melhoram drasticamente, até chegar a um terço do valor inicial. Também é simples explicar o fato de que não vemos melhorias entre o uso de 3 e 6 trabalhadores:a máquina em que o teste foi executado possui 4 CPUs, portanto, os resultados são estáveis ​​após adicionar mais 3 trabalhadores ao processo original .

Finalmente, o PostgreSQL 9.6 preparou o cenário para a paralelização de consultas, na qual a varredura sequencial paralela é apenas o primeiro grande resultado. Também veremos que na 9.6 as agregações foram paralelizadas, mas isso é informação para outro artigo que será lançado nas próximas semanas!