Aller au contenu

JIT

JIT ou le Just-in-Time avec PostgreSQL

Le Just-in-Time (JIT) est activé par défaut depuis la version 12 de PostgreSQL sur les systèmes ayant LLVM de disponible. JIT a pour but de permettre un gain de performance pour certaines opérations internes (pour répondre à certaines requêtes : listes de sélections, agrégats, expression clause WHERE).

JIT étant directement lié à la librairie LLVM, ces performances peuvent varier d’une version d’une distribution Linux à l’autre (la librairie LLVM est souvent rattachée à la version majeure de la distribution).

Il est donc conseillé de mettre à jour la version majeure de votre distribution afin de bénéficier de la dernière version LLVM disponible.

Configuration et optimisation de JIT

Pour déterminer si la compilation JIT sera utilisée, le coût total estimé d’une requête est utilisé. Le coût estimé de la requête sera comparé à la configuration du paramètre jit_above_cost.

Attention

La décision d’utiliser le JIT ou pas se fait après que le plan soit décidé, si le coût calculé de la requête dépasse un certain seuil.

Si le coût est supérieur, une compilation JIT sera effectuée, mais deux autres paramètres supplémentaires seront utilisés afin de déterminer le niveau d’optimisation :

  • Si le coût estimé est plus important que la configuration de jit_inline_above_cost, les petites fonctions et opérateurs utilisés dans la requête seront intégrés ;
  • Si le coût est plus important que la valeur de jit_optimize_above_cost, des optimisations coûteuses seront appliquées pour améliorer le code généré.

Attention

Dans certains cas (hélas pas si rares) où la compilation JIT est trop coûteuse pour une requête, il est intéressant de la désactiver au niveau de la session avec SET jit = off;. Puis d’analyser l’exécution de cette requête avec EXPLAIN.

Chacune de ces deux options augmente la surcharge de la compilation JIT, mais peut réduire considérablement la durée d’exécution.

Le changement du comportement de déclenchement de JIT se fait donc au travers de ces trois paramètres :

  • jit_above_cost valeur possibles : -1, 0-DBL_MAX - toutes les requêtes avec un coût total plus élevé sont traitées par JIT, sans effectuer d’optimisation des parties coûteuses. Ceci résulte généralement déjà en des accélérations significatives si l’expression/déformation est un résulte généralement déjà en des accélérations significatives si l’évaluation des expressions ou la déformation des tuples (transformation du format de données utilisé sur disque a celui utilisé en mémoire) sont des goulot d’étranglement (en supprimant les branches dynamiques principalement).
  • jit_optimize_above_cost valeurs possibles : -1, 0-DBL_MAX - toutes les requêtes dont le coût total est plus élevé sont traitées en JIT, avec l’optimisation des parties coûteuses.
  • jit_inline_above_cost valeurs possibles : -1, 0-DBL_MAX - l’optimisation inlining (qui est la plus coûteuse en ressources) est essayée si la requête a un coût plus élevé que cette valeur.

Lorsque le coût total d’une requête est supérieur à ces limites, l’optimisation JIT est réalisée.

Or, la compilation JIT est intéressante pour des requêtes analytiques qui brassent beaucoup de lignes. Donc pour des requêtes très complexes avec un coût élevé mais un nombre raisonnable de lignes, le JIT peut se déclencher à cause des seuils de coût, alors son surcoût sera souvent supérieur au temps gagné.

Il est alors intéressant :

  • soit de désactiver par défaut la compilation JIT et de l’activer explicitement pour les requêtes analytiques ;
  • soit d’augmenter fortement les seuils de déclenchement de JIT (par exemple d’un facteur 10).

Pour éviter que de mauvaises estimations statistiques faussent le coût d’une requête et déclenchent une compilation JIT contre-productive, il faut s’assurer que les statistiques soient correctes pour que le coût soit représentatif de la réalité.

La désactivation peut se faire à plusieurs niveaux :

  • sur l’ensemble de l’instance (postgresql.conf ou ALTER SYSTEM) ;
  • dans une session (SET jit TO off;) ;
  • pour un utilisateur précis (ALTER ROLE utilisateur SET jit TO OFF;) ;
  • pour une base de données précise (ALTER DATABASE db SET jit TO OFF;) ;
  • pour un utilisateur dans une base de données (ALTER ROLE utilisateur IN DATABASE db SET jit TO OFF;).

Pour savoir si JIT est utilisé par une requête, EXPLAIN ANALYZE peut être utilisé ; si le JIT est utilisé, vous verrez dans le résultat :

 ... Planning Time: 16.592 ms JIT: Functions: 605 Options: Inlining false, Optimization false, Expressions true, Deforming true 
Timing: Generation 87.689 ms, Inlining 0.000 ms, Optimization 39.768 ms, Emission 630.735 ms, Total 758.193 ms 
Execution Time: 874.604 ms (535 rows)

Un autre exemple utilisant l’ensemble des optimisations possibles :

... JIT:   Functions: 638   Options: Inlining true, Optimization true, Expressions true, Deforming true   
Timing: Generation 93.785 ms, Inlining 56.288 ms, Optimization 2065.094 ms,         
Emission 1191.154 ms, Total 3406.321 ms Execution Time: 3501.602 ms%%```

Dans ce dernier exemple, on voit que les optimisations Inlining, Optimization ont été déclenchées.

Nous voyons dans Options le niveau de l’optimisation JIT appliqué et dans Timing le temps utilisé pour l’optimisation JIT.

Effet sur la mémoire

L’activation du JIT a un effet sur la mémoire utilisée sur votre serveur de base de données.

En effet, le fonctionnement du framework LLVM entraîne que la mémoire allouée à la compilation JIT est libérée à la fin de la requête (par lot). Si vous avez de nombreuses, longues et complexes requêtes utilisant JIT, vous pouvez le ressentir sur la mémoire utilisée (graphique mémoire en dents de scie). Dans des cas extrêmes, vous pouvez arriver à saturation de mémoire.

Ce peut être une autre raison pour le désactiver par défaut.

Recommandations générales lors pour la migration depuis les versions précédant la 12

  • Lors de la validation du serveur en version 12 (ou supérieure), soyez vigilant au comportement de JIT ;
  • Ajuster le comportement (augmentation forte des paramètres jit_above_cost, jit_optimize_above_cost et jit_inline_above_cost) afin que celui-ci ne soit pas utilisé pour les requêtes identifiées ;
  • Ou utiliser SET jit TO off; avant la requête identifiée et RESET jit; une fois celle-ci exécutée ;
  • Ou désactiver ou adapter les paramètres JIT pour les utilisateurs n’utilisant pas de requêtes de type analytique. Par exemple, monter les paramètres jit_optimize_above_cost et jit_inline_above_cost pour cet utilisateur, ou désactiver JIT pour cet utilisateur.

Observabilité et JIT

Depuis la version 15, l’extension pg_stat_statements agrège des statistiques sur JIT ce qui permet d’évaluer son utilisation. Lorsque ces statistiques sont historisées cela permet également de détecter des évolutions.

```text View “public.pg_stat_statements” Column | Type | Collation | Nullable | Default ------------------------+------------------+-----------+----------+--------- … jit_functions | bigint | | | jit_generation_time | double precision | | | jit_inlining_count | bigint | | | jit_inlining_time | double precision | | | jit_optimization_count | bigint | | | jit_optimization_time | double precision | | | jit_emission_count | bigint | | | jit_emission_time | double precision | | |