<!--

https://git.postgresql.org/gitweb/?p=postgresql.git;a=commit;h=8492feb98f6
Allow parallel CREATE INDEX for GIN indexes

Allow using parallel workers to build a GIN index, similarly to BTREE
and BRIN. For large tables this may result in significant speedup when
the build is CPU-bound.


The work is divided so that each worker builds index entries on a subset
of the table, determined by the regular parallel scan used to read the
data. Each worker uses a local tuplesort to sort and merge the entries
for the same key. The TID lists do not overlap (for a given key), which
means the merge sort simply concatenates the two lists. The merged
entries are written into a shared tuplesort for the leader.

The leader needs to merge the sorted entries again, before writing them
into the index. But this way a significant part of the work happens in
the workers, and the leader is left with merging fewer large entries,
which is more efficient.

Most of the parallelism infrastructure is a simplified copy of the code
used by BTREE indexes, omitting the parts irrelevant for GIN indexes
(e.g. uniqueness checks).

-->

<!-- Doc : https://www.postgresql.org/docs/current/sql-createindex.html
PostgreSQL can build indexes while leveraging multiple CPUs in order to process the table rows faster. This feature is known as parallel index build. For index methods that support building indexes in parallel (currently, B-tree, GIN, and BRIN), maintenance_work_mem specifies the maximum amount of memory that can be used by each index build operation as a whole, regardless of how many worker processes were started. Generally, a cost model automatically determines how many worker processes should be requested, if any.

Parallel index builds may benefit from increasing maintenance_work_mem where an equivalent serial index build will see little or no benefit. Note that maintenance_work_mem may influence the number of worker processes requested, since parallel workers must have at least a 32MB share of the total maintenance_work_mem budget. There must also be a remaining 32MB share for the leader process. Increasing max_parallel_maintenance_workers may allow more workers to be used, which will reduce the time needed for index creation, so long as the index build is not already I/O bound. Of course, there should also be sufficient CPU capacity that would otherwise lie idle.
-->

<div class="slide-content">

  * Parallélisation de la construction des index GIN
    + enfin !
  * `max_parallel_maintenance_workers` = 2 (défaut)
    + on peut augmenter un peu, gain non linéaire.

</div>

<div class="notes">

Après les index B-tree depuis PostgreSQL 11, les index BRIN depuis PostgreSQL 17,
PostgreSQL 18 sait paralléliser la construction d'index GIN.

Comme pour les index B-tree, la mémoire utilisable `maintenance_work_mem`
est partagée entre les workers (sinon il y a tri sur disque),
et le nombre de processus annexes est géré par
`max_parallel_maintenance_workers` (défaut : 2).

Augmenter le paramètre `max_parallel_maintenance_workers` implique souvent
de monter `max_parallel_workers` (défaut 8), voire `max_worker_processes`
(défaut 8), et, bien sûr, d'avoir une machine avec au moins autant de processus.
`maintenance_work_mem` doit suffire à attribuer 32 Mo à chaque worker,
ce qui n'est généralement pas un souci sur les instances correctement
paramétrées.


**Exemple : index GIN d'un JSON** :

La base **personnes**
pèse en version complète 613 Mo, pour 2 Go sur disque au final. Elle peut être installée comme suit :

<!-- TP de S22 -->
```bash
curl -kL https://dali.bo/tp_personnes -o /tmp/personnes.dump
createdb --echo personnes
pg_restore -v -d personnes /tmp/personnes.dump
rm -- /tmp/personnes.dump 
```
La construction de l'index de 284 Mo sur le champ JSON se fait ainsi :
```sql
SET maintenance_work_mem TO '1GB';
SET max_parallel_maintenance_workers TO 2 ;
DROP INDEX pers_json_idx ;
CREATE INDEX pers_json_idx ON json.personnes USING gin (personne jsonb_path_ops);
```


| `max_parallel_maintenance_workers`  |  Temps de calcul  |
|-----------------------------------+-------------------|
| 0 (pas de parallélisation)        |  27,3 s           |
| 2 (défaut)                        |  17,2 s           |
| 4                                 |  15,1 s           |
| 6                                 |  15,1 s           |

Le gain est très appréciable, mais il n'est pas linéaire
avec le nombre de workers, car toutes les étapes de création
de l'index ne sont pas parallélisables.
Monter `max_parallel_maintenance_workers`
très haut n'a d'intérêt que pour les plus grosses tables.

<!--

FIXME : quelle est la règle pour le nb de workers // ??

-->


**Exemple : index GIN pour pg_trgm** :

Dans certains cas, on hésite entre des index GIN et GiST (par exemple
pour utiliser [pg_trgm](https://dali.bo/x2_html#pg_trgm)).
Les index GiST sont censés être plus légers à maintenir,
mais la parallélisation fait pencher la balance un peu plus vers le GIN.

<!-- https://public.dalibo.com/exports/formation/manuels/modules/x2/x2.handout.html#travaux-pratiques-solutions -->
Cet exemple utilise la base **gutenberg** contenant de nombreux livres
(21 millions de lignes pour 3 Go sur le disque) :
```bash
curl -kL https://dali.bo/tp_gutenberg -o /tmp/gutenberg.dmp
# version réduite :
# curl -kL https://dali.bo/tp_gutenberg10 -o /tmp/gutenberg.dmp
createdb gutenberg
pg_restore -d gutenberg /tmp/gutenberg.dmp
rm -- /tmp/gutenberg.dmp
```
La construction de l'index GIN (taille : 1,3 Go)
pour l'indexation `pg_trgm` s'effectue ainsi :
```sql
SET max_parallel_maintenance_workers TO 2 ;
DROP INDEX idx_textes_trgm ;
CREATE INDEX idx_textes_trgm ON textes USING gin (contenu gin_trgm_ops);
\di+ idx_textes_trgm
```

| `max_parallel_maintenance_workers`  |  Temps de calcul |
|-----------------------------------+-------------------|
| 0 (pas de parallélisation)        | 6 min 45 s        |
| 2 (défaut)                        | 3 min 04 s        | 
| 4                                 | 2 min 29 s        |
| 6                                 | 2 min 06 s        |
| 8                                 | 2 min 05 s        |

On peut donc diviser le temps de calcul par 2 ou 3,
mais ensuite un plafond est relativement vite atteint.

Pour comparaison, la création d'un index GiST, non parallélisable,
prend 11 minutes !

</div>

