Le discours a généralement lieu lors d’une réunion où personne n’a reçu d’alerte à 3 h du matin récemment. « Utilisez simplement NoSQL », dit quelqu’un, comme si choisir une base de données était aussi simple que choisir une police. Quelques mois plus tard, vous regardez des commandes dupliquées, des écritures de grand livre manquantes, et un job de rapprochement « temporaire » qui est devenu votre système le plus fiable.
Le problème n’est pas que NoSQL soit mauvais. Le problème est que « utilisez simplement NoSQL » est une réponse paresseuse à une question difficile : comment stocker la vérité sous des contraintes de production réelles — latence, coût, concurrence, pannes, erreur humaine et le produit qui change d’avis tous les deux sprints ?
Pourquoi « utilisez simplement NoSQL » brûle les équipes
« Utilisez simplement NoSQL » est généralement un substitut pour l’une de ces croyances :
- SQL ne montera pas en charge (traduction : nous avons eu une requête lente une fois et nous avons blâmé la base).
- Les schémas freinent les équipes (traduction : nous ne voulons pas débattre du modèle de données aujourd’hui).
- Nous avons besoin de flexibilité (traduction : les exigences produit sont instables, alors nous allons encoder cette instabilité dans le stockage).
- Les jointures sont coûteuses (traduction : nous avons vu un plan de jointure lent une fois).
- Nous voulons l’échelle web (traduction : nous voulons nous sentir en sécurité sans faire de planification de capacité).
Ces croyances ne sont pas toujours fausses. Mais le conseil est incomplet — dangereusement incomplet — parce qu’il saute la phase où vous définissez les invariants et décidez où les faire respecter.
Les invariants sont le produit
Si vous stockez de l’argent, du stock, des permissions, des droits de facturation, des comptes sièges, ou « une seule session active par utilisateur », vous stockez des invariants. Ce n’est pas un détail d’implémentation ; c’est le business. La base de données est l’endroit où les invariants vivent en paix ou meurent bruyamment.
Les bases relationnelles ne sont pas magiques parce qu’elles parlent SQL. Elles sont puissantes parce qu’elles offrent un ensemble mature et intégré d’outils pour faire respecter la vérité :
- Transactions avec niveaux d’isolation bien compris
- Contraintes (clefs étrangères, contraintes d’unicité, check constraints)
- Index déclaratifs et planification des requêtes
- Journalisation durable (write-ahead logging) et reprise prévisible
- Observabilité opérationnelle affinée par des décennies de douleur
Les systèmes NoSQL peuvent absolument être corrects, fiables et rapides. Mais beaucoup décalent la correction vers la couche applicative : vers l’application, des jobs en arrière-plan, des transactions compensatoires, vers « on nettoiera plus tard ». Ce « plus tard » devient une taxe permanente, payée en fatigue d’astreinte.
Une règle pratique : si les données doivent être correctes dès aujourd’hui, faites-les respecter à l’endroit où elles sont écrites. Si vous pouvez tolérer d’être « éventuellement correct », vous devez malgré tout définir ce que « éventuellement » signifie et comment détecter quand cela n’a pas eu lieu.
Petite blague #1 : La cohérence éventuelle, c’est comme des excuses dans un chat de groupe : ça arrive plus tard, et ça ne répare pas toujours ce qui a cassé.
NoSQL n’est pas une seule chose
Quand quelqu’un dit « NoSQL », demandez de quelle catégorie il parle, car le profil opérationnel et l’histoire de la correction diffèrent :
- Stores de documents (ex. type Mongo) : excellents pour documents imbriqués, piège pour les invariants entre documents.
- Clé-valeur (ex. type Redis) : fantastique pour cache et état éphémère, terrible comme source de vérité sauf si fortement contraint.
- Wide-column (ex. type Cassandra) : excellent pour haut débit d’écriture avec des patterns d’accès connus ; les partitions chaudes ruineront votre week-end.
- Index de recherche (ex. type Elasticsearch) : ce n’est pas une base de données ; c’est un index avec des opinions.
- Bases graphe : niche mais utile quand les relations sont la requête.
« Utilisez simplement NoSQL » ignore la partie où vous faites correspondre la forme des données, les patterns d’accès et la tolérance aux pannes avec les garanties réelles d’un système.
Huit faits rapides et un peu d’histoire
Un peu de contexte aide, car l’industrie réapprend sans cesse les mêmes leçons sous d’autres appellations.
- Les bases relationnelles ont leur théorie en 1970 quand E. F. Codd publia le modèle relationnel. Ce n’était pas « SQL » ; c’était un modèle de correction.
- La standardisation SQL a commencé dans les années 1980. Les parties ennuyeuses — transactions, contraintes, optimisation des requêtes — sont où se situe la majorité de la valeur.
- « NoSQL » comme terme s’est popularisé vers 2009, surtout comme bannière pour des systèmes conçus pour répondre aux douleurs de montée en charge et de distribution de l’époque.
- Le théorème CAP a été formalisé au début des années 2000. Les gens l’utilisent encore mal, principalement pour justifier des comportements cassés comme un « compromis ».
- Le papier Dynamo d’Amazon (milieu des années 2000) a influencé une génération de designs clé-valeur et wide-column axés sur disponibilité et tolérance aux partitions.
- Le papier Bigtable de Google (milieu des années 2000) a façonné les wide-column stores et les moteurs LSM-tree optimisés pour le débit d’écriture.
- Le two-phase commit précède la plupart des plateformes cloud. Ce n’est pas nouveau ; c’est juste coûteux dans des environnements distribués et pénible à opérer.
- « NewSQL » n’a pas été une replacement magique ; c’était une tentative d’apporter la sémantique SQL aux systèmes distribués, parfois avec succès, souvent avec de nouvelles contraintes opérationnelles.
L’histoire ne choisit pas votre base de données. Mais elle explique pourquoi certains compromis existent, et pourquoi votre « schéma flexible » se transforme sans cesse en série de backfills sur-mesure.
La vraie alternative : concevoir à partir des invariants, puis choisir la technologie
La vraie alternative à « utilisez simplement NoSQL » n’est pas « utilisez simplement PostgreSQL ». C’est un processus de décision :
- Écrivez les invariants. Pas les fonctionnalités. Les invariants. « Un total de facture doit égaler la somme des lignes. » « Un utilisateur ne peut pas avoir deux abonnements actifs. » « Le stock ne peut pas être négatif. »
- Définissez le contrat de cohérence. Pour chaque invariant, répondez : doit-il être vrai au moment de l’écriture, ou peut-il être réconcilié plus tard ?
- Modélisez les patterns d’accès. Chemins de lecture, chemins d’écriture, cardinalité, pics, fan-outs.
- Choisissez où réside la vérité. Un système est le système de référence. Tout le reste est dérivé, mis en cache, indexé ou dénormalisé.
- Choisissez l’ensemble minimal de bases de données. Chaque datastore additionnel est un mode d’échec additionnel, un runbook d’astreinte, une histoire de sauvegarde et un plan de migration de données.
« Utiliser une seule base » n’est pas un dogme ; c’est un modèle de coût
Les équipes aiment la persistence polyglotte jusqu’à devoir la restaurer. « Nous avons Postgres pour les transactions, Redis pour le cache, Elasticsearch pour la recherche, Kafka pour les événements, et un store de documents pour le flexible. » Ça peut être correct. Ça peut aussi être un cauchemar de fiabilité.
Un défaut pratique pour beaucoup d’entreprises :
- PostgreSQL (ou une autre base relationnelle) comme système de référence.
- Redis pour le cache et la limitation de débit, pas pour l’état canonique sauf si vous acceptez les risques.
- Un index de recherche pour la recherche textuelle, alimenté depuis le système de référence.
- Un bus de logs/événements si vous avez besoin d’intégration asynchrone et de rejouabilité.
Commencez par l’architecture la plus simple qui puisse être correcte. Montez en charge avec des techniques ennuyeuses — index, tuning des requêtes, partitionnement, réplicas de lecture — avant de passer au « nous avons besoin d’une base distribuée ». Les critères de passage doivent être une charge mesurable, pas une peur imaginable.
Une citation, parce qu’elle reste vraie
Idée paraphrasée (Werner Vogels) : « Vous construisez, vous exploitez » signifie que les équipes sont responsables des résultats opérationnels, pas seulement des merges de code.
Les choix de base de données sont des résultats opérationnels. Si votre base est « le problème de quelqu’un d’autre », votre file d’incidents vous apprendra le contraire.
Modes d’échec NoSQL que vous rencontrerez réellement
1) « Schéma flexible » devient « corruption silencieuse »
Les stores de documents facilitent l’écriture de données avec des champs manquants, des types incorrects ou des formes subtilement incohérentes. Sans contraintes, votre application devient le validateur de schéma. Et les applications changent. Les ingénieurs tournent. Les migrations sont reportées.
Schéma de défaillance : un service écrit price_cents en tant que nombre, un autre l’écrit en tant que chaîne, l’analytics le caste « utilement », et maintenant les revenus sont une erreur d’arrondi au moment du bilan trimestriel.
2) Les invariants entre entités deviennent des jobs en arrière-plan
Besoin de « une seule souscription active par compte » ? Dans une BD relationnelle : contrainte d’unicité plus transaction. Dans de nombreux setups NoSQL : vérification dans l’app, course sous concurrence, correction par job périodique de « dedupe », et ajout d’un outil manuel pour l’équipe support.
3) Partitions chaudes et clés inégales
Les systèmes NoSQL distribués aiment la distribution uniforme des clés. Les entreprises réelles adorent les IDs séquentiels, les tenants qui ont un trafic énorme, et les préfixes « date du jour ». Vous pouvez monter un cluster wide-column qui gère des millions d’écritures… jusqu’à ce qu’une clé de partition devienne un trou noir.
4) Les index secondaires ne sont pas gratuits (et parfois pas réels)
Certaines solutions traitent les index secondaires comme optionnels, éventuellement cohérents, ou coûteux à maintenir. Votre requête marche en staging puis s’effondre sous la cardinalité de production.
5) La complexité opérationnelle devient le produit
Réplication, compaction, repair, hinted handoff, lectures en quorum, tombstones — ce ne sont pas des « features avancées ». Ce sont le prix d’entrée. Si votre équipe n’a pas l’appétit pour ce travail, n’achetez pas ce système.
6) Sauvegarde/restauration et récupération point-in-time deviennent bizarres
Une base relationnelle avec archivage WAL vous donne une histoire simple : sauvegarde complète + replay WAL. Dans certaines solutions NoSQL, les backups sont « prendre des snapshots par nœud et espérer qu’ils s’alignent ». On peut le faire, mais il faut s’entraîner. Sinon la première tentative de restauration se fera pendant une panne. Ce n’est pas un exercice ; c’est un moment de carrière.
Quand NoSQL est le bon choix (et quand ce n’est pas le cas)
Utilisez NoSQL quand ces conditions sont vraies
- Vos patterns d’accès sont connus et stables. Vous pouvez décrire vos requêtes à l’avance et elles ne changeront pas chaque semaine.
- Vous avez besoin d’un débit d’écriture massif réparti entre partitions et pouvez concevoir des clés pour éviter les hotspots.
- Vous pouvez tolérer des sémantiques transactionnelles plus faibles pour le flux principal, ou vous avez un design compensatoire que vous avez testé.
- Vous construisez des vues dérivées : caches, modèles de lecture matérialisés, index de recherche, couches d’ingestion time-series.
- Votre équipe peut l’exploiter. Pas « quelqu’un peut Googler ». Quelqu’un en assumera la responsabilité.
Évitez NoSQL comme système de référence quand ces conditions sont vraies
- Vous avez besoin d’invariants multi-entités à l’écriture. Facturation, grand livre, permissions, inventaire, droits.
- Vous ne connaissez pas encore les patterns d’accès. Le « schéma flexible » ne vous sauvera pas des requêtes inconnues ; il ne fait que retarder l’argument.
- Vous ne pouvez pas vous permettre le travail de nettoyage des données. La réconciliation en arrière-plan est un plan de recrutement caché.
- Vous avez besoin d’un audit simple. Vous voulez des contraintes, des logs et une histoire claire de « qui a changé quoi ».
Petite blague #2 : « Sans schéma » signifie généralement « schéma écrit dans Slack et perdu dans un canal que personne ne consulte ».
Trois micro-histoires d’entreprise du terrain
Micro-histoire #1 : L’incident causé par une fausse hypothèse (la cohérence éventuelle par défaut)
Une entreprise SaaS de taille moyenne a reconstruit son workflow de facturation pour « aller plus vite ». Le vieux monolithe utilisait une base relationnelle avec des transactions. La nouvelle architecture a séparé « subscriptions », « invoices » et « payments » en services distincts, chacun avec son store de documents. Les services communiquaient via un bus d’événements.
L’hypothèse erronée était subtile : ils ont supposé que « cohérence éventuelle » signifiait « quelques secondes ». En réalité, cela signifiait « quand les consommateurs sont sains, rattrapés, et ne re-traitent pas ». Lors d’un déploiement, le service facture a pris du retard sur les événements de paiement suffisamment longtemps pour que l’UI affiche « paiement reçu » mais « facture impayée ». Les tickets support ont explosé, puis la finance est montée au créneau parce que des relances automatiques ont été déclenchées sur des comptes payés.
L’astreinte a tenté de patcher avec des retries et le scaling des consumers. Ça s’est empiré. Les replays ont réordonné les événements. Certains événements ont été traités deux fois. Un petit nombre a été perdu à cause d’un bug dans les clés d’idempotence. Personne n’avait un endroit unique pour interroger « quelle est la vérité pour le compte X maintenant ? »
La correction n’a pas été héroïque. Ils ont fait d’un service — le grand livre de facturation — l’autorité dans une base relationnelle. Les autres services sont devenus des projections. Ils ont ajouté l’enforcement d’idempotence au chemin d’écriture du grand livre et ont fait lire l’UI depuis le grand livre pour le statut visible client. Le bus d’événements est resté, mais son rôle est devenu l’intégration, pas la vérité.
Micro-histoire #2 : L’optimisation qui a mal tourné (dénormalisation pour éviter les jointures)
Une plateforme e‑commerce avait un endpoint « détails de commande » lent. Quelqu’un a profilé, vu plusieurs jointures, et pris la décision confiante : « On doit dénormaliser. Les jointures ne montent pas en charge. » Ils ont déplacé les lignes de commande et le statut d’expédition dans un seul document par commande dans un store de documents. Les lectures sont devenues plus rapides immédiatement. Tout le monde a célébré.
Puis les retours sont arrivés comme prévu. Le traitement des retours devait mettre à jour des lignes individuelles tout en préservant une piste d’audit. Le support client devait faire des mises à jour partielles sans écraser des changements concurrents. Un workflow antifraude a ajouté des tags et des notes. Trois équipes ont commencé à écrire dans le même document, depuis différents services, avec des hypothèses différentes sur la concurrence.
Les conflits ont tourné en perte de données due au dernier écrit qui gagne. Les ingénieurs ont ajouté une logique de fusion par champ. Puis un champ « version document » et des retries. Puis un job de « réparation » en arrière-plan pour réconcilier les champs manquants. Le document de commande est devenu un territoire contesté. Les incidents sont devenus politiques : « votre service a écrasé nos champs. »
La conception finalement stable a réintroduit la normalisation là où la concurrence comptait. L’en‑tête de commande est resté dans une ligne, les lignes de commande dans une autre table, et les annotations mutables dans leur propre store avec une propriété explicite. La lecture rapide est venue des vues matérialisées et du cache — pas de transformer un problème de correction en blob JSON.
Micro-histoire #3 : La pratique ennuyeuse mais correcte qui a sauvé la mise (restaurations testées et PITR)
Une plateforme B2B utilisait Postgres comme store principal. Rien de flashy. Mais un SRE insistait sur deux disciplines ennuyeuses : archivage WAL pour la récupération point‑in‑time et un exercice mensuel de restauration. Pas « nous avons des backups », mais « nous avons restauré sur un nouveau cluster et exécuté des contrôles applicatifs ».
Un après-midi, un ingénieur a exécuté une migration qui a supprimé une colonne puis l’a recréée avec le même nom mais un type différent. La migration a passé en staging. En production, l’app a commencé à écrire des données incohérentes dans la nouvelle colonne. Le bug a été détecté rapidement, mais les données étaient déjà incorrectes d’une manière qui ne pouvait pas être « corrigée vers l’avant » sans un point de référence fiable.
Ils ont effectué une récupération point‑in‑time à quelques minutes avant la migration, extrait les tables affectées, et rejoué les écritures légitimes en utilisant les logs applicatifs et des runners idempotents. La panne a été douloureuse, mais bornée. Pas d’email « nous avons perdu une journée de données ». La finance n’est pas intervenue. Personne n’a eu à expliquer « on pense que c’est correct. »
Cet incident n’a pas fait une conférence. Il a cependant évité un trimestre de chaos au ralenti. L’ennuyeux gagne parce qu’il est répétable.
Mode d’emploi pour un diagnostic rapide : quoi vérifier en premier/deuxième/troisième
Voici la séquence d’astreinte qui trouve le goulot sans passer une heure à débattre des tableaux de bord.
Premier : décidez si c’est saturation, contention ou correction
- Saturation : CPU, IO, mémoire ou réseau saturés. La latence augmente avec la charge.
- Contention : verrous, clés chaudes, concurrence limitée. Le débit stagne ; les files s’allongent.
- Correction : lag de réplication, lectures inconsistantes, index manquants provoquant des timeouts qui ressemblent à des pannes.
Deuxième : localisez le point d’étranglement (app vs base vs stockage)
- Vérifiez la latence p95 de l’app et le taux d’erreurs autour des appels DB.
- Vérifiez la santé de la DB : connexions, attentes de locks, requêtes lentes.
- Vérifiez le stockage : iowait, latence disque, saturation, erreurs système de fichiers.
Troisième : choisissez la mitigation la plus rapide et sûre
- Réduire la charge : rate limit, dégrader le trafic non critique, désactiver les endpoints coûteux.
- Améliorer le plan de requête : ajouter/ajuster un index, corriger les patterns N+1, limiter les requêtes non bornées.
- Corriger la contention : éviter les lignes/partitions chaudes, raccourcir les transactions, ajuster l’isolation, ajouter des queues.
- Monter en charge en sécurité : ajouter des réplicas de lecture, ajouter du cache, augmenter la taille d’instance uniquement si vous comprenez la limite.
Tâches pratiques : commandes, sorties et la décision que vous prenez
Ce sont des tâches réelles que vous pouvez exécuter aujourd’hui. Chacune inclut une commande, ce que la sortie vous dit, et la décision à prendre. Supposez des serveurs Linux et un mix de PostgreSQL et de composants NoSQL courants. Ajustez les hostnames et les credentials en conséquence.
Task 1: Check if the box is IO-bound (Linux iostat)
cr0x@server:~$ iostat -x 1 3
Linux 6.1.0 (db01) 02/04/2026 _x86_64_ (16 CPU)
avg-cpu: %user %nice %system %iowait %steal %idle
12.31 0.00 4.22 31.88 0.00 51.59
Device r/s w/s rkB/s wkB/s await svctm %util
nvme0n1 85.0 220.0 5200.0 14800.0 18.2 0.9 92.4
Ce que cela signifie : Un %iowait élevé et un %util disque élevé suggèrent une saturation du stockage. await indique une latence de queue.
Décision : Ne commencez pas par « optimiser le code ». Réduisez l’amplification d’écriture (indexes, autovacuum, compaction), déplacez le WAL sur un disque plus rapide, ou scalez le stockage/IOPS.
Task 2: See who is eating memory and whether swapping is involved
cr0x@server:~$ free -h
total used free shared buff/cache available
Mem: 64Gi 52Gi 1.2Gi 1.1Gi 11Gi 8.5Gi
Swap: 8Gi 2.9Gi 5.1Gi
Ce que cela signifie : L’usage du swap sur un hôte DB se corrèle souvent avec des pics de latence aléatoires.
Décision : Réduisez la pression mémoire (ajustez shared buffers / tailles de cache, corrigez les fuites, redimensionnez). Si vous swappez, votre « problème DB » est un problème de noyau.
Task 3: Confirm filesystem space and inode health
cr0x@server:~$ df -h /var/lib/postgresql
Filesystem Size Used Avail Use% Mounted on
/dev/nvme0n1p2 900G 812G 42G 96% /var/lib/postgresql
Ce que cela signifie : 96% d’espace utilisé est une panne au ralenti (vacuum, compaction et fichiers temporaires ont besoin d’espace libre).
Décision : Agrandissez le stockage ou purgez en toute sécurité. Ajoutez des alertes à 80/85/90%. N’attendez pas 100%.
Task 4: Check disk latency directly
cr0x@server:~$ nvme smart-log /dev/nvme0n1 | sed -n '1,12p'
Smart Log for NVME device:nvme0n1 namespace-id:ffffffff
critical_warning : 0x00
temperature : 41 C
available_spare : 100%
available_spare_threshold : 10%
percentage_used : 4%
data_units_read : 1,210,331
data_units_written : 2,980,552
host_read_commands : 18,220,114
host_write_commands : 55,991,202
Ce que cela signifie : Ce n’est pas une métrique de latence, mais cela vous indique si le périphérique est sain. Les critical warnings comptent.
Décision : Si des warnings/erreurs SMART apparaissent, arrêtez de débattre des plans de requête et commencez à planifier le remplacement ou la migration du disque.
Task 5: Find the slowest PostgreSQL queries by total time
cr0x@server:~$ psql -d appdb -c "SELECT query, calls, total_time, mean_time FROM pg_stat_statements ORDER BY total_time DESC LIMIT 5;"
query | calls | total_time | mean_time
----------------------------------------------------------------------+-------+------------+----------
SELECT * FROM orders WHERE account_id = $1 ORDER BY created_at DESC... | 9842 | 812345.12 | 82.54
UPDATE inventory SET available = available - $1 WHERE sku = $2 | 62111 | 420998.77 | 6.78
SELECT * FROM events WHERE tenant_id = $1 AND created_at > $2 | 2109 | 318120.33 | 150.86
Ce que cela signifie : total_time repère la « mort par mille coupures ». mean_time repère les couteaux aigus.
Décision : Corrigez d’abord le total_time élevé s’il domine la charge ; corrigez le mean_time élevé s’il domine la latence tail.
Task 6: Explain a specific slow query (Postgres EXPLAIN ANALYZE)
cr0x@server:~$ psql -d appdb -c "EXPLAIN (ANALYZE, BUFFERS) SELECT * FROM orders WHERE account_id = 42 ORDER BY created_at DESC LIMIT 50;"
Limit (cost=0.56..220.14 rows=50 width=312) (actual time=120.331..120.410 rows=50 loops=1)
Buffers: shared hit=120 read=845
-> Index Scan Backward using orders_created_at_idx on orders (cost=0.56..44120.12 rows=10024 width=312) (actual time=120.329..120.401 rows=50 loops=1)
Filter: (account_id = 42)
Rows Removed by Filter: 580000
Buffers: shared hit=120 read=845
Planning Time: 0.219 ms
Execution Time: 120.450 ms
Ce que cela signifie : L’index est sur created_at, mais vous filtrez par account_id. Il scanne beaucoup puis filtre.
Décision : Ajoutez un index composite comme (account_id, created_at DESC) ou restructurez la requête. C’est « SQL ne scale pas » seulement si vous refusez d’indexer correctement.
Task 7: Check lock contention in PostgreSQL
cr0x@server:~$ psql -d appdb -c "SELECT blocked.pid AS blocked_pid, blocking.pid AS blocking_pid, blocked.query AS blocked_query FROM pg_stat_activity blocked JOIN pg_locks bl ON bl.pid = blocked.pid JOIN pg_locks kl ON kl.locktype = bl.locktype AND kl.database IS NOT DISTINCT FROM bl.database AND kl.relation IS NOT DISTINCT FROM bl.relation AND kl.page IS NOT DISTINCT FROM bl.page AND kl.tuple IS NOT DISTINCT FROM bl.tuple AND kl.virtualxid IS NOT DISTINCT FROM bl.virtualxid AND kl.transactionid IS NOT DISTINCT FROM bl.transactionid AND kl.classid IS NOT DISTINCT FROM bl.classid AND kl.objid IS NOT DISTINCT FROM bl.objid AND kl.objsubid IS NOT DISTINCT FROM bl.objsubid AND kl.pid != bl.pid JOIN pg_stat_activity blocking ON blocking.pid = kl.pid WHERE NOT bl.granted;"
blocked_pid | blocking_pid | blocked_query
------------+--------------+--------------------------------
21934 | 21811 | UPDATE inventory SET available =
Ce que cela signifie : Une transaction bloque d’autres. La DB est saine ; votre modèle de concurrence ne l’est pas.
Décision : Raccourcissez les transactions, ajoutez des index adéquats pour éviter l’escalade de locks, ou redesign le pattern de ligne chaude (compteurs par SKU sont notoirement problématiques).
Task 8: Check replication lag (Postgres streaming replica)
cr0x@server:~$ psql -d appdb -c "SELECT application_name, state, sync_state, write_lag, flush_lag, replay_lag FROM pg_stat_replication;"
application_name | state | sync_state | write_lag | flush_lag | replay_lag
------------------+---------+------------+-----------+-----------+------------
replica01 | streaming | async | 00:00:01 | 00:00:02 | 00:00:15
Ce que cela signifie : Le replay lag signifie que les lectures depuis la réplique peuvent être obsolètes de cette durée.
Décision : Si votre appli lit-après-écrit depuis les réplicas, vous avez besoin de règles de routage de lecture, de sticky sessions, ou de réplication synchrone pour les workflows spécifiques.
Task 9: See if autovacuum is keeping up (Postgres bloat risk)
cr0x@server:~$ psql -d appdb -c "SELECT relname, n_dead_tup, last_autovacuum FROM pg_stat_user_tables ORDER BY n_dead_tup DESC LIMIT 5;"
relname | n_dead_tup | last_autovacuum
------------+------------+--------------------------
events | 81234567 | 2026-02-03 01:12:02+00
sessions | 21003444 | 2026-01-29 12:40:10+00
orders | 8120032 | 2026-02-04 00:02:11+00
Ce que cela signifie : Un grand nombre de tuples morts signifie bloat de table, taux de hit cache plus faible et requêtes plus lentes.
Décision : Tondez autovacuum, ajustez le fillfactor, ou partitionnez les tables à fort churn. Le bloat n’est pas une « lenteur mystérieuse » ; c’est de la physique.
Task 10: Check connection pressure (Postgres)
cr0x@server:~$ psql -d appdb -c "SELECT count(*) AS connections, state FROM pg_stat_activity GROUP BY state ORDER BY connections DESC;"
connections | state
------------+---------------------
220 | active
180 | idle
40 | idle in transaction
Ce que cela signifie : Trop de connexions actives peuvent thrash le CPU. « Idle in transaction » est un tueur silencieux (ça garde des locks et empêche le vacuum).
Décision : Ajoutez un pooler (ex. pgbouncer), corrigez la gestion des transactions, et limitez les connexions par service.
Task 11: Detect a hot key pattern at the application layer (nginx / access logs example)
cr0x@server:~$ awk '{print $7}' /var/log/nginx/access.log | grep -E '^/api/orders\?account_id=' | sort | uniq -c | sort -nr | head
18421 /api/orders?account_id=42
9211 /api/orders?account_id=17
4420 /api/orders?account_id=5
Ce que cela signifie : Un petit nombre de clés dominent le trafic. Ça se traduit par des partitions chaudes, des lignes chaudes, ou des stampedes de cache.
Décision : Ajoutez du cache, des limites de pagination, ou des vues pré-calculées pour les comptes/tenants chauds. Envisagez aussi des throttles par tenant.
Task 12: Verify Kafka consumer lag (if you use events for “eventual truth”)
cr0x@server:~$ kafka-consumer-groups.sh --bootstrap-server kafka01:9092 --describe --group invoice-service
GROUP TOPIC PARTITION CURRENT-OFFSET LOG-END-OFFSET LAG CONSUMER-ID
invoice-service billing 0 1288812 1299910 11098 consumer-1
invoice-service billing 1 1290011 1299988 9977 consumer-2
Ce que cela signifie : Le lag signifie que vos projections sont obsolètes. Si votre UI ou vos workflows dépendent de ces projections, les utilisateurs verront un état incohérent.
Décision : Soit scalez les consumers/corrigez le traitement, soit cessez d’utiliser les projections comme source de vérité visible par l’utilisateur.
Task 13: Check MongoDB index usage (example with mongosh)
cr0x@server:~$ mongosh --quiet --eval 'db.orders.find({account_id: 42}).sort({created_at:-1}).limit(50).explain("executionStats").executionStats'
{
"nReturned" : 50,
"executionTimeMillis" : 187,
"totalKeysExamined" : 0,
"totalDocsExamined" : 580050
}
Ce que cela signifie : Examiner beaucoup de documents avec zéro clé examinée suggère qu’il n’y a pas d’index utile pour cette forme de requête.
Décision : Ajoutez un index composé {account_id:1, created_at:-1} ou reconsidérez le modèle de données. Votre cluster n’est pas lent ; votre requête est coûteuse.
Task 14: Check Redis memory and eviction policy (cache turning into outage)
cr0x@server:~$ redis-cli INFO memory | egrep 'used_memory_human|maxmemory_human|mem_fragmentation_ratio'
used_memory_human:14.92G
maxmemory_human:16.00G
mem_fragmentation_ratio:1.62
Ce que cela signifie : Proche de la mémoire max avec fragmentation signifie que vous approchez d’un comportement d’éviction ou d’OOM selon la config.
Décision : Si Redis est un cache, vérifiez que la politique d’éviction est configurée et sûre. Si Redis stocke l’état canonique, reconsidérez vos choix de vie et ajoutez discipline de persistance/sauvegarde.
Erreurs courantes : symptômes → cause racine → correction
1) Symptôme : enregistrements « uniques » dupliqués (utilisateurs, abonnements, commandes)
Cause racine : unicité appliquée dans le code applicatif, pas dans le datastore ; les courses concurrentes l’emportent.
Correction : faites respecter l’unicité à l’écriture avec une contrainte de base de données ou une écriture conditionnelle atomique. Si vous ne pouvez pas, redesign : choisissez une clé canonique, utilisez des clés d’idempotence, et introduisez un processus de déduplication auditable.
2) Symptôme : lectures « aléatoires » obsolètes après des déploiements
Cause racine : lecture depuis des réplicas ou des projections sans contrat de fraîcheur ; lag de réplication ou pics de lag des consommateurs pendant les déploiements.
Correction : routage read-your-writes (lire du primaire pour une session), contrôles de fraîcheur bornés, ou réplication synchrone pour les workflows qui l’exigent.
3) Symptôme : pics de latence tail sous charge modérée
Cause racine : saturation du stockage (iowait), pression de compaction/vacuum, ou thrash du cache dû au bloat.
Correction : réduisez l’amplification d’écriture, ajustez autovacuum/compaction, ajoutez des index appropriés, partitionnez les tables à fort churn, et assurez-vous que le disque est dimensionné pour la charge.
4) Symptôme : la base est « lente », mais le CPU est bas
Cause racine : contention de locks ou mise en queue sur une clé/partition/ligne chaude. Ou bien vous êtes IO-bound.
Correction : identifiez les bloqueurs (vues lock), redesign des compteurs chauds, shard sur une meilleure clé, ou batcher les mises à jour. Pour IO-bound, adressez le disque et les patterns d’écriture.
5) Symptôme : les changements de schéma prennent des semaines et font peur
Cause racine : absence de discipline de migration ; réécritures de grosses tables ; flags manquants ; stratégie de backfill insuffisante.
Correction : adoptez des migrations expand/contract, backfill par lots contrôlés, et gardez le code compatible entre versions jusqu’à la fin du rollout.
6) Symptôme : « Nous ne pouvons pas restaurer rapidement »
Cause racine : les backups existent mais les restaurations ne sont pas testées ; pas de RTO/RPO définis ; pas de runbook.
Correction : faites des drills de restauration, définissez RPO/RTO, implémentez le PITR quand possible, et automatisez le provisionnement d’un environnement de restauration.
7) Symptôme : les résultats de recherche ne correspondent pas à la source de vérité
Cause racine : l’index de recherche est traité comme une base ; le pipeline d’indexation a du lag, des pertes ou des réordres.
Correction : traitez la recherche comme un état dérivé ; ajoutez une capacité de reindex ; utilisez versioning et idempotence ; construisez une vérification d’intégrité comparant l’index et la source.
8) Symptôme : le cluster NoSQL est « sain » mais les erreurs applicatives augmentent
Cause racine : timeouts et retries amplifient la charge ; clients mal configurés ; latence p95 qui augmente alors que la moyenne semble correcte.
Correction : instrumentez la latence côté client, limitez les retries, ajoutez des coupe‑circuits, et ajustez les timeouts pour qu’ils correspondent au SLA et au comportement de panne.
Listes de contrôle / plan étape par étape
Étape par étape : choisir le datastore sans se mentir
- Listez les invariants (vérité à l’écriture vs vérité éventuelle).
- Définissez la tolérance aux pannes : que se passe‑t‑il si les écritures réussissent partiellement ? Quel est l’impact visible par l’utilisateur ?
- Définissez les patterns de lecture : listez les 10 requêtes/endpoints principaux, pas « on recherchera plus tard ».
- Estimez la croissance : volume de données, QPS d’écriture, QPS de lecture, et distribution entre tenants/cles.
- Choisissez un système de référence. Tout le reste est dérivé.
- Choisissez la base la plus simple qui satisfasse les invariants. Si c’est relationnel, assumez‑le.
- Planifiez migrations et rollbacks. Si vous ne pouvez pas rollback en sécurité, vous n’avez pas de plan.
- Opérationnalisez : monitoring, sauvegardes, exercices de restauration, politique de changement de schéma, alertes de capacité.
Checklist : readiness production pour n’importe quelle base
- RPO et RTO définis, écrits, approuvés par le business
- Backups automatisés, chiffrés et vérifiés
- Drill de restauration effectué (pas « on a regardé les logs »)
- Alertes de capacité pour CPU, mémoire, disque et IOPS
- Visibilité des requêtes lentes avec échantillonnage et rétention
- Propriété claire et chemin d’escalade
- Timeouts et retries clients configurés intentionnellement
- Processus de schéma/migration avec expand/contract
- Plan de rétention et d’archivage des données
- Tests de charge incluant les modes de panne (perte de nœud, partitions réseau, lag consumer)
Checklist : si vous insistez sur la « cohérence éventuelle »
- Définissez « éventuel » : secondes ? minutes ? heures ? Qu’est‑ce qui est acceptable ?
- Clés d’idempotence pour chaque consommateur qui mute l’état
- Stratégie de replay testée (y compris out‑of‑order et événements dupliqués)
- Dead‑letter queue avec responsabilité opérationnelle
- Job de réconciliation auditable, pas une boîte noire
- Un endroit unique pour interroger la vérité actuelle (système de référence)
FAQ
1) La question « SQL vs NoSQL » est‑elle la bonne ?
Pas vraiment. La vraie question est : où faites‑vous respecter les invariants, et quels modes d’échec votre équipe peut‑elle opérer ? SQL est souvent la réponse la plus simple et correcte pour les systèmes de référence.
2) Mais NoSQL ne s’échelonne-t‑il pas mieux ?
Certaines solutions NoSQL s’échelonneront très bien en écriture et distribution horizontale — si vous modélisez les clés et les patterns d’accès correctement. Les systèmes relationnels montent aussi bien au‑delà de ce que la plupart des équipes atteignent, surtout avec indexation, partitionnement et mise à l’échelle en lecture appropriés.
3) Les jointures sont-elles intrinsèquement lentes ?
Non. Les mauvaises jointures sont lentes. Les index manquants sont lents. Joindre d’énormes jeux de données non filtrés est lent. Une jointure bien indexée sur des prédicats sélectifs est souvent rapide et prévisible.
4) Quel est le coût caché majeur des bases « sans schéma » ?
La dérive opérationnelle et organisationnelle : vous perdez un point central d’application. Chaque service devient validateur de schéma, les migrations deviennent « effort maximal », et le nettoyage des données devient un workflow permanent.
5) Puis‑je faire des transactions dans des bases NoSQL ?
Parfois, oui — dans certaines limites. La question est de savoir si le modèle transactionnel est mature, ce qu’il coûte, et s’il est prévisible opérationnellement sous charge et partitions. Testez les modes d’échec, pas des démos.
6) Quelle est la « vraie alternative » si mon équipe veut vitesse et flexibilité ?
Utilisez une base relationnelle comme grand livre de vérité, puis construisez des modèles dérivés flexibles : colonnes JSON quand c’est approprié, vues matérialisées, caches et index de recherche. Vous obtenez de l’agilité sans abandonner l’intégrité.
7) Quand le stockage de documents est‑il le système de référence correct ?
Quand les documents sont vraiment indépendants et que vos invariants tiennent majoritairement dans la frontière du document. Exemple : blobs de gestion de contenu, préférences utilisateur, ou données de session — à condition de concevoir malgré tout pour les migrations et la validation des données.
8) Comment éviter les partitions chaudes dans un store NoSQL distribué ?
Concevez des clés de partition pour une distribution uniforme, évitez les préfixes monotoniques, et simulez un trafic réaliste. Surveillez les top clés et la skew. Si un tenant domine, construisez une isolation et des throttles spécifiques par tenant.
9) Et si nous avons déjà choisi NoSQL et que nous le regrettons ?
Ne réécrivez pas tout en panique. Identifiez les invariants qui vous posent problème, introduisez un store autoritaire pour ceux‑ci (souvent relationnel), et migrez les workflows progressivement avec des écritures doubles seulement si vous pouvez vérifier la correction.
10) Quelle est la première « mise à niveau » de base de données que la plupart des équipes devraient faire ?
Pas le sharding. Généralement : corriger l’indexation, adopter le pooling de connexion, implémenter sauvegarde+PITR, et construire un processus de migration discipliné. Le sharding est un style de vie opérationnel, pas un feature flag.
Prochaines étapes que vous pouvez réaliser cette semaine
- Notez vos 10 invariants principaux et marquez ceux qui doivent être vrais à l’écriture.
- Choisissez le système de référence pour chaque invariant (idéalement un seul). Déclarez tout le reste dérivé.
- Exécutez le playbook de diagnostic rapide sur votre douleur actuelle : est‑ce saturation, contention ou correction ?
- Faites un drill de restauration vers un environnement frais. Chronométrez‑le. Documentez les étapes. C’est votre vrai RTO.
- Tuez un job de réconciliation « temporaire » en déplaçant l’invariant dans une contrainte d’écriture ou une opération atomique.
- Rendez une requête banale : ajoutez le bon index, vérifiez avec
EXPLAIN (ANALYZE), et stabilisez la performance avec un test de régression.
La technologie de base de données ne vous sauvera pas d’une vérité mal définie. Mais un modèle de vérité clair vous épargnera la plupart des débats technologiques. Choisissez les systèmes comme vous choisissez la réponse aux incidents : par ce qui se passe quand ça casse, pas par ce qui se passe dans une démo.