sql >> Base de Datos >  >> RDS >> PostgreSQL

Cómo aprovechar las nuevas funciones de partición en PostgreSQL 11

¿Qué es el particionamiento?

El particionamiento divide las tablas grandes en partes más pequeñas, lo que ayuda a aumentar el rendimiento de las consultas, facilita las tareas de mantenimiento, mejora la eficiencia del archivo de datos y acelera las copias de seguridad de la base de datos. Puede leer más sobre el particionamiento de PostgreSQL en nuestro blog "Una guía para el particionamiento de datos en PostgreSQL".

Con el reciente lanzamiento de PostgreSQL 11, hay muchas características nuevas y asombrosas de particionamiento. Los detalles de estas nuevas funciones de partición se tratarán en este blog con algunos ejemplos de código.

Actualización de las claves de partición

Antes de PostgreSQL 11, la declaración de actualización que cambia el valor de la clave de partición estaba restringida y no permitida. Esto ahora es posible en la nueva versión. La declaración de actualización puede cambiar el valor de la clave de partición; en realidad mueve las filas a la tabla de particiones correcta. Bajo el capó, básicamente ejecuta ELIMINAR DE la partición anterior e INSERTAR en la partición nueva (ELIMINAR + INSERTAR).

Muy bien, probemos esto. Cree una tabla y verifique cómo funciona la actualización en la clave de partición.

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)

Precaución:la ACTUALIZACIÓN generará un error si no hay una tabla de partición predeterminada y los valores actualizados no coinciden con los criterios de partición en ninguna tabla secundaria.

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]

Crear una partición predeterminada

La función de partición DEFAULT de PostgreSQL 11 almacena tuplas que no se asignan a ninguna otra partición. Antes de PostgreSQL 11, estas filas fallaban. Una fila que no esté asignada a ninguna tabla de partición se insertará en la partición predeterminada.

Ejemplo de laboratorio:el código de país "EE. UU." no se definió en la tabla de particiones a continuación, pero aun así se insertó correctamente en la tabla predeterminada.

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

Advertencia:la partición predeterminada evitará que se agreguen nuevas particiones si ese valor de partición existe en la tabla predeterminada. En este caso, `USA` existía en la partición predeterminada, por lo que no funcionará como se muestra a continuación.

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 :

La partición DEFAULT no se puede especificar para la tabla particionada HASH. No puede haber más de una tabla DEFAULT para la tabla de particiones.

Partición hash

Es un nuevo mecanismo de partición, si no puede decidir sobre una partición de rango o lista (ya que no está seguro de qué tan grande sería el cubo). La partición hash resuelve este problema de distribución de datos.

La tabla se particiona especificando un módulo y un resto para cada partición. Cada partición contendrá las filas para las que el valor hash de la clave de partición dividido por el módulo especificado producirá el resto especificado. La función HASH asegura que las filas se distribuirán en su mayoría de manera uniforme en toda la tabla de particiones.

Para empezar, debe decidir cuántos números de la tabla de partición se requieren y, en consecuencia, se pueden definir el módulo y el resto; si el módulo fuera 4, el resto solo puede ser de [0-3].

[Módulo - Número de mesas | Resto:qué valor del resto va a qué depósito]

Cómo configurar una partición 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);

Inserte 50k registros en la tabla principal:

severalnines_v11=# INSERT INTO part_hash_test SELECT generate_series(0,50000);
INSERT 0 50001

y vea cómo distribuyó los registros de manera uniforme en la tabla secundaria...

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)

No podemos cambiar la cantidad de particiones especificada por `Modulus` anteriormente, por lo que debe planificar mucho antes de los requisitos para la cantidad de tablas de partición.

Se producirá un error cuando intente agregar una nueva partición con un resto 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);

La partición hash puede funcionar en cualquier tipo de datos y también puede funcionar para el tipo UUID. Siempre se recomienda que el número de tablas sea una potencia de 2, y tampoco es obligatorio usar el mismo módulo al crear la tabla; esto ayudará a crear la tabla de particiones más adelante según sea necesario.

Esta implementación también haría que el vacío fuera más rápido y puede permitir la unión inteligente de particiones.

Soporte para claves foráneas

Antes de PostgreSQL 11, no se admitía la clave externa en la tabla de particiones. Las claves foráneas son posibles en la tabla de particiones ahora y a continuación se muestra cómo...

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

Creación automática de índices en tablas secundarias

