Image de couverture : RegreSQL 2.0 : quand votre base de dev vous ment sur les performances
Bases de données

RegreSQL 2.0 : quand votre base de dev vous ment sur les performances

15 juillet 2026
5 min de lecture
7 vues
Sébastien Muler

Un index supprimé, trois jours de silence, puis le timeout

Tous les tests étaient verts. Le code n'avait pas bougé d'un caractère. Et pourtant, trois jours après une migration de nettoyage anodine, l'API commençait à timeouté sur une requête parfaitement inchangée. Le planificateur PostgreSQL avait silencieusement basculé d'un index scan vers un sequential scan — et personne ne l'avait vu venir. C'est exactement le scénario que RegreSQL 2.0 cherche à éliminer.

Le problème : tester les lignes ne suffit pas

La version 1.0 de RegreSQL fonctionnait sur un principe simple et solide : exécuter vos requêtes SQL, comparer les résultats à un fichier de référence versionné, et lever une alerte dès que les données retournées changent. C'est utile. Mais c'est insuffisant.

Ce que ce modèle ne détecte pas, c'est la requête qui retourne les bonnes lignes de la mauvaise façon. Une requête peut produire un résultat identique tout en passant d'un index scan à un sequential scan sur une table qui grossit chaque jour. Les lignes sont correctes. Le plan d'exécution, lui, est devenu catastrophique.

-- orders-by-customer.sql
select id, total from orders
where customer_id = :cid
order by id;

Supprimez l'index sur customer_id et relancez les tests RegreSQL 1.0 : tout passe au vert. Supprimez ce même index avec RegreSQL 2.0 activé en mode plan :

$ regresql test
✓ 1 passing
✗ 1 failing

FAILING: orders-by-customer.1.buffers (3898 > 109 * 102%, +3476.1%)
Expected buffers: 109
Actual buffers:   3898 (+3476.1%)

La régression de performance est capturée immédiatement, avant tout déploiement.

La base de dev est une menteuse

Mais il y a un problème encore plus profond, et RegreSQL 2.0 s'y attaque frontalement : votre base de développement ne ressemble pas à votre base de production.

En dev, les tables sont quasi vides. Les statistiques collectées par PostgreSQL (pg_statistic) reflètent quelques centaines de lignes, pas des millions. Or, le planificateur de requêtes PostgreSQL base entièrement ses décisions sur ces statistiques : seuils de sélectivité, coûts comparés d'un index scan vs un seq scan, choix des algorithmes de jointure.

Conséquence directe : une requête peut être planifiée de façon optimale en dev (index scan, nested loop) et catastrophiquement en production (seq scan, hash join) — non pas parce que la requête a changé, mais parce que le volume de données change radicalement les arbitrages du planificateur.

RegreSQL 2.0 résout ce problème en permettant de capturer les statistiques réelles de production et de les injecter dans l'environnement de test via pg_restore_relation_stats(). Votre base de dev reste légère, mais le planificateur raisonne comme s'il était en production.

# Capture des stats de production
regresql stats export --env production > prod-stats.json

# Injection dans l'environnement de CI
regresql stats import prod-stats.json

C'est un changement de paradigme : on ne teste plus le plan sur des données fictives, on teste le plan sur la réalité statistique de production.

Ce que RegreSQL 2.0 surveille concrètement

Au-delà du nombre de buffers, RegreSQL 2.0 versionne et compare plusieurs métriques clés du plan d'exécution :

  • Buffers lus (shared_buffers hit/read) : indicateur direct de la charge I/O
  • Type de scan : index scan vs bitmap scan vs sequential scan
  • Méthode de jointure : nested loop, hash join, merge join
  • Coût estimé par le planificateur : utile pour détecter une dérive des statistiques

Chaque requête SQL de votre suite de tests génère un fichier .buffers et .plan versionné dans Git, aux côtés du fichier .expected habituel. Lors d'un regresql test, les métriques actuelles sont comparées aux métriques de référence avec un seuil de tolérance configurable (par défaut 2%).

Si un index disparaît, si une statistique dérive, si le planificateur change de stratégie — la CI devient rouge avant que le code ne parte en production.

Intégration CI/CD

L'intégration dans un pipeline existant reste minimale :

# .github/workflows/db-tests.yml
- name: Import production stats
  run: regresql stats import ./ci/prod-stats.json

- name: Run RegreSQL tests
  run: regresql test --plan --buffers-threshold 5%

Les fichiers de référence (.expected, .buffers, .plan) sont committés dans le dépôt. Toute régression produit une diff lisible dans la PR, exactement comme un test unitaire classique.

Conclusion : tester ce qui se passe vraiment

RegreSQL 2.0 change ce que signifie "vérifier une requête SQL". Vérifier qu'une requête retourne les bonnes lignes est nécessaire, mais pas suffisant. Vérifier qu'elle les retourne de la bonne façon, avec les statistiques qui correspondent à la réalité de production, c'est ce qui permet d'attraper les régressions de performance avant qu'elles ne se transforment en incidents.

Pour les projets PHP/Symfony qui s'appuient sur Doctrine et PostgreSQL, ce type de garde-fou au niveau de la CI est particulièrement précieux : les migrations Doctrine peuvent modifier des index de façon non évidente, et les tables grossissent progressivement jusqu'au jour où le planificateur change d'avis.

📖 Article source : The tests passed. The plan didn't. par Radim Marek — présentation détaillée de RegreSQL 2.0 et de ses mécanismes internes.

Partager cet article