PostgreSQL
 sql >> Base de données >  >> RDS >> PostgreSQL

Un aperçu des changements d'index dans PostgreSQL 11

La bonne application des index peut rendre les requêtes extrêmement rapides.

Les index utilisent des pointeurs pour accéder rapidement aux pages de données.

Des changements majeurs se sont produits sur les index dans PostgreSQL 11, de nombreux correctifs très attendus ont été publiés.

Jetons un coup d'œil à certaines des fonctionnalités intéressantes de cette version.

Constructions d'index B-TREE parallèles

PostgreSQL 11 a introduit un correctif d'infrastructure pour permettre la création d'index parallèles.

Il ne peut être utilisé qu'avec l'index B-Tree pour le moment.

Construire un index B-Tree parallèle est deux à trois fois plus rapide que de faire la même chose sans travail en parallèle (ou construction en série).

Dans PostgreSQL 11, la création d'index parallèles est activée par défaut.

Il y a deux paramètres importants :

  • max_parallel_workers :définit le nombre maximal de nœuds de calcul que le système peut prendre en charge pour les requêtes parallèles.
  • max_parallel_maintenance_workers :contrôle le nombre maximal de processus de travail pouvant être utilisés pour CREATE INDEX.

Vérifions-le avec un exemple :

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)

Essayons avec un travail parallèle à 8 voies :

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)

Nous pouvons voir la différence de performance avec le travailleur parallèle, plus de 60% performant avec juste un petit changement. Le maintenance_work_mem peut également être augmenté pour obtenir plus de performances.

La table ALTER permet également d'augmenter les travailleurs parallèles. La syntaxe ci-dessous peut être utilisée pour augmenter les travailleurs parallèles avec max_parallel_maintenance_workers. Cela contourne complètement le modèle de coût.

ALTER TABLE test_btree SET (parallel_workers = 24);

Conseil :RÉINITIALISEZ par défaut une fois la construction de l'index terminée pour éviter un plan de requête indésirable.

CREATE INDEX avec l'option CONCURRENTLY prend en charge les constructions parallèles sans restrictions particulières, seule la première analyse de table est réellement effectuée en parallèle.

Des tests de performances plus approfondis sont disponibles ici.

Ajouter un verrouillage de prédicat pour les index de hachage, Gist et Gin

PostgreSQL 11 est livré avec la prise en charge du verrouillage de prédicat pour les index de hachage, les index gin et les index gist. Cela rendra l'isolation des transactions SERIALIZABLE beaucoup plus efficace avec ces index.

Avantage :le verrouillage de prédicat peut fournir de meilleures performances au niveau de l'isolation sérialisable en réduisant le nombre de faux positifs, ce qui entraîne des échecs de sérialisation inutiles.

Dans PostgreSQL 10, la plage de verrouillage est la relation, mais dans PostgreSQL 11, le verrou se trouve uniquement sur la page.

Testons-le.

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)

Comme nous pouvons le voir ci-dessous, le verrou est au niveau de la page au lieu de la relation. Dans PostgreSQL 10, c'était au niveau de la relation, c'est donc un GRAND GAGNANT pour les transactions simultanées dans 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)

Conseil :Une analyse séquentielle nécessitera toujours un verrou de prédicat au niveau de la relation. Cela peut entraîner une augmentation du taux d'échecs de sérialisation. Il peut être utile d'encourager l'utilisation des analyses d'index en réduisant random_page_cost et/ou en augmentant cpu_tuple_cost.

Autoriser les mises à jour HOT pour certains index d'expression

La fonctionnalité Heap Only Tuple (HOT) élimine les entrées d'index redondantes et permet la réutilisation de l'espace occupé par les tuples DELETed ou UPDATE obsolètes sans effectuer de vide à l'échelle de la table. Il réduit la taille de l'index en évitant la création d'entrées d'index à clé identique.

Si la valeur d'une expression d'index reste inchangée après UPDATE, autorisez les mises à jour HOT là où PostgreSQL les interdisait auparavant, ce qui améliore considérablement les performances dans ces cas.

Ceci est particulièrement utile pour les index tels que le champ JSON->> où la valeur JSON change mais pas la valeur indexée.

