PostgreSQL for Everything : Arbitrer entre spécialisation prématurée et sobriété d'infrastructure
En résumé :
- Mutualisation des ressources : Un nœud PostgreSQL dimensionné avec soin exploite dynamiquement 100 % de la RAM (
shared_buffers+ Linux Page Cache), supprimant le surdimensionnement passif lié à l'empilement de services secondaires (ex: Valkey). - Réalité des chiffres (Cache) : Sur un dataset de 10M de lignes (~1,3 Go), Postgres délivre ~54 000 GET/s (70 % du débit brut de Valkey/Redis) avec une latence P99 < 9,3 ms.
- Inconvénient majeur : Contention I/O et verrouillage mémoire si les workloads analytiques, d'écriture massive et de cache s'affrontent sur le même nœud sans isolation.
L’empilement technologique est devenu le biais par défaut de l'architecture logicielle : un SGBD relationnel pour l'état, Redis pour le cache, Elasticsearch pour le Full-Text et RabbitMQ pour l'asynchronisme.
Cette micro-spécialisation théorique se paye cash en production : multiplication des sauts réseaux (network hops), risques de pannes partielles, consommation mémoire fragmentée et explosion du Coût Total de Possession (TCO). Pour 90 % des applications dont la charge ne dépasse pas 50 000 req/s, cette architecture éclate l'infrastructure sans justification opérationnelle.
La démarche « PostgreSQL for Everything » n'est pas une posture dogmatique mais une analyse coût/bénéfice du cycle de vie d'un système.
Chapitre 1 : Le Caching — Déconstruire le mythe du « Pure In-Memory »
L'argument historique en faveur d'un moteur dédié (Redis/Valkey) repose sur le stockage 100 % en RAM. C'est ignorer la manière dont les noyaux modernes et PostgreSQL (notamment avec les dernières versions) gèrent la mémoire.
Ressources de benchmark :
- /base-de-connaissances/benchmarkings/caching — Rapport d'analyse sur 10M de lignes avec percentiles P99.
- Dépôt GitHub
benchmark-caching— Benchmarks Go, schémas PostgreSQL et tuning noyau. - Analyse des percentiles (p50, p95, p99) — Pourquoi la latence moyenne est un indicateur trompeur.
1.1 L'illusion de la mémoire dédiée : RAM fragmentée vs Mappings partagés
Allouer un cluster Redis dédié en parallèle d'un serveur PostgreSQL revient à découper artificiellement votre RAM :
- Une tranche fixe réservée au cache (ex: 16 Go).
- Une tranche fixe pour Postgres (
shared_buffers). - Le reste alloué au Page Cache du noyau Linux.
Ce cloisonnement crée un gaspillage systémique : la RAM de Redis est bloquée même à faible charge, tandis que Postgres peut souffrir d'évictions sur le Page Cache.
À l'inverse, PostgreSQL s'appuie sur une architecture à double niveau de cache. Les pages de données chaudes et les index B-Tree résident dans les shared_buffers ou sont immédiatement servis par le Page Cache Linux sans accès disque physique. Un unique pool de RAM mutualisé permet à l'OS d'allouer dynamiquement 100 % des ressources là où la pression I/O s'exerce.
1.2 Matrice comparative : Facture d'infrastructure et complexité
| Axe d'Analyse | Approche Fragmentée (Postgres + Redis/Valkey) | Approche Unifiée (PostgreSQL for Everything) |
|---|---|---|
| Ressources / TCO | Surdimensionnement obligatoire des instances pour absorber les pics isolés. | CPU/RAM mutualisés. Lissage naturel des pics de charge. |
| Latence Réseau | Quelques millisecondes par appel via network hop inter-services + coût de sérialisation. | Latence sub-milliseconde en connexions réutilisées ou colocalisées. |
| Exploitation / HA | Multiples clusters à répliquer, monitorer, patcher et sauvegarder (PITR). | Un seul cluster HA (ex: Patroni + PGBackRest). |
| Cohérence Données | Gestion manuelle du stale cache, pannes partielles, invalidations manquées. | Cohérence transactionnelle ACID garantie par le moteur de stockage. |
1.3 Éliminer la dette d'invalidation : L'approche Transactionnelle ACID
Dans une architecture disjointe, la double écriture (Base + Cache) introduit obligatoirement des race conditions et du code parasite (retry queues, gestion des timeouts).
En rapatriant le cache temporaire dans des tables UNLOGGED (qui désactivent le journal de transaction WAL pour supprimer les bottlenecks I/O), l'invalidation devient synchrone, atomique et gérée directement par le moteur :
1-- Invalidation atomique du cache métier au sein d'une transaction unique
2BEGIN;
3
4-- 1. Mutation métier sur la table relationnelle
5UPDATE users
6SET email = 'chris.new@domain.com', updated_at = NOW()
7WHERE id = 42;
8
9-- 2. Invalidation immédiate sur la table de cache UNLOGGED
10DELETE FROM user_cache_unlogged
11WHERE user_id = 42;
12
13-- En cas d'erreur ou d'annulation applicative :
14-- Tout est rollback, le cache n'est jamais désynchronisé.
15COMMIT;
1.4 Arbitrage technique : Quand faut-il (vraiment) recourir à d'autres solutions ?
PostgreSQL peut couvrir de nombreux cas d'usage mais il n'est pas optimal pour tous les besoins. Voici les limites factuelles qui imposent une séparation des responsabilités :
Bénéfices constatés :
-
Débit de lecture massifs : ~54 000 GET/s maintenus sur 10M de lignes (soit 70 % des performances brutes de Valkey sur un dataset de 10M de lignes).
-
Latence P99 maîtrisée : Réponse < 9,3 ms sous 250 connexions concurrentes.
-
Simplification du code : Suppression des wrappers d'invalidation et des dépendances à des SDK tiers.
Limites et cas de rupture :
-
Débit d'écriture extrême (SET > 50k req/s) : Même en mode UNLOGGED (soutenu à ~29 000 SET/s), la contention sur les verrous de pages (row-level locks) et la saturation du CPU par le gestionnaire de processus Postgres poseront une limite stricte face à un Single-Thread in-memory comme Valkey.
-
Consommation de connexions : Postgres gère les connexions via des processus fils (fork). Sans un pooler robuste en amont (ex: PgBouncer), monter à des dizaines de milliers de connexions clientes simultanées écrasera la mémoire vive.
-
Isolation des pannes (Blast Radius) : Si votre logique de cache génère un pic de charge critique, elle risque d'asphyxier la base transactionnelle métier.