Aller au contenu

pg_hint_plan

Dalibo recommande une utilisation restreinte de pg_hint_plan.

Warning

Dans le cadre de notre offre de support, l’utilisation de pg_hint_plan est limitée : seuls les hints de type GUC sont admis dans le cadre des contrats de support Dalibo, sauf accord explicite de Dalibo pour des cas spécifiques clairement identifiés.

Les hints de type GUC permettent de positionner des paramètres comme join_collapse_limit ou work_mem pour une requête, soit en ajoutant un commentaire au texte de la requête, soit en insérant une ligne dans la table hint_plan.hints, les requêtes étant alors identifiées par leur query_id. C’est cette dernière méthode qui nous intéresse plus particulièrement ici, puisqu’elle a l’avantage de ne pas avoir à modifier le code applicatif. Pour l’autre méthode, se référer à la documentation officielle.

Les autres types de hints sont considérés comme une mauvaise pratique par Dalibo, et ne sont considérés qu’en toute dernière extrémité. Voir aussi cette page sur l’utilisation des hints en général.

Installation

Cette extension est empaquetée pour Debian, RHEL, et leurs dérivées. Pour installer la version adaptée à PostgreSQL 17 :

apt install postgresql-17-pg-hint-plan

ou, pour les dérivées RHEL :

dnf install pg_hint_plan_17

Configuration

La configuration suivante doit être ajoutée :

compute_query_id = on
shared_preload_libraries = 'pg_hint_plan'
pg_hint_plan.enable_hint_table = on

Il sera nécessaire de redémarrer l’instance après.

Ensuite, pour chaque base en ayant besoin :

CREATE EXTENSION pg_hint_plan ;

Paramétrage spécifique à une requête

Nous créons une base pgbench pour l’exemple :

createdb bench
pgbench -i -s10 bench

La requête suivante utilise un Parallel Index Only Scan avec deux workers :

EXPLAIN (SETTINGS, VERBOSE, COSTS OFF) SELECT aid FROM pgbench_accounts WHERE aid % 5 = 0;
                                      QUERY PLAN                                       
---------------------------------------------------------------------------------------
 Gather
   Output: aid
   Workers Planned: 2
   ->  Parallel Index Only Scan using pgbench_accounts_pkey on public.pgbench_accounts
         Output: aid
         Filter: ((pgbench_accounts.aid % 5) = 0)
 Query Identifier: 23581355037094665

L’option VERBOSE de EXPLAIN permet de récupérer le query_id (dernière ligne).

Nous souhaitons n’autoriser qu’un seul worker pour cette requête. Cela se fait de la façon suivante :

INSERT INTO hint_plan.hints (query_id, application_name, hints)
    VALUES (23581355037094665, '', 'Set(max_parallel_workers_per_gather 1)');

Nous voyons que cela fonctionne :

EXPLAIN (SETTINGS, VERBOSE, COSTS OFF) SELECT aid FROM pgbench_accounts WHERE aid % 5 = 0;
                                      QUERY PLAN                                       
---------------------------------------------------------------------------------------
 Gather
   Output: aid
   Workers Planned: 1
   ->  Parallel Index Only Scan using pgbench_accounts_pkey on public.pgbench_accounts
         Output: aid
         Filter: ((pgbench_accounts.aid % 5) = 0)
 Query Identifier: 23581355037094665

Notez néanmoins que l’option SETTINGS n’affiche pas le paramétrage spécifique. C’est une limitation.

Pour cette même requête, nous souhaitons maintenant augmenter la valeur de random_page_cost, car les performances de notre stockage cloud sont très mauvaises pour les accès non-séquentiels :

UPDATE hint_plan.hints SET hints = 'Set(max_parallel_workers_per_gather 1) Set(random_page_cost 8)'
    WHERE query_id = 23581355037094665;

On voit que ça fonctionne, il y a toujours un seul worker, et on passe sur un Parallel Seq Scan :

EXPLAIN (SETTINGS, VERBOSE, COSTS OFF) SELECT aid FROM pgbench_accounts WHERE aid % 5 = 0;
                     QUERY PLAN                     
----------------------------------------------------
 Gather
   Output: aid
   Workers Planned: 1
   ->  Parallel Seq Scan on public.pgbench_accounts
         Output: aid
         Filter: ((pgbench_accounts.aid % 5) = 0)
 Query Identifier: 23581355037094665

Bonnes pratiques

Il est important de documenter l’utilisation de pg_hint_plan. Pour chaque nouvelle ligne ajoutée à la table hint_plan.hints, il faut (idéalement) documenter la date d’ajout, le texte de la requête associée au query_id, et les raisons qui expliquent le choix du hint.

En l’absence de documentation, pg_stat_statements permettra (si la requête est suffisamment fréquente pour ne pas avoir été évincée) de retrouver le texte normalisé de la requête, ce qui est déjà ça. Quoi qu’il en soit, nous recommandons de toujours laisser pg_stat_statements dans shared_preload_libraries, quitte à désactiver la collecte des métriques en positionnant le paramètre pg_stat_statements.track à off si l’on souhaite éviter tout risque de surcharge d’une instance de production. Réactiver la collecte peut alors se faire à chaud, sans avoir à redémarrer l’instance.

Avec pg_stat_statements chargée, il n’est pas nécessaire de positionner compute_query_id, la configuration minimale sera alors la suivante :

shared_preload_libraries = 'pg_hint_plan,pg_stat_statements'
pg_hint_plan.enable_hint_table = on