Cette fonctionnalité a été annulée dans 11.1 en raison d'une dégradation des performances (AT Free BSD uniquement selon Simon), plus de détails / benchmark peuvent être trouvés ici. Cela devrait être corrigé dans une prochaine version.

Autoriser l'analyse de pages d'index de hachage entières

Index de hachage :le planificateur de requêtes envisagera d'utiliser un index de hachage chaque fois qu'une colonne indexée est impliquée dans une comparaison à l'aide de l'opérateur =. Il n'était pas non plus sécurisé en cas de crash (non connecté à WAL), il doit donc être reconstruit après un crash de la base de données, et les modifications apportées au hachage n'ont pas été écrites via la réplication en continu.

Dans PostgreSQL 10, l'index de hachage était journalisé WAL, ce qui signifie qu'il est sécurisé par CRASH et peut être répliqué. Les index de hachage utilisent beaucoup moins d'espace par rapport à B-Tree afin qu'ils puissent mieux s'adapter à la mémoire.

Dans PostgreSQL 11, les index Btree ont une optimisation appelée "vide de page unique", qui supprime de manière opportuniste les pointeurs d'index morts des pages d'index, empêchant une énorme quantité de gonflement d'index, qui se produirait autrement. La même logique a été portée sur les index Hash. Il accélère le recyclage de l'espace, réduisant le ballonnement.

STATISTIQUES de l'index des fonctions

Il est désormais possible de spécifier une valeur STATISTICS pour une colonne d'index de fonction. C'est très précieux pour l'efficacité d'une application spécialisée. Nous pouvons désormais collecter des statistiques sur les colonnes d'expression, ce qui aidera le planificateur à prendre une décision plus précise.

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"

amcheck

Un nouveau module Contrib amcheck a été ajouté. Seuls les index B-Tree peuvent être vérifiés.

Testons-le !

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.

Possibilité d'un index partitionné local

Avant PostgreSQL11, il n'était pas possible de créer un index sur une table enfant ou une table partitionnée.

Dans PostgreSQL 11, lorsque CREATE INDEX est exécuté sur une table partitionnée / table parent, il crée des entrées de catalogue pour un index sur la table partitionnée et crée en cascade des index réels sur les partitions existantes. Il les créera également dans les partitions futures.

Essayons de créer une table parent et de la partitionner :

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

Essayons de créer un index sur la table parent :

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)

L'index est répercuté sur toutes les partitions dans PostgreSQL 11, ce qui est une fonctionnalité vraiment intéressante.

Index de couverture (inclure CLAUSE pour les index)

Une clause INCLUDE pour ajouter des colonnes à l'index peut être spécifiée. Ceci est efficace lors de l'ajout de colonnes qui ne sont pas liées à une contrainte unique d'un index unique. Les colonnes INCLUDE existent uniquement pour permettre à davantage de requêtes de bénéficier d'analyses d'index uniquement. Seuls les index B-tree prennent en charge la clause INCLUDE pour le moment.

Vérifions le comportement sans INCLUDE. Il n'utilisera pas l'analyse d'index uniquement si des colonnes supplémentaires apparaissent dans le SELECT. Ceci peut être réalisé en utilisant la clause 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)

Essayons avec la clause include. Dans l'exemple ci-dessous, la CONTRAINTE UNIQUE est créée dans les colonnes a et b, mais l'index inclut une colonne 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)

Il ne peut pas y avoir de chevauchement entre les colonnes de la liste de colonnes principale et celles de la liste d'inclusion

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

Une colonne utilisée avec une expression dans la liste principale fonctionne :

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

Les expressions ne peuvent pas être utilisées dans une liste d'inclusion car elles ne peuvent pas être utilisées dans un parcours d'index uniquement :

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

Conclusion

Les nouvelles fonctionnalités de PostgreSQL amélioreront sûrement la vie des administrateurs de bases de données, il est donc en passe de devenir un choix alternatif solide dans les bases de données open source. Je comprends que quelques fonctionnalités d'index sont actuellement limitées à B-Tree, c'est toujours un bon début d'ère d'exécution parallèle pour PostgreSQL et se dirige vers un bel outil à regarder de près. Merci !