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

Como aproveitar os novos recursos de particionamento do PostgreSQL 11


O que é particionamento?



O particionamento divide tabelas grandes em partes menores, o que ajuda a aumentar o desempenho das consultas, facilitando as tarefas de manutenção, melhorando a eficiência do arquivamento de dados e backups de banco de dados mais rápidos. Você pode ler mais sobre o particionamento do PostgreSQL em nosso blog “A Guide to Partitioning Data In PostgreSQL”.



Com o lançamento recente do PostgreSQL 11, há muitos novos recursos incríveis de particionamento. Os detalhes desses novos recursos de particionamento serão abordados neste blog com alguns exemplos de código.

Atualizando as chaves de partição


Antes do PostgreSQL 11, a instrução Update que altera o valor da chave de partição era restrita e não permitida. Isso agora é possível na nova versão. A instrução de atualização pode alterar o valor da chave de partição; na verdade, ele move as linhas para a tabela de partição correta. Sob o capô, ele basicamente executa DELETE FROM partição antiga e INSERT em nova partição ( DELETE + INSERT).

Tudo bem, vamos testar isso. Crie uma tabela e verifique como a atualização funciona na chave de partição.
CREATE TABLE customers(cust_id bigint NOT NULL,cust_name varchar(32) NOT NULL,cust_address text,
cust_country text)PARTITION BY LIST(cust_country);
CREATE TABLE customer_ind PARTITION OF customers FOR VALUES IN ('ind');
CREATE TABLE customer_jap PARTITION OF customers FOR VALUES IN ('jap');
CREATE TABLE customers_def PARTITION OF customers DEFAULT;
severalnines_v11=# INSERT INTO customers VALUES (2039,'Puja','Hyderabad','ind');
INSERT 0 1
severalnines_v11=#  SELECT * FROM customer_ind;
 cust_id | cust_name | cust_address | cust_country
  2039 | Puja      | Hyderabad    | ind
(1 row)
severalnines_v11=# UPDATE customers SET cust_country ='jap' WHERE cust_id=2039;
UPDATE 1
--  it moved the row to correct  partition table.
severalnines_v11=# SELECT * FROM customer_ind;
 cust_id | cust_name | cust_address | cust_country
---------+-----------+--------------+--------------
(0 rows)
severalnines_v11=# SELECT * FROM customer_jap;
 cust_id | cust_name | cust_address | cust_country
---------+-----------+--------------+--------------
    2039 | Puja      | Hyderabad    | jap
(1 row)

Cuidado:O UPDATE apresentará um erro, se não houver uma tabela de partição padrão e os valores atualizados não corresponderem aos critérios de partição em qualquer tabela filha.
severalnines_v11=#  UPDATE customers1 SET cust_country ='ypp' WHERE cust_id=2039;
2018-11-21 00:13:54.901 IST [1479] ERROR:  no partition of relation "customers1" found for row
2018-11-21 00:13:54.901 IST [1479] DETAIL:  Partition key of the failing row contains (cust_country) = (ypp).
2018-11-21 00:13:54.901 IST [1479] STATEMENT:  UPDATE customers1 SET cust_country ='ypp' WHERE cust_id=2039;
ERROR:  no partition of relation "customers1" found for row
DETAIL:  Partition key of the failing row contains (cust_country) = (ypp).
[ -- the value of cust_country was not mapped to any part table so it failed]

Criando uma partição padrão


O recurso de partição DEFAULT do PostgreSQL 11 armazena tuplas que não são mapeadas para nenhuma outra partição. Antes do PostgreSQL 11, essas linhas apresentavam erros. Uma linha não mapeada para nenhuma tabela de partição seria inserida na partição padrão.

Exemplo de laboratório:o código do país `USA` não foi definido na tabela de partições abaixo, mas ainda é inserido na tabela padrão com sucesso.
CREATE TABLE customers_def PARTITION OF customers DEFAULT;
severalnines_v11=#  INSERT INTO customers VALUES (4499,'Tony','Arizona','USA');
INSERT 0 1
severalnines_v11=#  select * FROM customers_def;
 cust_id | cust_name | cust_address | cust_country
