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

Mejoras de partición en PostgreSQL 11

Un sistema de partición en PostgreSQL fue agregado por primera vez en PostgreSQL 8.1 por el fundador de 2ndQuadrant Simon Riggs . Se basaba en la herencia de relaciones y utilizaba una técnica novedosa para evitar que las tablas fueran analizadas por una consulta, denominada "exclusión de restricciones". Si bien fue un gran paso adelante en ese momento, hoy en día se considera engorroso de usar y lento, y por lo tanto necesita ser reemplazado.

En la versión 10, fue reemplazada gracias a los heroicos esfuerzos de Amit Langote con una “partición declarativa” de estilo moderno. Esta nueva tecnología significó que ya no necesitaba escribir código manualmente para enrutar tuplas a sus particiones correctas, y ya no necesitaba declarar manualmente las restricciones correctas para cada partición:el sistema hizo esas cosas automáticamente por usted.

Lamentablemente, en PostgreSQL 10 eso es prácticamente todo lo que hizo. Debido a la gran complejidad y las limitaciones de tiempo, faltaban muchas cosas en la implementación de PostgreSQL 10. Roberto Haas dio una charla al respecto en la PGConf.EU de Varsovia.

Muchas personas trabajaron para mejorar la situación de PostgreSQL 11; aquí está mi intento de recuento. Los divido en tres áreas:

  1. Nuevas funciones de partición
  2. Mejor compatibilidad con DDL para tablas particionadas
  3. Optimizaciones de rendimiento.

Nuevas funciones de partición

En PostgreSQL 10, sus tablas particionadas pueden estar en RANGE y LISTA modos. Estas son herramientas poderosas para basar muchas bases de datos del mundo real, pero para muchos otros diseños necesita el nuevo modo agregado en PostgreSQL 11:HASH partición . Muchos clientes necesitan esto y Amul Sul trabajó duro para hacerlo posible. He aquí un ejemplo simple:

CREATE TABLE clients (
client_id INTEGER, name TEXT
) PARTITION BY HASH (client_id);

CREATE TABLE clients_0 PARTITION OF clients
    FOR VALUES WITH (MODULUS 3, REMAINDER 0);
CREATE TABLE clients_1 PARTITION OF clients
    FOR VALUES WITH (MODULUS 3, REMAINDER 1);
CREATE TABLE clients_2 PARTITION OF clients
    FOR VALUES WITH (MODULUS 3, REMAINDER 2);

No es obligatorio utilizar el mismo valor de módulo para todas las particiones; esto le permite crear más particiones más adelante y redistribuir las filas una partición a la vez, si es necesario.

Otra característica muy útil, escrita por Amit Khandekar es la capacidad de permitir ACTUALIZAR para mover filas de una partición a otra — es decir, si hay un cambio en los valores de la columna de partición, la fila se mueve automáticamente a la partición correcta. Anteriormente, esa operación habría arrojado un error.

Otra característica nueva, escrita por Amit Langote y atentamente , es que INSERTAR EN ACTUALIZACIÓN DE CONFLICTO se puede aplicar a tablas particionadas . Anteriormente, este comando fallaba si se dirigía a una tabla particionada. Podría hacer que funcione sabiendo exactamente en qué partición terminaría la fila, pero eso no es muy conveniente. No repasaré los detalles de ese comando, pero si alguna vez deseaste tener UPSERT en Postgres, esto es todo. Una advertencia es que la ACTUALIZACIÓN la acción no puede mover la fila a otra partición.

Finalmente, otra linda nueva característica en PostgreSQL 11, esta vez por Jeevan Ladhe, Beena Emerson, Ashutosh Bapat, Rahila Syed, y Robert Haas es soporte para una partición predeterminada en una tabla particionada , es decir, una partición que recibe todas las filas que no caben en ninguna de las particiones regulares. Sin embargo, si bien es agradable sobre el papel, esta característica no es muy conveniente en la configuración de producción porque algunas operaciones requieren un bloqueo más pesado con las particiones predeterminadas que sin ellas. Ejemplo:la creación de una nueva partición requiere escanear la partición predeterminada para determinar que ninguna fila existente coincida con los límites de la nueva partición. Tal vez en el futuro se reduzcan estos requisitos de bloqueo, pero mientras tanto, mi sugerencia es no usarlo.