En versiones anteriores de PostgreSQL, era un esfuerzo manual crear un índice en cada tabla de particiones. En la versión 11 de PostgreSQL, es bastante conveniente para los usuarios. Una vez que se crea el índice en la tabla maestra, creará automáticamente el índice con la misma configuración en todas las particiones secundarias existentes y también se ocupará de las futuras tablas de partición.

Índice creado en la tabla maestra

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

Creó automáticamente el índice en todas las tablas secundarias como se muestra a continuación. (Verifique con la tabla del 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)

El índice solo se puede crear en una tabla maestra, no puede estar en una tabla secundaria. Los índices generados automáticamente no se pueden eliminar individualmente.

Creación automática de disparadores en tablas secundarias

Una vez que se crea el activador en la tabla principal, se creará automáticamente el activador en todas las tablas secundarias (este comportamiento es similar al observado para el índice).

Capaz de crear un índice único

En la versión 11, se pueden agregar índices únicos a la tabla maestra, lo que creará la restricción única en todas las tablas secundarias existentes y futuras tablas de partición.

Creemos una tabla maestra con restricciones únicas.

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);

La restricción única se ha creado en la tabla secundaria automáticamente como se muestra a continuación.

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)

Precaución:una restricción única en la tabla principal en realidad no garantiza la unicidad en toda la jerarquía de partición. No es una restricción global, es solo local.

Descargue el documento técnico hoy Gestión y automatización de PostgreSQL con ClusterControl Obtenga información sobre lo que necesita saber para implementar, monitorear, administrar y escalar PostgreSQLDescargar el documento técnico

Rendimiento de consultas más rápido

Recorte dinámico de particiones

En PostgreSQL 11, la búsqueda binaria permite una identificación más rápida de las tablas secundarias requeridas, ya sea que estén particionadas por LISTA o RANGO. La función hash encuentra la partición coincidente para la partición HASH. De hecho, elimina dinámicamente las tablas de partición que no son necesarias y aumenta el rendimiento de Query.

La poda de partición dinámica se puede controlar mediante el 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)

La otra implementación increíble es así.

Recorte de partición de tiempo de ejecución

En las versiones de PostgreSQL anteriores a la 11, la eliminación de particiones solo puede ocurrir en el momento del plan; planificador requiere un valor de clave de partición para identificar la partición correcta. Este comportamiento se solucionó en PostgreSQL 11, ya que el planificador del tiempo de ejecución sabría qué valor se está proporcionando y, en función de esa partición, la selección/eliminación es posible y se ejecutaría mucho más rápido. El caso de uso puede ser una consulta que utilice un parámetro (declaración preparada) O una subconsulta que proporcione el 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)

En el plan explicado arriba, podemos ver que, en el momento de la ejecución, el planificador identificó sobre la marcha la tabla de partición correcta en función del valor del parámetro, se ejecutó mucho más rápido y no dedicó tiempo a escanear/bucle en otra tabla de partición (ver nunca sección ejecutada en el plan de explicación anterior). Esto es muy poderoso y comenzó una nueva era de mejora del rendimiento en el particionamiento.

Agregado inteligente de partición

Parámetro:enable_partitionwise_aggregate

Si la clave de partición coincide con la clave de agrupación, cada partición producirá un conjunto discreto de grupos en lugar de escanear toda la partición a la vez. Hará el agregado paralelo para cada partición y durante el resultado final concatenará todos los 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)

Sin duda, esto es más rápido, ya que incluye el procesamiento de agregación en paralelo y el análisis por partición.

La consulta de catálogo se puede utilizar para conocer todas las tablas de partición principales.

SELECT relname FROM pg_class WHERE oid in (select partrelid FROM  pg_partitioned_table);

Breve matriz de funciones de partición

Características de partición v11 v10
Partición predeterminada SI NO
Herencia de tabla externa SI NO
Particionamiento por clave hash SI NO
Soporte para PK y FK SI NO
ACTUALIZAR en una clave de partición SI NO
Inexes automatizados en CT SI NO
Activadores automáticos en CT SI NO
Poda de partición de tiempo de ejecución SI NO
Unión inteligente de partición SI NO
Punción de partición dinámica SI NO

¿Qué sigue?

Rendimiento de partición

Esta es una de las áreas de trabajo más activas ahora en la comunidad de PostgreSQL. La versión 12 de PostgreSQL se empaquetará con aún más mejoras de rendimiento en el espacio de partición. Se espera que la versión 12 se lance en noviembre de 2019.