---------+-----------+--------------+--------------
    4499 | Tony      | Arizona      | USA

Aviso:A partição padrão impedirá qualquer nova adição de partição se esse valor de partição existir na tabela padrão. Neste caso, `USA` existia na partição padrão, então não funcionará como abaixo.
severalnines_v11=# CREATE TABLE customer_usa PARTITION OF customers FOR VALUES IN ('USA');
2018-11-21 00:46:34.890 IST [1526] ERROR:  updated partition constraint for default partition "customers_def" would be violated by some row
2018-11-21 00:46:34.890 IST [1526] STATEMENT:  CREATE TABLE customer_usa PARTITION OF customers FOR VALUES IN ('USA');ERROR:  updated partition constraint for default partition "customers_def" would be violated by some row
severalnines_v11=#
Resolution - You need to move/remove those rows from Default table, then it will then let you create new part table like below.
severalnines_v11=# DELETE FROM customers_def WHERE cust_country in ('USA'); DELETE 1
severalnines_v11=# CREATE TABLE customer_usa PARTITION OF customers FOR VALUES IN ('USA');
CREATE TABLE
severalnines_v11=#
Nudgets :

A partição DEFAULT não pode ser especificada para a tabela particionada HASH. Não pode haver mais de uma tabela DEFAULT para a tabela de partição.

Particionamento de hash


É um novo mecanismo de partição, se você não puder decidir sobre uma partição de intervalo ou lista (já que você não tem certeza do tamanho do bucket). O particionamento de hash resolve esse problema de distribuição de dados.

A tabela é particionada especificando um módulo e um resto para cada partição. Cada partição conterá as linhas para as quais o valor de hash da chave de partição dividido pelo módulo especificado produzirá o restante especificado. A função HASH garante que as linhas sejam distribuídas de maneira uniforme em toda a tabela de partição.

Para começar, você precisa decidir quantos números da tabela de partição são necessários e, de acordo, o módulo e o restante podem ser definidos; se o módulo for 4, o resto só pode ser de [0-3].

[Módulo - Número de mesas | Restante - Qual valor do resto vai para qual bucket ]

Como configurar uma partição de hash

-- hash partition
CREATE TABLE part_hash_test (x int, y text) PARTITION BY hash (x);
-- create child partitions
CREATE TABLE part_hash_test_0 PARTITION OF part_hash_test FOR VALUES WITH (MODULUS 4, REMAINDER 0);
CREATE TABLE part_hash_test_1 PARTITION OF part_hash_test FOR VALUES WITH (MODULUS 4, REMAINDER 1);
CREATE TABLE part_hash_test_2 PARTITION OF part_hash_test FOR VALUES WITH (MODULUS 4, REMAINDER 2);
CREATE TABLE part_hash_test_3 PARTITION OF part_hash_test FOR VALUES WITH (MODULUS 4, REMAINDER 3);

Insira 50k registros na tabela pai:
severalnines_v11=# INSERT INTO part_hash_test SELECT generate_series(0,50000);
INSERT 0 50001

e veja como ele distribuiu os registros uniformemente na tabela filho...
severalnines_v11=# SELECT count(1),tableoid::regclass FROM part_hash_test GROUP by 2 order by 2 ;
 count |     tableoid
-------+------------------
 12537 | part_hash_test_0
 12473 | part_hash_test_1
 12509 | part_hash_test_2
 12482 | part_hash_test_3
(4 rows)

Não podemos alterar o número de partições especificado pelo `Modulus` anteriormente, então você precisa planejar bem antes dos requisitos para o número de tabelas de partição.

Ocorrerá um erro quando você tentar adicionar uma nova partição com um restante diferente.
severalnines_v11=# CREATE TABLE part_hash_test_5 PARTITION OF part_hash_test FOR VALUES
WITH (MODULUS 4, REMAINDER 5);severalnines_v11-#
2018-11-21 01:51:28.966 IST [1675] ERROR:  remainder for hash partition must be less than modulus
2018-11-21 01:51:28.966 IST [1675] STATEMENT:  CREATE TABLE part_hash_test_5 PARTITION OF part_hash_test FOR VALUES  WITH (MODULUS 4, REMAINDER 5);

