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

Una descripción general de los cambios de índice en PostgreSQL 11

La aplicación correcta de los índices puede hacer que las consultas sean increíblemente rápidas.

Los índices usan punteros para acceder a las páginas de datos de manera rápida.

Se produjeron cambios importantes en los índices de PostgreSQL 11, se lanzaron muchos parches muy esperados.

Echemos un vistazo a algunas de las excelentes funciones de esta versión.

Compilaciones paralelas de índices B-TREE

PostgreSQL 11 introdujo un parche de infraestructura para permitir la creación de índices paralelos.

Solo se puede usar con el índice B-Tree por ahora.

Crear un índice B-Tree paralelo es dos o tres veces más rápido que hacer lo mismo sin trabajar en paralelo (o compilación en serie).

En PostgreSQL 11, la creación de índices paralelos está activada de forma predeterminada.

Hay dos parámetros importantes:

  • max_parallel_workers:establece el número máximo de trabajadores que el sistema puede admitir para consultas paralelas.
  • max_parallel_maintenance_workers:controla la cantidad máxima de procesos de trabajo que se pueden usar para CREAR ÍNDICE.

Comprobémoslo con un ejemplo:

severalnines=# CREATE TABLE test_btree AS SELECT generate_series(1,100000000) AS id;
SELECT 100000000
severalnines=#  SET maintenance_work_mem = '1GB';
severalnines=# \timing
severalnines=#  CREATE INDEX q ON test_btree (id);
TIME: 25294.185 ms (00:25.294)

Intentémoslo con un trabajo en paralelo de 8 vías:

severalnines=# SET maintenance_work_mem = '2GB';
severalnines=# SET max_parallel_workers = 16;
severalnines=# SET max_parallel_maintenance_workers = 8;
severalnines=# \timing
severalnines=# CREATE INDEX q1 ON test_btree (id);
TIME: 11001.240 ms (00:11.001)

Podemos ver la diferencia de rendimiento con el trabajador paralelo, más del 60% de rendimiento con solo un pequeño cambio. Maintenance_work_mem también se puede aumentar para obtener más rendimiento.

La mesa ALTER también ayuda a aumentar los trabajadores paralelos. La siguiente sintaxis se puede usar para aumentar los trabajadores paralelos junto con max_parallel_maintenance_workers. Esto pasa por alto el modelo de costos por completo.

ALTER TABLE test_btree SET (parallel_workers = 24);

Sugerencia:RESTABLECER a los valores predeterminados una vez que se complete la creación del índice para evitar un plan de consulta adverso.

CREATE INDEX con la opción CONCURRENTLY admite compilaciones paralelas sin restricciones especiales, solo el primer escaneo de la tabla se realiza en paralelo.

Se pueden encontrar pruebas de rendimiento más profundas aquí.

Agregar bloqueo de predicado para índices hash, gist y Gin

PostgreSQL 11 se envió con soporte de bloqueo de predicado para índices hash, índices gin e índices gist. Esto hará que el aislamiento de transacciones SERIALIZABLE sea mucho más eficiente con esos índices.

Ventaja:el bloqueo de predicado puede proporcionar un mejor rendimiento en el nivel de aislamiento serializable al reducir la cantidad de falsos positivos que conducen a una falla de serialización innecesaria.

En PostgreSQL 10, el rango de bloqueo es la relación, pero en PostgreSQL 11 se encuentra que el bloqueo es solo de página.

Vamos a probarlo.

severalnines=# CREATE TABLE sv_predicate_lock1(c1 INT, c2 VARCHAR(10)) ;
CREATE TABLE
severalnines=# CREATE INDEX idx1_sv_predicate_lock1 ON sv_predicate_lock1 USING 'hash(c1) ;
CREATE INDEX
severalnines=# INSERT INTO sv_predicate_lock1 VALUES (generate_series(1, 100000),  'puja') ;
INSERT 0 100000
severalnines=#  BEGIN ISOLATION LEVEL SERIALIZABLE ;
BEGIN
severalnines=# SELECT * FROM sv_predicate_lock1 WHERE c1=10000 FOR UPDATE ;
  c1   |  c2
