Pourquoi votre listing PostgreSQL rame (et comment 1 index le rend instantané)

“Le tableau dans notre dashboard met plusieurs secondes à charger…”
C’est une remarque que j’entends souvent en maintenance d’applications existantes.
On pense parfois à :
- Un composant frontend trop lourd,
- Un ORM qui génère des requêtes inefficaces,
- Un serveur un peu juste…
Mais dans la majorité des cas que j’ai analysés, la cause est bien plus simple :
👉 la table n’est pas indexée sur les colonnes utilisées pour le tri ou les filtres.
🔍 Test réel sur 100 000+ lignes
J’ai simulé un cas métier classique avec une table invoices contenant 100 000 factures réalistes (statuts, dates, montants aléatoires).
Objectif : reproduire un listing de dashboard typique.
❌ Requête de listing SANS index
SELECT * FROM invoices
WHERE status = 'paid'
ORDER BY created_at DESC
LIMIT 50;Résultat de EXPLAIN ANALYZE :
Execution Time: 26.976 ms
→ PostgreSQL parcourt 70 000 lignes, filtre, puis trie partiellement.Même avec 100k lignes, 27 ms par requête, multiplié par des dizaines d’utilisateurs, ça devient frustrant.
✅ Solution : un index composite bien ciblé
CREATE INDEX idx_invoices_status_created ON invoices (status, created_at DESC);Même requête, après l’index :
Execution Time: 0.200 ms
→ PostgreSQL va directement aux 50 bonnes lignes, triées, sans scan.🚀 Résultat : 26.976 ms → 0.200 ms = 135× plus rapide.
Et aucune modification dans l’application.
⚖️ Et l’impact sur les écritures ? J’ai mesuré.
J’ai inséré 10 000 nouvelles lignes avant et après la création de l’index :
Sans index => 52.4 ms
Avec index => 56.4 ms
➡️ Surcoût : +4 ms pour 10 000 lignes
→ Soit +0.0004 ms par ligne.
💡 Dans un dashboard, on a souvent des centaines de lectures pour une écriture.
Gagner 26.8 ms par requête utilisateur pour un coût quasi nul en écriture ?
Le compromis est évident.
❌ Erreur courante à éviter
Ne créez pas :
- Un index sur status seul → pas d’aide pour le tri.
- Un index sur created_at seul → pas d’aide pour le filtre.
✅ **Seul l’index composite **(status, created_at DESC) **couvre les deux besoins :
**
- Filtrer rapidement (WHERE status = 'paid'),
- Récupérer les résultats déjà triés (ORDER BY created_at DESC).
📊 Le vrai coût ? Ce n’est pas l’index… c’est son absence
- Sans index : le listing rame, les utilisateurs attendent.
- Avec un bon index : réactivité immédiate.
- Le surcoût en écriture ? Négligeable dans la quasi-totalité des applications métier.
La vraie erreur n’est pas d’avoir des index.
C’est d’en avoir trop (inutiles)… ou aucun (quand il en faut).
🔍 Avant de chercher une solution complexe…
… posez-vous une question simple :
« Est-ce que mon listing est correctement indexé ? »
Utilisez EXPLAIN ANALYZE.
Si vous voyez un Seq Scan sur une table volumineuse, la solution est souvent un index.
Et dans 90 % des cas, une seule ligne SQL suffit à transformer une expérience lente en une interface fluide.
✨ Bonus : nettoyez les index inutiles
Un index jamais utilisé ne sert qu’à ralentir les écritures.
Trouvez-les avec :
SELECT indexname, idx_tup_read
FROM pg_stat_user_indexes
WHERE idx_tup_read = 0;✅ Conclusion :
Un listing lent n’est pas une fatalité technique.
Souvent, la solution tient en une ligne :
CREATE INDEX …