O particionamento de hash pode funcionar em qualquer tipo de dados e também pode funcionar para o tipo UUID. É sempre recomendado que o número de tabelas seja uma potência de 2, e também não é obrigatório usar o mesmo módulo na criação da tabela; isso ajudará a criar a tabela de partição posteriormente, conforme necessário.

Essa implementação também tornaria o vácuo mais rápido e pode permitir a junção por partição.

Suporte para chaves estrangeiras


Antes do PostgreSQL 11, a chave estrangeira na tabela de partição não era suportada. As chaves estrangeiras são possíveis na tabela de partição agora e abaixo é como ...
severalnines_v11=# CREATE TABLE customers2 ( cust_id integer PRIMARY KEY );
CREATE TABLE
severalnines_v11=# CREATE TABLE account (
    ac_date   date    NOT NULL,
    cust_id  integer REFERENCES customers2(cust_id),
     amount INTEGER NOT NULL) PARTITION BY RANGE (ac_date);
CREATE TABLE

Criação de índice automático em tabelas filhas


Nas versões anteriores do PostgreSQL, era um esforço manual criar um índice em cada tabela de partição. Na versão 11 do PostgreSQL, é bastante conveniente para os usuários. Depois que o índice for criado na tabela mestre, ele criará automaticamente o índice com a mesma configuração em todas as partições filhas existentes e cuidará de todas as tabelas de partição futuras.

Índice criado na tabela mestra

severalnines_v11=# CREATE index idx_name ON customers(cust_name);
CREATE INDEX

Ele criou automaticamente o índice em todas as tabelas filhas conforme abaixo. (Verifique com tabela de catálogo)
severalnines_v11=# SELECT tablename,indexname,indexdef FROM pg_indexes WHERE tablename ilike '%customer_%';
   tablename   |          indexname          |       indexdef
---------------+-----------------------------+------------------------------------------------------------------------------------------
 customer_ind  | customer_ind_cust_name_idx  | CREATE INDEX customer_ind_cust_name_idx ON public.customer_ind USING btree (cust_name)
 customer_jap  | customer_jap_cust_name_idx  | CREATE INDEX customer_jap_cust_name_idx ON public.customer_jap USING btree (cust_name)
 customer_usa  | customer_usa_cust_name_idx  | CREATE INDEX customer_usa_cust_name_idx ON public.customer_usa USING btree (cust_name)
 customers_def | customers_def_cust_name_idx | CREATE INDEX customers_def_cust_name_idx ON public.customers_def USING btree (cust_name)
(4 rows)

O índice só pode ser criado em uma tabela mestre, não pode ser em uma tabela filha. Índices gerados automaticamente não podem ser excluídos individualmente.

Criação de acionadores automáticos em tabelas filhas


Depois que o gatilho for criado na tabela mestre, ele criará automaticamente o gatilho em todas as tabelas filhas (esse comportamento é semelhante ao visto para o índice).

Capaz de criar um índice exclusivo


Na versão 11, índices exclusivos podem ser adicionados à tabela mestre, que criará a restrição exclusiva em todas as tabelas filhas existentes e nas tabelas de partição futuras.

Vamos criar uma tabela mestre com restrições exclusivas.
CREATE TABLE uniq_customers(  cust_id bigint NOT NULL, cust_name varchar(32) NOT NULL, cust_address text, cust_country text,cust_email text, unique(cust_email,cust_id,cust_country)  )PARTITION BY LIST(cust_country);

A restrição exclusiva foi criada na tabela filha automaticamente como abaixo.
severalnines_v11=# SELECT table_name,constraint_name,constraint_type FROM information_schema.table_constraints WHERE table_name ilike '%uniq%' AND constraint_type = 'UNIQUE';
    table_name     |                    constraint_name                    | constraint_type
-------------------+-------------------------------------------------------+-----------------
 uniq_customers    | uniq_customers_cust_email_cust_id_cust_country_key    | UNIQUE
 uniq_customer_ind | uniq_customer_ind_cust_email_cust_id_cust_country_key | UNIQUE