-------+-------
 10000 | puja
(1 row)

Como podemos ver a continuación, el bloqueo está a nivel de página en lugar de relación. En PostgreSQL 10 estaba en el nivel de relación, por lo que es una GRAN GANANCIA para transacciones simultáneas en PostgreSQL 11.

severalnines=# SELECT locktype, relation::regclass, mode FROM pg_locks ;
   locktype    |        relation         |      mode
---------------+-------------------------+-----------------
 relation      | pg_locks                | AccessShareLock
 relation      | idx1_sv_predicate_lock1 | AccessShareLock
 relation      | sv_predicate_lock1      | RowShareLock
 virtualxid    |                         | ExclusiveLock
 transactionid |                         | ExclusiveLock
 page          | idx1_sv_predicate_lock1 | SIReadLock
 tuple         | sv_predicate_lock1      | SIReadLock
(7 rows)

Sugerencia:una exploración secuencial siempre necesitará un bloqueo de predicado a nivel de relación. Esto puede resultar en una mayor tasa de errores de serialización. Puede ser útil fomentar el uso de escaneos de índice al reducir el costo_de_la_página_aleatoria y/o al aumentar el_costo_tuple_de_la_cpu.

Permitir actualizaciones HOT para algunos índices de expresión

La función Heap Only Tuple (HOT) elimina las entradas de índice redundantes y permite la reutilización del espacio ocupado por tuplas ELIMINADAS o ACTUALIZADAS obsoletas sin realizar un vacío en toda la tabla. Reduce el tamaño del índice al evitar la creación de entradas de índice con claves idénticas.

Si el valor de una expresión de índice no cambia después de la ACTUALIZACIÓN, permita actualizaciones HOT donde PostgreSQL no las permitió anteriormente, lo que brinda un aumento significativo del rendimiento en esos casos.

Esto es especialmente útil para índices como JSON->>campo donde el valor JSON cambia pero el valor indexado no.

Esta característica se revirtió en 11.1 debido a la degradación del rendimiento (AT Free BSD solo según Simon), se pueden encontrar más detalles / puntos de referencia aquí. Esto debería corregirse en una versión futura.

Permitir escanear páginas de índice hash completas

Índice hash:el planificador de consultas considerará usar un índice hash cada vez que una columna indexada esté involucrada en una comparación usando el operador =. Tampoco era seguro contra fallas (no se inició sesión en WAL), por lo que debe reconstruirse después de que la base de datos se bloquee, y los cambios en el hash no se escribieron a través de la replicación de transmisión.

En PostgreSQL 10, el índice hash se registró en WAL, lo que significa que es seguro contra CRASH y se puede replicar. Los índices hash utilizan mucho menos espacio en comparación con B-Tree, por lo que caben mejor en la memoria.

En PostgreSQL 11, los índices Btree tienen una optimización llamada "vacío de una sola página", que elimina de manera oportunista los punteros de índice muertos de las páginas de índice, evitando una gran cantidad de aumento del índice, que de otro modo ocurriría. La misma lógica se ha portado a los índices Hash. Acelera el reciclaje del espacio, reduciendo la hinchazón.

ESTADÍSTICAS del índice de funciones

Ahora es posible especificar un valor de ESTADÍSTICAS para una columna de índice de función. Es muy valioso para la eficiencia de una aplicación especializada. Ahora podemos recopilar estadísticas en columnas de expresión, que ayudarán al planificador a tomar una decisión más precisa.

severalnines=# CREATE INDEX idx1_stats ON stat ((s1 + s2)) ;
CREATE INDEX
severalnines=# ALTER INDEX idx1_stats ALTER COLUMN 1 SET STATISTICS 1000 ;
ALTER INDEX
severalnines=# \d+ idx1_stats
 Index "public.idx1_stats"
