Références des nœuds

Module J6

Dalibo SCOP

25.09

5 septembre 2025

Sur ce document

Formation Module J6
Titre Références des nœuds
Révision 25.09
PDF https://dali.bo/j6_pdf
EPUB https://dali.bo/j6_epub
HTML https://dali.bo/j6_html
Slides https://dali.bo/j6_slides

Licence Creative Commons CC-BY-NC-SA

Cette formation est sous licence CC-BY-NC-SA. Vous êtes libre de la redistribuer et/ou modifier aux conditions suivantes :

  • Paternité
  • Pas d’utilisation commerciale
  • Partage des conditions initiales à l’identique

Marques déposées

PostgreSQL® Postgres® et le logo Slonik sont des marques déposées par PostgreSQL Community Association of Canada.

Versions de PostgreSQL couvertes

Ce document ne couvre que les versions supportées de PostgreSQL au moment de sa rédaction, soit les versions 13 à 17.

Référence sur les nœuds d’exécution

Introduction

  • Types de nœuds principaux
    • parcours de table (Seq Scan)
    • parcours d’index (Index Scan & variantes)
    • jointures (Nested Loop, Merge Join, Hash Join…)
    • opérateurs sur des ensembles (Append, Except, Intersect…)
  • Et quelques autres :
    • Sort, _*Aggregate_, Unique, Limit, Materialize, Memoize
  • Plusieurs variantes pour chacun

Les parcours

Un seq scan et deux index Scan à la base d’un plan

Principe des parcours

  • Rien en entrée
  • Renvoie un ensemble de données des tables
    • trié ou non
    • filtré ou non
  • Exemples :
    • parcours séquentiel d’une table, avec ou sans filtrage des enregistrements produits
    • parcours via un index, avec ou sans filtrage supplémentaire

Parcours de table

Seq Scan : principe

  • Parcours séquentiel de la table (Sequential Scan ou Seq Scan)
    • parallélisation possible (Parallel Seq Scan)
  • La table est lue entièrement
    • même si seulement quelques lignes satisfont la requête
    • sauf avec LIMIT sans ORDER BY
  • Séquentiellement, par bloc de 8 ko

Seq Scan : performances

  • Idéal pour :
    • petites tables
    • % de retour élevé des blocs
    • partitions entières
  • Contre-performant pour :
    • quelques lignes d’une grande table
  • Optimisation : synchronize_seqscans

Parcours d’index : Index Scan

Index Scan : principe

  • Parcours « aléatoire » de l’index
  • Pour chaque enregistrement correspondant à la recherche
    • accès direct au bloc de la table
    • vérification de la visibilité de la ligne
    • renvoie des lignes triées

Index Scan : performances

  • Intéressant pour :
    • les accès à peu de lignes dans la table
    • les tris (ORDER BY…)
  • Non adapté à :
    • récupération de trop nombreuses lignes
  • Coût en maintenance
  • Parallélisation (Parallel Index Scan)
    • B-Tree uniquement

Parcours d’index bitmap

Bitmap Scan : principe

  • Bitmap Index Scan / Bitmap Heap Scan
  • Réduire les allers-retours index ⇆ table
    • trouver les blocs de l’index
    • puis lecture des blocs de la table
  • Combinaison de plusieurs index en mémoire
    • nœud BitmapAnd
    • nœud BitmapOr
  • Index B-tree, GIN, BRIN…

Bitmap Scan : performances

  • Indiqué pour :
    • de nombreuses lignes
    • bonne corrélation
    • combinaison de critères IN, ANY
    • combinaison d’index AND/OR
  • Mais :
    • coût de démarrage important
    • pas intéressant avec LIMIT
  • Parallélisation possible
  • Sensible à :
    • work_mem (sinon lossy)
    • effective_cache_size
    • effective_io_concurrency

Parcours d’index seul : Index Only Scan

CREATE INDEX ON t1 (c1) ;

SELECT c1 FROM t1 WHERE c1 < 10 ;
  • Avant 9.2 : PostgreSQL devait lire l’index + la table
  • À présent : le planificateur utilise la Visibility Map
    • nœud Index Only Scan
    • B-tree
    • GiST, SP-GiST : selon opérateur

Index Only Scan : performances

  • Extrêmement performant
    • requêtes avec peu de colonnes
  • Conditions
    • VACUUM fréquent
    • …sinon Heap Fetches

Autres parcours

  • TID Scan
  • Function Scan
  • Values
  • Result

Jointures

  • Prend 2 ensembles de données en entrée
    • inner (interne)
    • outer (externe)
  • Et renvoie un seul ensemble de données
  • Nested Loop, Merge Join, Hash Join

Nested Loops

Nested Loops : principe

« Boucles imbriquées »

  • Ensemble externe parcouru une fois
  • Pour chaque ligne :
    • chercher les lignes dans la relation interne selon la condition de jointure
      • émettre ces lignes en résultat

Nested Loops : performances

  • Coût proportionnel à la taille des ensembles
  • Intérêts
    • démarrage rapide
    • pas besoin de mémoire
    • marche toujours
  • Inadapté à :
    • grands nombre de lignes
  • Index chaudement conseillé

Merge Join

Merge Join : principe