(2 rows)

Cuidado:Uma restrição exclusiva na tabela pai não garante, na verdade, exclusividade em toda a hierarquia de particionamento. Não é uma restrição global, é apenas local.
Baixe o whitepaper hoje PostgreSQL Management &Automation with ClusterControlSaiba o que você precisa saber para implantar, monitorar, gerenciar e dimensionar o PostgreSQLBaixe o whitepaper

Desempenho de consulta mais rápido

Remoção de Partições Dinâmicas


No PostgreSQL 11, a pesquisa binária permite a identificação mais rápida das tabelas filhas necessárias, sejam elas particionadas LIST ou RANGE. A função de hash encontra a partição correspondente para a partição HASH. Na verdade, ele elimina dinamicamente as tabelas de partição que não são necessárias e aumenta o desempenho da consulta.

A remoção de partição dinâmica pode ser controlada pelo parâmetro `enable_partition_pruning`.
severalnines_v11=# show enable_partition_pruning;
 enable_partition_pruning
--------------------------
 off
(1 row)
severalnines_v11=# EXPLAIN SELECT * from customers where cust_country = 'ind';
                             QUERY PLAN
---------------------------------------------------------------------
 Append  (cost=0.00..18.54 rows=5 width=154)
   ->  Seq Scan on customer_ind  (cost=0.00..1.01 rows=1 width=154)
         Filter: (cust_country = 'ind'::text)
   ->  Seq Scan on customer_jap  (cost=0.00..1.00 rows=1 width=154)
         Filter: (cust_country = 'ind'::text)
   ->  Seq Scan on customer_usa  (cost=0.00..15.50 rows=2 width=154)
         Filter: (cust_country = 'ind'::text)
   ->  Seq Scan on customers_def  (cost=0.00..1.00 rows=1 width=154)
         Filter: (cust_country = 'ind'::text)
(9 rows)
Enabled the parameter to ON.
severalnines_v11=# set enable_partition_pruning TO on;
SET
severalnines_v11=# EXPLAIN SELECT * from customers where cust_country = 'ind';
                             QUERY PLAN
--------------------------------------------------------------------
 Append  (cost=0.00..1.02 rows=1 width=154)
   ->  Seq Scan on customer_ind  (cost=0.00..1.01 rows=1 width=154)
         Filter: (cust_country = 'ind'::text)
(3 rows)

A outra implementação incrível é assim.

Remoção de partição em tempo de execução


Nas versões do PostgreSQL anteriores à 11, a remoção de partições só pode ocorrer em tempo de planejamento; planejador requer um valor de chave de partição para identificar a partição correta. Esse comportamento é corrigido no PostgreSQL 11, pois o planejador de tempo de execução saberia qual valor está sendo fornecido e com base nessa seleção/eliminação de partição é possível e seria executado muito mais rápido. O caso de uso pode ser uma consulta que usa parâmetro (instrução preparada) OU subconsulta que fornece o valor como parâmetro.
Example : cus_country is partition key and getting value from subquery
severalnines_v11=# explain analyze  select * from customers WHERE cust_country = (select cust_count_x FROM test_execution_prun1);
                                                        QUERY PLAN
---------------------------------------------------------------------------------------------------------------------------
 Append  (cost=23.60..42.14 rows=5 width=154) (actual time=0.019..0.020 rows=0 loops=1)
   InitPlan 1 (returns $0)
     ->  Seq Scan on test_execution_prun1  (cost=0.00..23.60 rows=1360 width=32) (actual time=0.006..0.007 rows=1 loops=1)
   ->  Seq Scan on customer_ind  (cost=0.00..1.01 rows=1 width=154) (never executed)
         Filter: (cust_country = $0)
   ->  Seq Scan on customer_jap  (cost=0.00..1.00 rows=1 width=154) (never executed)
         Filter: (cust_country = $0)
   ->  Seq Scan on customer_usa  (cost=0.00..15.50 rows=2 width=154) (never executed)
         Filter: (cust_country = $0)
   ->  Seq Scan on customers_def  (cost=0.00..1.00 rows=1 width=154) (actual time=0.003..0.003 rows=0 loops=1)
         Filter: (cust_country = $0)
 Planning Time: 0.237 ms
 Execution Time: 0.057 ms