Column | Type | Definition | Storage | Stats target
--------+---------+------------+---------+--------------
expr | numeric | (c1 + c2) | main | 1000
btree, for table "public.stat1"

comprobar

Se ha agregado un nuevo módulo Contrib amcheck. Solo se pueden verificar los índices B-Tree.

¡Vamos a probarlo!

severalnines=# CREATE EXTENSION amcheck ;
CREATE EXTENSION
severalnines=# SELECT bt_index_check('idx1_stats') ;
ERROR: invalid page in block 0 of relation base/16385/16580
severalnines=#CREATE INDEX idx1_hash_data1 ON data1 USING hash (c1) ;
CREATE INDEX
severalnines=# SELECT bt_index_check('idx1_hash_data1') ;
ERROR: only B-Tree indexes are supported as targets for verification
DETAIL: Relation "idx1_hash_data1" is not a B-Tree index.

Índice particionado local posible

Antes de PostgreSQL11, no era posible crear un índice en una tabla secundaria o en una tabla particionada.

En PostgreSQL 11, cuando CREATE INDEX se ejecuta en una tabla particionada/tabla principal, crea entradas de catálogo para un índice en la tabla particionada y se conecta en cascada para crear índices reales en las particiones existentes. También los creará en futuras particiones.

Intentemos crear una tabla principal y particionarla:

severalnines=# create table test_part ( a int, list varchar(5) ) partition by list (list);
CREATE TABLE
severalnines=# create table part_1 partition of test_part for values in ('India');
CREATE TABLE
severalnines=# create table part_2 partition of test_part for values in ('USA');
CREATE TABLE
severalnines=#
severalnines=# \d+ test_part
                                        Table "public.test_part"
 Column |         Type         | Collation | Nullable | Default | Storage  | Stats target | Description
--------+----------------------+-----------+----------+---------+----------+--------------+-------------
 a      | integer              |           |          |         | plain    |              |
 list   | character varying(5) |           |          |         | extended |              |
Partition key: LIST (list)
Partitions: part_1 FOR VALUES IN ('India'),
            part_2 FOR VALUES IN ('USA')

Intentemos crear un índice en la tabla principal:

severalnines=# create index i_test on test_part (a);
CREATE INDEX
severalnines=# \d part_2
                     Table "public.part_2"
 Column |         Type         | Collation | Nullable | Default
--------+----------------------+-----------+----------+---------
 a      | integer              |           |          |
 list   | character varying(5) |           |          |
Partition of: test_part FOR VALUES IN ('USA')
Indexes:
    "part_2_a_idx" btree (a)

severalnines=# \d part_1
                     Table "public.part_1"
 Column |         Type         | Collation | Nullable | Default
--------+----------------------+-----------+----------+---------
 a      | integer              |           |          |
 list   | character varying(5) |           |          |
Partition of: test_part FOR VALUES IN ('India')
Indexes:
    "part_1_a_idx" btree (a)

El índice se distribuye en cascada a todas las particiones en PostgreSQL 11, lo cual es una función realmente interesante.

Índice de cobertura (incluir CLÁUSULA para índices)

Se puede especificar una cláusula INCLUDE para agregar columnas al índice. Esto es efectivo cuando se agregan columnas que no están relacionadas con una restricción única de un índice único. Las columnas INCLUDE existen únicamente para permitir que más consultas se beneficien de los análisis de solo índice. Solo los índices B-tree admiten la cláusula INCLUDE por ahora.

Verifiquemos el comportamiento sin INCLUDE. No usará el escaneo de índice solamente si aparecen columnas adicionales en SELECCIONAR. Esto se puede lograr usando la cláusula INCLUDE.