Jointure d’ensembles triés

  • Trier l’ensemble interne
  • Trier l’ensemble externe
  • Tant qu’il reste des lignes dans un des ensembles
    • lire les deux ensembles en parallèle
    • si condition de jointure émettre la ligne

Merge Join : performances

  • Intérêt
    • le plus rapide sur une grosse volumétrie
    • pas de besoin de mémoire (si tri déjà fait…)
  • Limites
    • uniquement les égalités
    • besoin de données triées (Sort ou index)
  • Produit un résultat trié

Hash Join

Hash Join : principe

Jointure par hachage

  • Hacher les lignes de l’ensemble interne
  • Pour chaque ligne de l’ensemble externe
    • hacher la ligne lue
    • comparer ce hachage aux lignes hachées de l’ensemble interne
    • si correspondance : émettre la ligne

Hash Join : performances

  • Avantages :
    • idéal pour joindre une grande table à une petite
  • Limites :
    • uniquement conditions avec égalité
    • coût de démarrage important
    • mémoire, volumétrie !
    • sensible aux mauvaises estimations
  • Paramétrage mémoire :
    • work_mem × hash_mem_multiplier
  • Parallélisable

Opérations ensemblistes

  • Entrée : un ou plusieurs ensembles de données
  • Sortie : un ensemble de données
  • Souvent des requêtes sur des tables partitionnées ou héritées
  • Exemples typiques
    • Append et MergeAppend
    • Intersect
    • Except

Append

  • Entrée :
    • plusieurs ensembles de données
  • Sortie :
    • non triée, non dédupliquée
  • Utilisation :
    • partitionnement, tables héritées
    • UNION ALL et des UNION
    • NB : UNION sans ALL élimine les doublons (lourd !)
  • Parallélisable

MergeAppend

  • Append avec optimisation du tri
  • Sortie triée
  • Utilisation :
    • UNION ALL, partitionnement/héritage
    • avec parcours triés
    • idéal avec LIMIT

Nœuds ensemblistes

  • Nœud HashSetOp Except
    • EXCEPT et EXCEPT ALL
  • Nœud HashSetOp Intersect
    • INTERSECT et INTERSECT ALL

Tris

  • Entrée :
    • un ensemble de données
  • Renvoie :
    • un ensemble trié
  • Nœuds
    • Sort,
    • Incremental Sort

Sort

  • Utilisé pour le ORDER BY
    • mais aussi DISTINCT, GROUP BY, UNION
    • souvent avant une jointure Merge Join
  • Gros délai de démarrage
  • Plusieurs méthodes :
    • quicksort (en mémoire)
    • top-N heapsort (en mémoire, si LIMIT)
    • external merge (sur disque)

Incremental Sort

  • Pour tri multicolonnes,
    • déjà partiellement triées
  • Idéal quand un index existe sur les premières colonnes du tri
    • ORDER BY, GROUP BY, UNION
    • DISTINCT (v16)
    • jointures de type Merge Join
  • Délai de démarrage réduit
  • Moins de mémoire
  • PostgreSQL 16

Agrégats

  • Entrée :
    • un ensemble de données
  • Renvoie :
    • un ensemble trié
  • Nœuds
    • Aggregate
    • HashAggregate
    • GroupAggregate

Aggregate

  • Pour un seul résultat
    • SELECT max(…), count(*), sum(…)…
    • plusieurs agrégats au besoin
  • Penser aux index pour accélérer
    • count(*)
    • max/min

HashAggregate

  • Hachage de chaque n-uplet de regroupement
  • Accès direct à chaque n-uplet pour appliquer la fonction d’agrégat
  • Typiquement : GROUP BY
  • Mémoire :
    • work_mem×hash_mem_multiplier

GroupAggregate

  • Pré-requis :
    • données déjà triées : Sort ou index
  • Parcours des données
    • regroupement du groupe précédent arrivé à une donnée différente
  • GROUPING SETS, ROLLUP, CUBE

MixedAggregate

  • Pour plusieurs niveaux d’agrégation
  • Utilise des tables de hachage
  • Typiquement : ROLLUP, CUBE

Autres nœuds

  • Exemples typiques
    • Unique
    • Limit
    • InitPlan, SubPlan

Unique

  • Pour DISTINCT
  • Entrée : données déjà triées
  • Renvoie : la donnée précédente une fois arrivé à une donnée différente
  • Résultat trié
  • Souvent remplacé par un agrégat

Limit

  • Limite le nombre de résultats renvoyés
  • LIMIT et OFFSET
  • Peut modifier complètement le plan
    • favorise les nœuds au coût de démarrage réduit (Seq Scan, Nested Loop)

Memoize

  • Cache de résultat
  • Utilisable par la table interne des Nested Loop
  • Utile si :
    • peu de valeurs distinctes dans l’ensemble interne
    • beaucoup de valeurs dans l’ensemble externe
    • peu de correspondance entre les deux ensembles
  • Paramètres : work_mem×hash_mem_multiplier
  • Sensible à ndistinct

Conclusion

  • De nombreux nœuds
  • Qu’il faut apprendre
  • Expérimentez !

Questions

EXPLAIN (VERBOSE)
SELECT * FROM questions ;