(13 rows)

No plano de explicação acima, podemos ver, no momento da execução, o planejador em tempo real identificou a tabela de partição correta com base no valor do parâmetro e executou muito mais rápido e não gastou tempo em varredura/loop em outra tabela de partição (consulte nunca seção executada no plano de explicação acima). Isso é muito poderoso e iniciou uma nova era de aprimoramento de desempenho no particionamento.

Partição Wise Aggregate


Parâmetro:enable_partitionwise_aggregate

Se a chave de partição corresponder à chave de agrupamento, cada partição produzirá um conjunto discreto de grupos em vez de varrer toda a partição de uma vez. Ele fará a agregação paralela para cada partição e durante o resultado final concatena todos os resultados.
severalnines_v11=# explain SELECT count(1),cust_country FROM customers GROUP BY 2;
                                 QUERY PLAN
----------------------------------------------------------------------------
 HashAggregate  (cost=21.84..23.84 rows=200 width=40)
   Group Key: customer_ind.cust_country
   ->  Append  (cost=0.00..19.62 rows=443 width=32)
         ->  Seq Scan on customer_ind  (cost=0.00..1.01 rows=1 width=32)
         ->  Seq Scan on customer_jap  (cost=0.00..1.00 rows=1 width=32)
         ->  Seq Scan on customer_usa  (cost=0.00..14.40 rows=440 width=32)
         ->  Seq Scan on customers_def  (cost=0.00..1.00 rows=1 width=32)
(7 rows)
severalnines_v11=# SET  enable_partitionwise_aggregate TO on;
SET
severalnines_v11=#  explain SELECT count(1),cust_country FROM customers GROUP BY 2;
                                 QUERY PLAN
----------------------------------------------------------------------------
 Append  (cost=1.01..22.67 rows=203 width=40)
   ->  HashAggregate  (cost=1.01..1.02 rows=1 width=40)
         Group Key: customer_ind.cust_country
         ->  Seq Scan on customer_ind  (cost=0.00..1.01 rows=1 width=32)
   ->  HashAggregate  (cost=1.00..1.01 rows=1 width=40)
         Group Key: customer_jap.cust_country
         ->  Seq Scan on customer_jap  (cost=0.00..1.00 rows=1 width=32)
   ->  HashAggregate  (cost=16.60..18.60 rows=200 width=40)
         Group Key: customer_usa.cust_country
         ->  Seq Scan on customer_usa  (cost=0.00..14.40 rows=440 width=32)
   ->  HashAggregate  (cost=1.00..1.01 rows=1 width=40)
         Group Key: customers_def.cust_country
         ->  Seq Scan on customers_def  (cost=0.00..1.00 rows=1 width=32)
(13 rows)

Isso é certamente mais rápido, pois inclui processamento de agregação paralela e varredura por partição.

A consulta de catálogo pode ser usada para conhecer todas as tabelas de partição pai.
SELECT relname FROM pg_class WHERE oid in (select partrelid FROM  pg_partitioned_table);

Breve Matriz de Recursos de Partição

Recursos de particionamento v11 v10
Partição padrão SIM NÃO
Herança de tabela estrangeira SIM NÃO
Particionamento por chave de hash SIM NÃO
Suporte para PK e FK SIM NÃO
ATUALIZAR em uma chave de partição SIM NÃO
Inexes automatizados no CT SIM NÃO
Acionadores automatizados no CT SIM NÃO
Remoção de partição de tempo de execução SIM NÃO
Partição inteligente Unir SIM NÃO
Remoção de partição dinâmica SIM NÃO

O que vem depois?

Desempenho de particionamento


Esta é uma das áreas de trabalho mais ativas agora na comunidade PostgreSQL. O PostgreSQL Versão 12 será empacotado com ainda mais melhorias de desempenho no espaço de particionamento. Espera-se que a versão 12 seja lançada em novembro de 2019.