severalnines=# CREATE TABLE no_include (a int, b int, c int);
CREATE TABLE
severalnines=# INSERT INTO no_include SELECT 3 * val, 3 * val + 1, 3 * val + 2 FROM generate_series(0, 1000000) as val;
INSERT 0 1000001
severalnines=# CREATE UNIQUE INDEX old_unique_idx ON no_include(a, b);
CREATE INDEX
severalnines=# VACUUM ANALYZE;
VACUUM
EXPLAIN ANALYZE SELECT a, b FROM no_include WHERE a < 1000;  - It will do index only scan 
EXPLAIN ANALYZE SELECT a, b, c FROM no_include WHERE a < 1000; - It will not do index only scan as we have extra column in select. 
severalnines=# CREATE INDEX old_idx ON no_include (a, b, c);
CREATE INDEX
severalnines=# VACUUM ANALYZE;
VACUUM
severalnines=# EXPLAIN ANALYZE SELECT a, b, c FROM no_include WHERE a < 1000;   - It did index only scan as index on all three columns. 
                     QUERY PLAN
-------------------------------------------------
 Index Only Scan using old_idx on no_include
     (cost=0.42..14.92 rows=371 width=12)
     (actual time=0.086..0.291 rows=334 loops=1)
   Index Cond: (a < 1000)
   Heap Fetches: 0
 Planning Time: 2.108 ms
 Execution Time: 0.396 ms
(5 rows)

Probemos con la cláusula include. En el siguiente ejemplo, la RESTRICCIÓN ÚNICA se crea en las columnas a y b, pero el índice incluye una columna c.

severalnines=# CREATE TABLE with_include (a int, b int, c int);
CREATE TABLE
severalnines=# INSERT INTO with_include SELECT 3 * val, 3 * val + 1, 3 * val + 2 FROM generate_series(0, 1000000) as val;
INSERT 0 1000001
severalnines=# CREATE UNIQUE INDEX new_unique_idx ON with_include(a, b) INCLUDE (c);
CREATE INDEX
severalnines=# VACUUM ANALYZE;
VACUUM
severalnines=#  EXPLAIN ANALYZE SELECT a, b, c FROM with_include WHERE a < 10000;
                       QUERY PLAN
-----------------------------------------------------
 Index Only Scan using new_unique_idx on with_include
     (cost=0.42..116.06 rows=3408 width=12)
     (actual time=0.085..2.348 rows=3334 loops=1)
   Index Cond: (a < 10000)
   Heap Fetches: 0
 Planning Time: 1.851 ms
 Execution Time: 2.840 ms
(5 rows)

No puede haber ninguna superposición entre las columnas de la lista de columnas principal y las de la lista de inclusión

severalnines=# CREATE UNIQUE INDEX new_unique_idx ON with_include(a, b) INCLUDE (a);
ERROR:  42P17: included columns must not intersect with key columns
LOCATION:  DefineIndex, indexcmds.c:373

Una columna utilizada con una expresión en la lista principal funciona:

severalnines=# CREATE UNIQUE INDEX new_unique_idx_2 ON with_include(round(a), b) INCLUDE (a);
CREATE INDEX

Las expresiones no se pueden usar en una lista de inclusión porque no se pueden usar en un escaneo de solo índice:

severalnines=# CREATE UNIQUE INDEX new_unique_idx_2 ON with_include(a, b) INCLUDE (round(c));
ERROR:  0A000: expressions are not supported in included columns
LOCATION:  ComputeIndexAttrs, indexcmds.c:1446

Conclusión

Las nuevas características de PostgreSQL seguramente mejorarán la vida de los administradores de bases de datos, por lo que se convertirá en una sólida opción alternativa en la base de datos de código abierto. Entiendo que algunas características de los índices están actualmente limitadas a B-Tree, sigue siendo un gran comienzo de la era de ejecución paralela para PostgreSQL y se dirige a una buena herramienta para mirar de cerca. ¡Gracias!