Mejor compatibilidad con DDL

En PostgreSQL 10, ciertos DDL se negaban a funcionar cuando se aplicaban a una tabla particionada y requerían que usted procesara cada partición individualmente. En PostgreSQL 11 hemos solucionado algunas de estas limitaciones, como lo anunció anteriormente Simon Riggs. Primero, ahora puede usar CREAR ÍNDICE en una tabla dividida , un artículo escrito por un servidor suyo. Esto se puede ver simplemente como una cuestión de reducir el tedio:en lugar de repetir el comando para cada partición (y asegurarse de nunca olvidarlo para cada nueva partición), puede hacerlo solo una vez para la tabla particionada principal, y se aplica automáticamente. a todas las particiones, existentes y futuras.

Una cosa interesante a tener en cuenta es la coincidencia de los índices existentes en las particiones. Como sabe, la creación de un índice es una propuesta de bloqueo, por lo que cuanto menos tiempo lleve, mejor. Escribí esta función para que los índices existentes en la partición se comparen con los índices que se están creando y, si hay coincidencias, no es necesario escanear la partición para crear nuevos índices:se utilizarán los índices existentes.

Junto con esto, también por su servidor, también puede crear ÚNICO restricciones, así como CLAVE PRIMARIA restricciones . Dos advertencias:primero, la clave de partición debe ser parte de la clave principal. Esto permite que las comprobaciones únicas se realicen localmente por partición, evitando los índices globales. En segundo lugar, todavía no es posible tener claves externas que hagan referencia a estas claves principales. Estoy trabajando en eso para PostgreSQL 12.

Otra cosa que puedes hacer (gracias a la misma persona) es crear PARA CADA FILA disparadores en una tabla particionada y hacer que se aplique a todas las particiones (existentes y futuras). Como efecto secundario, puede tener único diferido Restricciones en tablas particionadas. Una advertencia:solo DESPUÉS se permiten disparadores, hasta que descubramos cómo lidiar con ANTES disparadores que mueven filas a una partición diferente.

Por último, una tabla particionada puede tener CLAVE EXTERNA restricciones . Esto es muy útil para particionar grandes tablas de hechos y evitar las referencias colgantes, que todo el mundo detesta. Mi colega Gabriele Bartolini me agarró del regazo cuando descubrió que había escrito y cometido esto, gritando que esto era un cambio de juego y cómo podía ser tan insensible como para no informarle de esto. Yo solo sigo hackeando el código por diversión.

Trabajo de rendimiento

Anteriormente, el preprocesamiento de consultas para averiguar qué particiones no escanear (exclusión de restricciones) era bastante simple y lento. Esto se ha mejorado gracias al admirable trabajo en equipo realizado por Amit Langote, David Rowley, Beena Emerson y Dilip Kumar para introducir primero la "poda más rápida" y la "poda en tiempo de ejecución" basada en ella después. El resultado es mucho más potente y rápido (David Rowley ya describí esto en un artículo anterior). Después de todo este esfuerzo, la eliminación de particiones se aplica en tres puntos de la vida de una consulta:

  1. En el momento del plan de consulta,
  2. Cuando se reciben los parámetros de consulta,
  3. En cada punto donde un nodo de consulta pasa valores como parámetros a otro nodo.

Esta es una mejora notable con respecto al sistema original que solo se podía aplicar en el momento del plan de consulta y creo que complacerá a muchos.

Puede ver esta función en acción comparando la salida de EXPLAIN para una consulta antes y después de desactivar enable_partition_pruning opción. Como un ejemplo muy simple, compare este plan sin podar:

SET enable_partition_pruning TO off;
EXPLAIN (ANALYZE, COSTS off)
SELECT * FROM clientes
WHERE cliente_id = 1234;
                                QUERY PLAN                                
-------------------------------------------------------------------------
 Append (actual time=6.658..10.549 rows=1 loops=1)
   ->  Seq Scan on clientes_1 (actual time=4.724..4.724 rows=0 loops=1)
         Filter: (cliente_id = 1234)
         Rows Removed by Filter: 24978
   ->  Seq Scan on clientes_00 (actual time=1.914..1.914 rows=0 loops=1)
         Filter: (cliente_id = 1234)
         Rows Removed by Filter: 12644
   ->  Seq Scan on clientes_2 (actual time=0.017..1.021 rows=1 loops=1)
         Filter: (cliente_id = 1234)
         Rows Removed by Filter: 12570
   ->  Seq Scan on clientes_3 (actual time=0.746..0.746 rows=0 loops=1)
         Filter: (cliente_id = 1234)
         Rows Removed by Filter: 12448
   ->  Seq Scan on clientes_01 (actual time=0.648..0.648 rows=0 loops=1)
         Filter: (cliente_id = 1234)
         Rows Removed by Filter: 12482
   ->  Seq Scan on clientes_4 (actual time=0.774..0.774 rows=0 loops=1)
         Filter: (cliente_id = 1234)
         Rows Removed by Filter: 12400
   ->  Seq Scan on clientes_5 (actual time=0.717..0.717 rows=0 loops=1)
         Filter: (cliente_id = 1234)
         Rows Removed by Filter: 12477
 Planning Time: 0.375 ms
 Execution Time: 10.603 ms

con el producido con poda:

EXPLAIN (ANALYZE, COSTS off)
SELECT * FROM clientes
WHERE cliente_id = 1234;
                                QUERY PLAN                               
----------------------------------------------------------------------
 Append (actual time=0.054..2.787 rows=1 loops=1)
   ->  Seq Scan on clientes_2 (actual time=0.052..2.785 rows=1 loops=1)
         Filter: (cliente_id = 1234)
         Rows Removed by Filter: 12570
 Planning Time: 0.292 ms
 Execution Time: 2.822 ms

Estoy seguro de que lo encontrará convincente. Puede ver un montón de ejemplos más sofisticados examinando el archivo esperado de las pruebas de regresión.

Otro elemento fue la introducción de uniones por partición, por parte de Ashutosh Bapat. . La idea aquí es que si tiene dos tablas particionadas, y están particionadas de manera idéntica, entonces, cuando se unen, puede unir cada partición de un lado con su partición correspondiente del otro lado; esto es mucho mejor que unir cada partición de un lado a cada partición del otro lado. El hecho de que los esquemas de partición deben coincidir exactamente puede hacer que parezca poco probable que esto tenga mucho uso en el mundo real, pero en realidad hay muchas situaciones en las que esto se aplica. Ejemplo:una tabla de pedidos y su correspondiente tabla orders_items. Afortunadamente, ya hay mucho trabajo para relajar esta restricción.

El último elemento que quiero mencionar son los agregados por partición, por Jeevan Chalke, Ashutosh Bapat, y Robert Haas . Esta optimización significa que una agregación que incluye las claves de partición en el GROUP BY La cláusula se puede ejecutar agregando las filas de cada partición por separado, lo que es mucho más rápido.

Pensamientos finales

Después de los desarrollos significativos en este ciclo, PostgreSQL tiene una historia de partición mucho más convincente. Si bien aún quedan muchas mejoras por realizar, en particular para mejorar el rendimiento y la simultaneidad de varias operaciones que involucran tablas particionadas, ahora estamos en un punto en el que la partición declarativa se ha convertido en una herramienta muy valiosa para muchos casos de uso. En 2ndQuadrant continuaremos contribuyendo con código para mejorar PostgreSQL en esta y otras áreas, como lo hemos hecho para cada versión desde la 8.0.