Bonnes pratiques PostgreSQL pour les applications web modernes
La plupart des développeurs choisissent PostgreSQL parce qu’il est gratuit, fiable et éprouvé. Mais le choisir est la partie facile. Le véritable défi consiste à bien l’utiliser — concevoir des schémas qui résistent aux évolutions des exigences, écrire des requêtes qui restent rapides à mesure que les données augmentent, et gérer les connexions d’une manière qui ne dégrade pas silencieusement les performances de votre application.
Cet article couvre les bonnes pratiques PostgreSQL pour les applications web qui comptent le plus en production.
Points clés à retenir
- Utilisez des colonnes typées avec contraintes pour les champs sur lesquels vous filtrez, triez ou effectuez des jointures, et réservez JSONB pour les données évolutives ou non structurées.
- Choisissez les index délibérément — B-tree pour les requêtes d’égalité et de plage, GIN pour JSONB et la recherche plein texte, et les index partiels pour le filtrage ciblé.
- Utilisez un pooler de connexions comme PgBouncer en mode transaction pour éviter l’épuisement des connexions, en particulier dans les environnements serverless.
- Rendez les migrations de schéma additives et non bloquantes, et vérifiez toujours ce que votre outil de migration génère avant de l’exécuter en production.
- Activez
pg_stat_statementspour identifier les requêtes lentes avant qu’elles ne deviennent des incidents de production.
Conception de schéma : commencer structuré, rester flexible
Une bonne conception de schéma PostgreSQL commence par une séparation claire entre ce que vous savez être important et ce qui pourrait l’être plus tard.
Pour les champs sur lesquels vous allez filtrer, trier ou effectuer des jointures — identifiants utilisateur, horodatages, indicateurs de statut, clés étrangères — utilisez des colonnes typées appropriées avec contraintes. Pour tout le reste, une colonne JSONB vous offre la flexibilité d’un store de documents sans abandonner l’intégrité relationnelle.
CREATE TABLE users (
id BIGSERIAL PRIMARY KEY,
email TEXT UNIQUE NOT NULL,
created_at TIMESTAMPTZ DEFAULT now(),
metadata JSONB DEFAULT '{}'::jsonb
);
Cette approche hybride est l’une des fonctionnalités modernes de PostgreSQL les plus pratiques disponibles. Vous obtenez l’application du schéma là où c’est important et la flexibilité là où les exigences évoluent encore. Lorsqu’un champ JSONB commence à apparaître régulièrement dans les clauses WHERE, promouvez-le en colonne réelle et ajoutez une contrainte. Cette migration est simple et vaut la peine d’être effectuée.
Une chose à éviter : tout stocker en JSONB dès le départ. Cela semble flexible jusqu’à ce que vous ayez besoin d’appliquer une contrainte NOT NULL ou d’ajouter une clé étrangère — et là vous réécrivez la moitié de votre schéma de toute façon.
Stratégies d’indexation PostgreSQL qui aident réellement
Les index sont le point de convergence de la plupart des conseils de performance PostgreSQL. Les valeurs par défaut conviennent pour démarrer, mais les charges de travail de production nécessitent des choix délibérés.
Les index B-tree gèrent la plupart des requêtes d’égalité et de plage. Dans les versions récentes de PostgreSQL (18+), le planificateur peut effectuer des skip scans sur les index B-tree multicolonnes, ce qui signifie que l’ordre des colonnes importe moins qu’auparavant — mais commencer par votre colonne la plus sélective reste la valeur par défaut la plus sûre.
Les index GIN sont le bon choix pour les colonnes JSONB et la recherche plein texte :
CREATE INDEX idx_user_metadata ON users USING GIN (metadata);
Cela rend les requêtes comme WHERE metadata @> '{"plan": "premium"}'::jsonb rapides même sur de grandes tables.
Les index partiels sont sous-utilisés et souvent le meilleur outil pour filtrer les enregistrements actifs ou récents :
CREATE INDEX idx_active_users ON users (email) WHERE deleted_at IS NULL;
Évitez d’indexer chaque colonne. Chaque index ajoute une surcharge en écriture et consomme de la mémoire. Indexez ce sur quoi vos requêtes filtrent réellement, vérifiez avec EXPLAIN ANALYZE, et supprimez les index qui ne sont pas utilisés.
Discover how at OpenReplay.com.
Gestion des connexions pour les charges de travail web
Le modèle de connexion de PostgreSQL est avec état et relativement coûteux à établir. Chaque connexion génère un processus dédié sur le serveur. À grande échelle, des centaines de connexions éphémères provenant d’une application web dégraderont sensiblement les performances.
La solution est un pooler de connexions situé entre votre application et la base de données. PgBouncer est le choix standard. En mode transaction, il multiplexe de nombreuses connexions d’application sur un nombre beaucoup plus restreint de connexions réelles à la base de données.
Cela importe particulièrement dans les environnements serverless (Lambda, Vercel, Cloudflare Workers) où chaque invocation de fonction peut tenter d’ouvrir une nouvelle connexion. Sans pooling, vous atteindrez la limite max_connections de PostgreSQL sous un trafic modéré. Consultez la documentation PostgreSQL sur les paramètres de connexion pour plus de détails.
Migrations sécurisées en production
Les modifications de schéma sont le moment où les choses tournent mal. Le modèle le plus sûr consiste à rendre les modifications additives d’abord : ajouter une nouvelle colonne avec une valeur par défaut, remplir les données, puis supprimer l’ancienne colonne lors d’un déploiement ultérieur.
Évitez les opérations ALTER TABLE qui verrouillent la table sous charge. Ajouter une colonne NOT NULL sans valeur par défaut, par exemple, réécrit la table entière dans les anciennes versions de PostgreSQL. Dans PostgreSQL 11+, l’ajout d’une colonne avec une valeur par défaut non volatile ne nécessite plus de réécriture de table — mais il vaut toujours la peine de vérifier ce que votre outil de migration génère réellement avant de l’exécuter en production.
Exécutez toujours les migrations dans des transactions lorsque c’est possible, et testez les chemins de rollback avant le déploiement.
Performance des requêtes : identifier d’abord les requêtes lentes
Ne devinez pas les problèmes de performance. Activez pg_stat_statements pour suivre les statistiques d’exécution des requêtes sur l’ensemble de votre charge de travail :
CREATE EXTENSION IF NOT EXISTS pg_stat_statements;
Ensuite, interrogez-le pour trouver vos requêtes les plus lentes ou les plus appelées :
SELECT query, calls, mean_exec_time, total_exec_time
FROM pg_stat_statements
ORDER BY mean_exec_time DESC
LIMIT 10;
Une fois que vous avez une cible, utilisez EXPLAIN (ANALYZE, BUFFERS) pour comprendre ce que fait le planificateur et si un index serait utile.
Conclusion
PostgreSQL récompense les développeurs qui prennent au sérieux la conception de schéma, l’indexation et la gestion des connexions dès le départ. Ce ne sont pas des sujets avancés — c’est la base pour exécuter une application web en production sans surprises. Commencez avec des colonnes typées et des contraintes là où elles comptent, ajoutez JSONB uniquement là où la flexibilité est réellement nécessaire, et indexez en fonction des modèles de requêtes réels plutôt que des suppositions. Associez cela à un pooler de connexions et une stratégie de migration disciplinée, et vous éviterez les pièges les plus courants qui font trébucher les applications de production.
FAQ
Promouvez un champ JSONB en sa propre colonne lorsque vous filtrez, triez ou effectuez régulièrement des jointures dessus. Les colonnes dédiées vous permettent d'ajouter des contraintes NOT NULL, des clés étrangères et des index B-tree standard, qui améliorent tous l'intégrité des données et les performances des requêtes par rapport à l'interrogation de chemins JSONB imbriqués.
Le mode transaction est la valeur par défaut appropriée pour la plupart des applications web car il retourne les connexions au pool après chaque transaction, maximisant le débit. Utilisez le mode session uniquement si votre application s'appuie sur des fonctionnalités au niveau de la session comme les verrous consultatifs, LISTEN/NOTIFY, les modifications de configuration de session ou les tables temporaires qui doivent persister à travers plusieurs transactions.
Dans PostgreSQL 11 et versions ultérieures, l'ajout d'une colonne avec une valeur par défaut non volatile est une opération de métadonnées uniquement et ne réécrit pas la table. Cependant, l'ajout d'une colonne NOT NULL sans valeur par défaut sur les versions plus anciennes verrouille et réécrit la table entière. Vérifiez toujours le SQL que votre outil de migration génère avant de l'exécuter en production.
Il n'y a pas de nombre fixe. Indexez uniquement les colonnes sur lesquelles vos requêtes filtrent, trient ou effectuent réellement des jointures. Chaque index supplémentaire augmente la surcharge d'écriture et l'utilisation de la mémoire. Utilisez pg_stat_user_indexes pour identifier les index inutilisés et les supprimer. Commencez minimal, surveillez avec EXPLAIN ANALYZE, et ajoutez des index en fonction des modèles de requêtes observés.
Gain control over your UX
See how users are using your site as if you were sitting next to them, learn and iterate faster with OpenReplay. — the open-source session replay tool for developers. Self-host it in minutes, and have complete control over your customer data. Check our GitHub repo and join the thousands of developers in our community.