Si vous avez déjà eu une conversation du type « mais la doc disait que c’était ACID » pendant que le pager continue de vibrer sur le bureau,
vous connaissez déjà le thème : les transactions ne sont pas une fonctionnalité isolée, ce sont un comportement système. Et le comportement système
a des aspérités — surtout quand la réplication, le basculement, les caches et les re-exécutions applicatives s’en mêlent.
PostgreSQL et MongoDB prennent tous deux en charge les transactions. Ils offrent chacun des réglages d’isolation. Ils revendiquent tous deux la durabilité.
En production, la différence porte moins sur « qui a des transactions » que sur ce que vous obtenez réellement
quand le réseau tousse, que le primaire cède sa place, que le disque cale, et que votre appli fait la chose la moins aidante : réessayer.
Où la doc s’arrête et où commence la réalité
La documentation répond généralement à « qu’est-ce qui est supporté ». La production demande « que se passe-t-il quand ça casse ».
Les transactions ne se résument pas à BEGIN/COMMIT ; elles sont un contrat entre :
le moteur de stockage, le protocole de réplication, le comportement de basculement, les pilotes clients et votre logique de retry.
Le modèle transactionnel de PostgreSQL est plus ancien, plus strict et très opiniâtre. Il est construit autour de MVCC et du WAL,
et il a tendance à échouer de manières reconnaissables : contention de verrous, bloat, latence de réplication, conflits en hot standby.
L’histoire transactionnelle de MongoDB est arrivée plus tard et repose sur un replica set et un moteur de documents.
Elle peut être parfaitement correcte — jusqu’au mélange avec write concerns, stepdowns, transactions longues,
et des schémas de charge qui s’opposent à son design.
Voici la vérité opérationnelle : si votre système a besoin d’invariants multi-entités, d’unicité correcte et
de rollbacks prévisibles sous concurrence, PostgreSQL est le choix par défaut. MongoDB peut faire des transactions,
mais vous les payez, et vous devez être explicite sur la durabilité et la sémantique de lecture.
Si vous traitez « supporte les transactions » comme « fonctionne comme Postgres », vous préparez un piège pour votre futur vous.
Bref historique : pourquoi ces systèmes se comportent ainsi
Les transactions ne sont pas un accessoire de mode. Elles sont le produit de décennies de douleur. Quelques points de contexte qui
expliquent les compromis actuels :
- PostgreSQL descend de POSTGRES (fin des années 1980), conçu à l’époque académique où la correction était un objectif majeur, pas une option commerciale.
- Le MVCC de PostgreSQL est devenu sa stratégie de concurrence définissante : les lecteurs ne bloquent pas les écrivains, mais le vacuum devient votre colocataire silencieux qui dévore votre CPU la nuit.
- Le WAL (Write-Ahead Logging) est l’épine dorsale de la durabilité de Postgres ; c’est pourquoi la récupération après crash est ennuyeuse dans le bon sens.
- MongoDB a commencé (fin des années 2000) en mettant l’accent sur l’agilité développeur et la flexibilité documentaire ; les premières versions comptaient fortement sur l’atomicité au niveau du document.
- Les replica sets sont devenus le centre opérationnel de MongoDB ; « primary » et « secondaries » ne sont pas que de la topologie, ils définissent la sémantique de lecture/écriture.
- Les transactions multi-documents de MongoDB sont arrivées plus tard (4.0+), d’abord pour les replica sets, puis pour les clusters sharded — implémentées en coordonnant des écritures avec de la tenue d’état additionnelle.
- Le « majority write concern » existe parce que la réplication asynchrone n’est pas de la durabilité ; c’est de l’optimisme avec une bonne équipe marketing.
- Par défaut, PostgreSQL utilise Read Committed, ce qui surprend les développeurs s’attendant à un comportement sérialisable par défaut.
- Les niveaux de read concern de MongoDB ont évolué pour répondre à « qu’ai-je réellement lu » dans un monde répliqué (local, majority, snapshot, linearizable).
Ces histoires importent parce qu’elles prédisent les modes de défaillance. Postgres tend à prioriser la correction d’abord,
puis vous oblige à tuner les performances. MongoDB tend à faciliter la montée en charge et l’évolution du schéma,
puis vous laisse choisir combien de correction vous êtes prêt à acheter à l’exécution.
Modèle de transaction PostgreSQL : MVCC, verrous et WAL
MVCC signifie « les lectures voient un snapshot », pas « les verrous n’existent pas »
PostgreSQL utilise MVCC : chaque version de ligne possède des métadonnées de visibilité, et une transaction voit un instantané de ce qui est
commis (à un moment donné, selon l’isolation). Les lectures ne bloquent pas les écritures parce que les lecteurs lisent des versions
anciennes des lignes. Les écrivains créent de nouvelles versions. C’est élégant, mais cela entraîne des conséquences :
tuples morts, besoin de vacuum, et transactions longues qui empêchent le nettoyage.
Les verrous restent importants. Mettre à jour une ligne prend des verrous au niveau ligne. Les DDL prennent des verrous plus lourds.
L’unicité est appliquée avec un comportement de verrouillage d’index sûr mais pouvant surprendre sur des clés très sollicitées.
Et si vous poussez la concurrence tout en gardant des verrous trop longtemps, Postgres vous affichera patiemment une file de misère.
Niveaux d’isolation en pratique
PostgreSQL supporte Read Committed, Repeatable Read et Serializable.
En production, la plupart des systèmes tournent en Read Committed et s’appuient sur des contraintes et des requêtes soignées.
Serializable est réel, mais il est optimiste : il peut aborter des transactions avec des erreurs de sérialisation sous contention.
Ce n’est pas un bug ; c’est la base de données qui vous dit « votre histoire de concurrence n’est pas sérialisable ».
Le moment où « la réalité diffère des docs » arrive quand une équipe active Serializable sans implémenter de retries pour
SQLSTATE 40001. Ensuite elle apprend que la correction exige une coopération.
Durabilité WAL : COMMIT est une histoire de disque, pas une histoire de sentiments
Dans PostgreSQL, la durabilité repose fondamentalement sur le flush du WAL. Les réglages sont
synchronous_commit, wal_sync_method, et le comportement du système de fichiers et du stockage sous-jacent.
La réplication ajoute une couche : le commit est-il accusé de réception par le primaire seulement, ou aussi par les réplicas ?
Quand quelqu’un dit « Postgres a perdu des données commises », c’est généralement l’un des cas suivants :
basculement de réplication asynchrone, durabilité délibérément relâchée (synchronous_commit=off),
une pile de stockage qui a menti (cache d’écriture sans barrières appropriées), ou une erreur humaine.
Le super-pouvoir de Postgres : les contraintes sont appliquées transactionnellement
Clés étrangères, contraintes d’unicité, contraintes d’exclusion, contraintes CHECK. Vous pouvez modéliser les invariants près des données et compter sur eux sous charge concurrente.
Ce n’est pas juste de la commodité ; c’est réduire la surface des conditions de course au niveau applicatif.
Modèle de transaction MongoDB : sessions, write concern et replica sets
Les transactions MongoDB existent — mais elles ne sont pas gratuites
Les transactions multi-documents de MongoDB (replica set et sharded) fournissent des sémantiques ACID à l’intérieur de la transaction.
Le « à l’intérieur » fait beaucoup de travail ici. Le moteur maintient un état additionnel, et le coordinateur doit gérer
le commit entre participants (surtout dans les déploiements sharded). La latence augmente. Le débit baisse souvent.
Sous contention, vous pouvez voir plus d’aborts et d’erreurs transitoires qui nécessitent des retries.
Si votre charge MongoDB est majoritairement composée d’opérations mono-document, Mongo peut être extrêmement rapide et agréable opérationnellement.
Au moment où vous vous appuyez fortement sur des transactions multi-documents pour la correction fondamentale, vous demandez à MongoDB
de se comporter comme une base relationnelle — tout en payant le coût d’être une boutique de documents distribuée.
Write concern : la différence entre « acquitté » et « suffisamment durable »
Le write concern de MongoDB définit ce que signifie « succès » pour une écriture. Le piège classique : utiliser
w:1 (acquitté par le primaire) et supposer que cela signifie que l’écriture survivra au basculement.
Cela peut être vrai. Ça peut aussi ne pas l’être, selon le timing et la réplication.
Pour la durabilité face à une perte du primaire, vous voulez typiquement w:majority plus des réglages de journalisation sensés
(la journalisation est activée par défaut dans les versions modernes, mais vérifiez). Puis vous découvrez la réalité suivante :
majority peut être plus lent, surtout avec des secondaires lents ou des déploiements multi-zone.
Read concern : qu’ai-je vraiment lu ?
Le read concern de MongoDB contrôle les garanties de visibilité : local, majority,
snapshot et linearizable (avec des contraintes). Le read preference ajoute un autre axe :
lectures sur le primaire vs secondaires.
En production, l’erreur courante est de lire depuis des secondaires pour « scaler » tout en conservant les écritures sur le primaire,
puis d’être surpris quand les utilisateurs voient les données « reculer » ou quand une transaction lit quelque chose qui
disparaît ensuite après rollback. Si vous traitez le lag de réplication comme une fonctionnalité, il traitera votre correction comme optionnelle.
Sessions, retries et la taxe du « résultat de commit inconnu »
Les pilotes MongoDB implémentent des écritures retryables et un comportement de retry pour les transactions. Bien — jusqu’à ce que ça ne le soit plus.
Vous pouvez avoir des cas où le client expire et ne sait pas si le commit a réussi.
Ensuite votre application réessaye et crée accidentellement des doublons à moins que vous n’ayez conçu l’idempotence.
Ce n’est pas que MongoDB est mauvais ; c’est que les systèmes distribués sont des systèmes distribués. Mais vous devez en tenir compte.
Sémantiques importantes : matrice de comparaison pratique
1) Portée de l’atomicité
PostgreSQL : atomicité à travers toutes les lignes/tables touchées par la transaction dans la base de données.
MongoDB : atomicité au niveau du document unique par défaut ; l’atomicité multi-document existe via des transactions, avec un surcoût
et des mises en garde opérationnelles (surtout à travers des shards).
2) Valeurs par défaut d’isolation et surprises pour les développeurs
PostgreSQL par défaut Read Committed : chaque instruction voit un snapshot au début de l’instruction. Beaucoup d’anomalies sont évitées
par les contraintes et une structure de requêtes soignée, mais vous pouvez encore écrire des courses si vous supposez un comportement « repeatable ».
MongoDB : en dehors des transactions, vous êtes surtout dans un monde opératif par opération avec read concern et write concern.
À l’intérieur des transactions, vous pouvez obtenir un comportement d’isolation par snapshot. La surprise vient souvent de ce que « snapshot » signifie
par rapport à la réplication et au read preference.
3) Sémantiques de durabilité lors d’un basculement
PostgreSQL commit sur le primaire uniquement est durable sur ce nœud une fois le WAL flushé (sous réserve de l’honnêteté du stockage).
Mais si vous basculez vers un réplica asynchrone, vous pouvez perdre des transactions acquittées qui n’avaient pas été répliquées.
MongoDB : une écriture acquittée avec w:1 peut être annulée lors d’un basculement. Avec w:majority,
le risque de rollback est fortement réduit car l’écriture avait été répliquée à une majorité.
4) « Lire après écriture » et lectures monotoniques
PostgreSQL sur un nœud unique : oui, au sein de la même transaction et session. Avec des replicas, cela dépend de votre routage ; si vous
lisez depuis des réplicas vous pouvez voir du retard.
MongoDB : si vous lisez depuis des secondaires sans sessions cohérentes causalement (et des réglages corrects), vous pouvez lire des données périmées.
Même avec des sessions, les changements de topologie peuvent compliquer les garanties. Le système peut être correct ; vos hypothèses peuvent ne pas l’être.
5) Observabilité
PostgreSQL expose l’état transactionnel et le verrouillage très directement via les catalogues système et les vues.
MongoDB expose l’état via serverStatus/currentOp, le profiler et les métriques de réplication.
Les deux sont observables. Postgres tend à vous donner des images plus claires de « qui bloque qui » ; MongoDB vous oblige à
raisonner sur la santé du replica set et la satisfaction des write concern.
6) Rayon d’impact opérationnel
Postgres : une mauvaise requête peut verrouiller une table, engendrer du bloat ou saturer l’I/O ; les défaillances sont souvent localisées à ce nœud,
la latence de réplication étant un symptôme secondaire.
MongoDB : un membre de replica set malade, des secondaires lents ou des élections peuvent transformer des « écritures rapides » en « pourquoi tout timeout ».
Dans les clusters sharded, les transactions cross-shard peuvent répandre la douleur rapidement.
Modes de défaillance rencontrés à 2 h du matin
PostgreSQL : contention de verrous et « pourquoi COMMIT est lent ? »
La latence de COMMIT dans Postgres est habituellement la latence du stockage (fsync), la contention WAL, ou l’attente de réplication synchrone.
Ce n’est rarement « Postgres est lent » de façon abstraite. C’est presque toujours « votre stockage ou vos réglages de durabilité rendent
vos sémantiques désirées coûteuses ».
Un autre classique : une transaction longue empêche le vacuum de nettoyer les tuples morts, boursoufle les index,
et ensuite tout devient lent d’une façon qui ressemble à un « meltdown I/O aléatoire ».
MongoDB : élections, attentes de write concern et retries de transactions
Dans MongoDB, la douleur opérationnelle vient souvent du replica set qui fait son travail :
des élections ont lieu, les primaires se retirent, et les clients voient des erreurs transitoires. Si votre application ne gère pas ça bien,
votre incident devient « la base de données est en panne » alors qu’en réalité « vos retries sont un DDoS contre votre propre primaire ».
Les transactions peuvent amplifier cela. Une transaction maintenue ouverte en faisant beaucoup de travail garde des ressources occupées
et augmente la probabilité que quelque chose change sous vos pieds (comme un stepdown), forçant un abort et un retry.
La vérité distribuée : vous ne pouvez pas éviter l’ambiguïté
Les deux systèmes peuvent vous laisser dans l’ambiguïté « est-ce que ça a commit ? » quand le client perd la réponse.
Postgres vous laisse typiquement implémenter l’idempotence côté application.
MongoDB rend le problème plus explicite avec les écritures retryables et le comportement de commit des transactions — mais vous devez
quand même en concevoir la prise en charge.
Une citation à garder sur votre mur, parce qu’elle reste vraie quel que soit votre choix de base :
L’espoir n’est pas une stratégie.
—Gene Kranz
Blague #1 : Une transaction, c’est comme une promesse : c’est rassurant jusqu’à ce que vous deviez l’appliquer devant un tribunal, aka la production.
Trois mini-histoires du monde entreprise
Incident : une mauvaise hypothèse sur les « écritures acquittées »
Une entreprise SaaS de taille moyenne utilisait MongoDB pour les profils utilisateurs et l’état de facturation. Le schéma était propre, le code moderne,
et l’équipe avait la mauvaise habitude de lire « acquitté » comme « durable ». Les écritures utilisaient le write concern par défaut, et l’appli
lisait depuis le primaire. Tout semblait correct en staging, et correct pendant des mois en production.
Puis une fenêtre de maintenance routinière a coïncidé avec un incident réseau entre zones de disponibilité. Le primaire a accepté des écritures,
les a acquittées, puis s’est retiré pendant une élection. Certaines de ces écritures de dernière seconde n’avaient pas été répliquées
à une majorité. Un nouveau primaire a été élu qui ne les avait pas.
L’incident n’était pas une panne dramatique. C’était pire : une poignée d’actions clients a « disparu ». Des tickets de support sont arrivés
avec des captures d’écran. L’ingénierie a d’abord chassé des fantômes dans le frontend parce que les logs backend montraient des écritures réussies.
Les données n’étaient simplement plus là.
La correction a été opérationnelle et architecturale. Ils ont basculé les écritures critiques vers w:majority (avec des timeouts ajustés),
ont fait en sorte que certaines lectures utilisent readConcern: "majority" pour l’état critique de facturation, et ajouté des clés d’idempotence
pour que les retries ne dupliquent pas les effets secondaires. Les performances ont légèrement chuté ; la correction a massivement augmenté.
Optimisation qui s’est retournée contre eux : « accélérons les commits Postgres »
Une équipe fintech utilisait PostgreSQL pour un grand ledger intensif en écritures. Ils chassaient une régression de latence p99 et ont remarqué que
la latence de commit corrélait avec des pics I/O. Quelqu’un a proposé l’astuce classique :
mettre synchronous_commit=off pour les écritures « non critiques » et compter sur la réplication.
Ça a marché. La latence s’est améliorée instantanément. Le débit a augmenté. Les graphiques ressemblaient à un dossier de promotion.
Puis ils ont subi un événement électrique non propre sur le primaire. La machine a redémarré. Le WAL n’avait pas été flush pour certains commits.
La base a récupéré correctement — c’est-à-dire qu’elle a rollbacké ces transactions parce que, selon les nouveaux réglages, elles n’étaient jamais
garanties durables.
L’impact business n’a pas été catastrophique, mais il a été humiliant : des actions « confirmées » ont dû être réconciliées.
L’équipe a appris que les raccourcis de durabilité ne sont pas gratuits ; ce sont des conséquences différées.
Ils ont remis le réglage, déplacé le WAL sur un stockage plus rapide, et utilisé du traitement asynchrone pour les événements vraiment non critiques
au lieu d’affaiblir la durabilité du ledger central.
Blague #2 : Désactiver la durabilité pour accélérer les commits, c’est comme enlever l’alarme incendie parce qu’elle bippe trop fort.
Pratique ennuyeuse mais correcte : conception axée contraintes qui a sauvé la mise
Une plateforme B2B stockait les commandes dans PostgreSQL et avait un worker de fond qui « finalisait » les commandes en créant des factures,
en décrémentant l’inventaire et en envoyant des emails. Le workflow était distribué entre services, parce que bien sûr.
L’équipe a fait quelque chose d’impopulaire : elle a modélisé les invariants dans la base. Les décréments d’inventaire étaient contraints pour
ne jamais passer sous zéro. Les numéros de facture étaient uniques via un index strict. La machine d’état de la commande était protégée par des contraintes
CHECK et les transitions appliquées en SQL transactionnel.
Lors d’un déploiement, un bug a fait tourner le worker deux fois pour un sous-ensemble de commandes. Dans beaucoup de systèmes, cela devient un chantier
de nettoyage de plusieurs jours. Ici, la seconde exécution a échoué majoritairement vite avec des violations de contraintes.
Le service a logué des erreurs, les SRE ont repéré le pic, et les clients n’ont pour la plupart rien remarqué.
Le postmortem a été court et presque joyeux : la base de données avait agi comme un disjoncteur. La correction a été de réparer le bug d’ordonnancement
du job et d’améliorer l’idempotence, mais les contraintes avaient déjà prévenu la corruption financière et des stocks.
L’ennuyeux peut être beau.
Tâches pratiques : commandes, sorties et décisions (12+)
Voici les vérifications que j’exécute quand quelqu’un dit « les transactions sont lentes » ou « nous avons perdu des données » ou « c’est incohérent ».
Chaque tâche inclut : une commande, un extrait de sortie réaliste, ce que cela signifie, et la décision à prendre.
Task 1 (PostgreSQL) : Confirmer les réglages d’isolation et de durabilité
cr0x@server:~$ psql -h pg-primary -U postgres -d app -c "SHOW default_transaction_isolation; SHOW synchronous_commit; SHOW wal_level;"
default_transaction_isolation
-----------------------------
read committed
(1 row)
synchronous_commit
--------------------
on
(1 row)
wal_level
-----------
replica
(1 row)
Signification : Read Committed par défaut ; les commits attendent le flush du WAL ; WAL configuré pour la réplication.
Décision : Si vous déboguez des anomalies, confirmez si l’appli suppose Repeatable Read/Serializable.
Si la latence de commit est élevée, laissez synchronous_commit=on sauf si vous acceptez explicitement la perte de données.
Task 2 (PostgreSQL) : Identifier qui bloque qui
cr0x@server:~$ psql -h pg-primary -U postgres -d app -c "SELECT blocked.pid AS blocked_pid, blocked.query AS blocked_query, blocking.pid AS blocking_pid, blocking.query AS blocking_query FROM pg_stat_activity blocked JOIN pg_locks blocked_locks ON blocked_locks.pid = blocked.pid JOIN pg_locks blocking_locks ON blocking_locks.locktype = blocked_locks.locktype AND blocking_locks.database IS NOT DISTINCT FROM blocked_locks.database AND blocking_locks.relation IS NOT DISTINCT FROM blocked_locks.relation AND blocking_locks.page IS NOT DISTINCT FROM blocked_locks.page AND blocking_locks.tuple IS NOT DISTINCT FROM blocked_locks.tuple AND blocking_locks.virtualxid IS NOT DISTINCT FROM blocked_locks.virtualxid AND blocking_locks.transactionid IS NOT DISTINCT FROM blocked_locks.transactionid AND blocking_locks.classid IS NOT DISTINCT FROM blocked_locks.classid AND blocking_locks.objid IS NOT DISTINCT FROM blocked_locks.objid AND blocking_locks.objsubid IS NOT DISTINCT FROM blocked_locks.objsubid AND blocking_locks.pid != blocked_locks.pid JOIN pg_stat_activity blocking ON blocking.pid = blocking_locks.pid WHERE NOT blocked_locks.granted;"
blocked_pid | blocked_query | blocking_pid | blocking_query
------------+------------------------------+--------------+-------------------------------
24190 | UPDATE orders SET ... | 23811 | ALTER TABLE orders ADD COLUMN
(1 row)
Signification : Un DDL bloque une écriture. Ce n’est pas « les transactions sont lentes », c’est « quelqu’un a pris un verrou lourd ».
Décision : Stoppez le DDL s’il est dangereux, ou planifiez-le correctement. Utilisez des builds d’index concurrents et des migrations en ligne.
Task 3 (PostgreSQL) : Vérifier les transactions longues qui empêchent le vacuum
cr0x@server:~$ psql -h pg-primary -U postgres -d app -c "SELECT pid, now() - xact_start AS xact_age, state, query FROM pg_stat_activity WHERE xact_start IS NOT NULL ORDER BY xact_age DESC LIMIT 5;"
pid | xact_age | state | query
-------+------------+--------+----------------------------------------
31245 | 02:13:08 | idle | BEGIN;
29902 | 00:18:41 | active | SELECT ... FROM events ORDER BY ...
(2 rows)
Signification : Une transaction idle est ouverte depuis des heures. Cela fige d’anciennes versions de lignes et peut causer du bloat.
Décision : Corrigez l’application : assurez-vous que les transactions sont courtes et toujours commit/rollback.
Envisagez idle_in_transaction_session_timeout.
Task 4 (PostgreSQL) : Vérifier la pression WAL et les checkpoints
cr0x@server:~$ psql -h pg-primary -U postgres -d app -c "SELECT checkpoints_timed, checkpoints_req, checkpoint_write_time, checkpoint_sync_time FROM pg_stat_bgwriter;"
checkpoints_timed | checkpoints_req | checkpoint_write_time | checkpoint_sync_time
------------------+-----------------+-----------------------+----------------------
1021 | 487 | 98765432 | 1234567
(1 row)
Signification : Beaucoup de checkpoints demandés ; le temps d’écriture est élevé. La pression sur les checkpoints peut nuire à la latence des commits et à l’I/O.
Décision : Ajustez max_wal_size, checkpoint_timeout, et assurez-vous que le stockage peut gérer WAL et écritures de données.
Task 5 (PostgreSQL) : Mesurer le lag de réplication et la fenêtre de risque
cr0x@server:~$ psql -h pg-primary -U postgres -d app -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
------------------+-----------+------------+-----------+-----------+------------
pg-replica-1 | streaming | async | 00:00:02 | 00:00:03 | 00:00:05
(1 row)
Signification : Le réplica est en async et a quelques secondes de retard. Un basculement peut perdre quelques secondes de commits.
Décision : Si vous ne pouvez pas tolérer cela, implémentez la réplication synchrone pour les clusters critiques,
ou changez la politique de basculement pour éviter de promouvoir des réplicas en retard.
Task 6 (PostgreSQL) : Détecter les échecs de sérialisation et les besoins de retry
cr0x@server:~$ psql -h pg-primary -U postgres -d app -c "SELECT datname, xact_commit, xact_rollback, conflicts FROM pg_stat_database WHERE datname='app';"
datname | xact_commit | xact_rollback | conflicts
---------+-------------+---------------+-----------
app | 98765432 | 123456 | 842
(1 row)
Signification : Des rollbacks/conflits existent. Tous les rollbacks ne sont pas mauvais, mais des pics peuvent indiquer des échecs de sérialisation ou des deadlocks.
Décision : Corrélez avec les erreurs applicatives (SQLSTATE 40001, 40P01). Ajoutez de la logique de retry avec jitter et réduisez les hotspots de contention.
Task 7 (MongoDB) : Inspecter la santé du replica set et le churn d’élections
cr0x@server:~$ mongosh --host rs0/mb-primary,mb-secondary-1,mb-secondary-2 --eval "rs.status().members.map(m=>({name:m.name,stateStr:m.stateStr,health:m.health,optime:m.optime.ts}))"
[
{ name: 'mb-primary:27017', stateStr: 'PRIMARY', health: 1, optime: Timestamp({ t: 1735550101, i: 1 }) },
{ name: 'mb-secondary-1:27017', stateStr: 'SECONDARY', health: 1, optime: Timestamp({ t: 1735550101, i: 1 }) },
{ name: 'mb-secondary-2:27017', stateStr: 'SECONDARY', health: 1, optime: Timestamp({ t: 1735550096, i: 1 }) }
]
Signification : Un secondaire a quelques secondes de retard. Cela compte pour la latence de w:majority et la fraîcheur des lectures depuis les secondaires.
Décision : Si les écritures majority sont lentes, enquêtez sur les membres en retard ; si des lectures secondaires sont utilisées,
envisagez des read concerns plus stricts ou un routage différent.
Task 8 (MongoDB) : Vérifier les write concern par défaut et la journalisation
cr0x@server:~$ mongosh --host mb-primary --eval "db.getMongo().getWriteConcern()"
{ w: 1 }
Signification : Le défaut est w:1. C’est « acquitté par le primaire », pas « survit à la perte du primaire ».
Décision : Pour les données critiques, mettez w:majority côté client ou au niveau collection, et utilisez des timeouts pour éviter des écritures bloquées.
Task 9 (MongoDB) : Vérifier les aborts de transactions et les tempêtes de retry
cr0x@server:~$ mongosh --host mb-primary --eval "db.serverStatus().transactions"
{
currentActive: Long('14'),
currentInactive: Long('3'),
currentOpen: Long('17'),
totalAborted: Long('982'),
totalCommitted: Long('184433'),
totalStarted: Long('185415')
}
Signification : Des aborts se produisent. Certains aborts sont normaux ; des pics durant des stepdowns ou sous contention ne le sont pas.
Décision : Si les aborts augmentent, raccourcissez les transactions, réduisez le travail inter-collections, et confirmez que le comportement de retry du driver
n’amplifie pas la charge.
Task 10 (MongoDB) : Trouver les opérations lentes qui maintiennent des transactions ouvertes
cr0x@server:~$ mongosh --host mb-primary --eval "db.currentOp({active:true, secs_running:{$gte:5}}).inprog.map(op=>({secs:op.secs_running,ns:op.ns,desc:op.desc,command:op.command && Object.keys(op.command)}))"
[
{ secs: 31, ns: 'app.orders', desc: 'conn12345', command: [ 'find' ] },
{ secs: 12, ns: 'app.inventory', desc: 'conn23456', command: [ 'update' ] }
]
Signification : Des ops actives tournent depuis longtemps. Dans des transactions, les ops longues augmentent la probabilité de conflits et l’utilisation des ressources.
Décision : Ajoutez des index, réduisez la taille des scans de documents, et limitez le travail dans les transactions. Si c’est une requête débridée, tuez-la délibérément.
Task 11 (MongoDB) : Confirmer le read preference et le read concern dans le chemin client
cr0x@server:~$ mongosh --host mb-primary --eval "db.getMongo().getReadPref()"
{ mode: 'secondaryPreferred' }
Signification : Les lectures peuvent aller sur les secondaires. C’est un choix de correction, pas un truc gratuit pour monter en charge.
Décision : Pour les parcours « je viens d’écrire », utilisez des lectures sur le primaire ou des sessions cohérentes causalement et un readConcern approprié.
Task 12 (PostgreSQL) : Confirmer le comportement de fsync et détecter tôt un stockage qui « ment »
cr0x@server:~$ psql -h pg-primary -U postgres -d app -c "SHOW fsync; SHOW full_page_writes; SHOW wal_log_hints;"
fsync
-------
on
(1 row)
full_page_writes
------------------
on
(1 row)
wal_log_hints
---------------
off
(1 row)
Signification : Le WAL est fsynced ; full page writes est activé (important pour la sécurité au crash).
Décision : Gardez ces réglages en production sauf raison très spécifique et contrôles compensatoires.
Si vous suspectez la pile de stockage, validez les réglages de cache au niveau matériel comme partie de la réponse à l’incident.
Task 13 (PostgreSQL) : Trouver les deadlocks et les statements qui les causent
cr0x@server:~$ psql -h pg-primary -U postgres -d app -c "SELECT deadlocks, conflicts FROM pg_stat_database WHERE datname='app';"
deadlocks | conflicts
-----------+-----------
19 | 842
(1 row)
Signification : Des deadlocks se sont produits. Postgres tuera un des participants ; votre appli verra une erreur et devra retry.
Décision : Identifiez les tables/requêtes (via les logs), standardisez l’ordre des verrous dans le code, et gardez les transactions petites.
Task 14 (MongoDB) : Vérifier le lag de réplication en secondes, pas en impressions
cr0x@server:~$ mongosh --host mb-primary --eval "rs.printSecondaryReplicationInfo()"
source: mb-secondary-1:27017
syncedTo: Mon Dec 30 2025 02:41:55 GMT+0000 (UTC)
0 secs (0 hrs) behind the primary
source: mb-secondary-2:27017
syncedTo: Mon Dec 30 2025 02:41:49 GMT+0000 (UTC)
6 secs (0 hrs) behind the primary
Signification : Un secondaire est à 6 secondes de retard. Cela affecte la vitesse de reconnaissance de la majorité et les lectures périmées.
Décision : Enquêtez sur ce nœud (disque, CPU, réseau). S’il est régulièrement en retard, il viendra hanter la latence de vos transactions.
Task 15 (PostgreSQL) : Confirmer les réglages de réplication synchrone si « pas de perte de données » est requis
cr0x@server:~$ psql -h pg-primary -U postgres -d app -c "SHOW synchronous_standby_names; SHOW synchronous_commit;"
synchronous_standby_names
--------------------------
'ANY 1 (pg-replica-1)'
(1 row)
synchronous_commit
--------------------
on
(1 row)
Signification : Le primaire attend au moins un standby synchrone. C’est ainsi que vous réduisez les surprises d’« acquitté mais perdu ».
Décision : Utilisez ceci pour les systèmes critiques, mais surveillez : cela couple la latence d’écriture à la santé des réplicas et la qualité du réseau.
Guide de diagnostic rapide
Quand le comportement des transactions est « étrange », ne commencez pas par débattre des bases de données. Commencez par localiser le goulot d’étranglement.
Voici l’ordre qui vous amène à une cause racine avant que l’invitation à la réunion n’arrive.
Première étape : déterminer si la douleur est latence, débit ou correction
- Pic de latence : commits lents, requêtes lentes, timeouts.
- Baisse de débit : croissance des files, backlog des workers, connexions en hausse.
- Bug de correction : écritures manquantes, doublons, lectures hors ordre.
Deuxième étape : vérifier le « contrat durabilité et réplication »
- PostgreSQL : la réplication est-elle asynchrone ? un basculement s’est-il produit ?
synchronous_commitest-il relâché ? le flush WAL est-il lent ? - MongoDB : quel est le write concern ? des élections ont-elles lieu ? des secondaires accusent-ils du retard ? les clients lisent-ils depuis les secondaires ?
Troisième étape : vérifier la contention
- PostgreSQL : verrous bloquants, deadlocks, transactions longues, hot rows, contention d’index.
- MongoDB : conflits de transactions, ops longues, attentes de write concern, surcharge du coordinateur de shard.
Quatrième étape : vérifier le stockage et la saturation de l’hôte
- La latence disque est la main invisible derrière « COMMIT est lent ».
- La saturation CPU peut ressembler à des « verrous » car tout ralentit et des files se forment.
- Le jitter réseau peut ressembler à une « instabilité de la DB » car la réplication et les élections sont sensibles aux timeouts.
Cinquième étape : confirmer le comportement du client
- Est-ce que vous réessayez à chaque erreur sans backoff ? Félicitations, vous avez construit un multiplicateur de panne.
- Les timeouts sont-ils plus courts que votre chemin de commit pendant un basculement ? Alors vous générez volontairement un « résultat de commit inconnu ».
- Mélangez-vous des read preferences et attendez-vous à « lire-après-écrire » ? Alors vous testez votre chance en production.
Erreurs courantes : symptômes → cause racine → correction
1) « Des données commises ont disparu après un basculement »
Symptômes : les logs applicatifs montrent des écritures réussies ; après basculement, les données sont manquantes.
Cause racine : basculement avec réplication asynchrone (Postgres), ou écritures MongoDB acquittées avec w:1 qui ont été rollbackées.
Correction : Pour Postgres, utilisez la réplication synchrone pour les données critiques ou évitez de promouvoir des réplicas en retard. Pour MongoDB, utilisez w:majority et concevez l’idempotence.
2) « Les transactions sont lentes seulement en période de pointe »
Symptômes : latence p95/p99 en hausse ; débit en baisse ; le CPU semble correct ; les utilisateurs timeoutent.
Cause racine : contention (verrous dans Postgres ; attentes de write concern ou conflits de transaction dans MongoDB), ou saturation fsync du stockage.
Correction : Identifiez les chaînes de blocage / ops longues. Réduisez le périmètre des transactions. Ajoutez ou corrigez des index. Déplacez WAL/journal sur un stockage plus rapide si nécessaire.
3) « Nous avons activé Serializable et tout a commencé à échouer »
Symptômes : erreurs Postgres avec échecs de sérialisation ; tempêtes de retry.
Cause racine : Serializable nécessite des retries ; la forte contention déclenche des aborts.
Correction : Implémentez des retries bornés avec jitter ; réduisez les hotspots ; envisagez Repeatable Read plus des contraintes si approprié.
4) « La transaction MongoDB s’aborte sans cesse avec des erreurs transitoires »
Symptômes : taux d’abort élevé ; stepdowns ; l’appli voit des erreurs transitoires de transaction.
Cause racine : élections, transactions longues, conflits, ou surcharge de coordination cross-shard.
Correction : Raccourcissez les transactions, évitez les transactions cross-shard sur les chemins chauds, ajustez les timeouts, et assurez-vous que la logique de retry du driver est correcte et limitée en débit.
5) « Les lectures sont incohérentes juste après des écritures »
Symptômes : un utilisateur met à jour son profil ; un rafraîchissement montre des données anciennes ; plus tard les données apparaissent.
Cause racine : lecture depuis des réplicas/secondaires ; read concern insuffisant ; lag de réplication.
Correction : Orientez les lectures « lire-après-écrire » vers le primaire, ou utilisez des sessions à cohérence causale et un read concern approprié ; surveillez le lag de réplication.
6) « L’utilisation disque de Postgres ne cesse de croître ; les requêtes ralentissent graduellement »
Symptômes : bloat, I/O en hausse, autovacuum à la traîne.
Cause racine : transactions longues et tables à mise à jour intensive produisant des tuples morts ; starvation du vacuum.
Correction : Éliminez les idle-in-transaction ; ajustez autovacuum par table ; envisagez le partitionnement ; planifiez des maintenances si nécessaire.
7) « Nous avons utilisé secondaryPreferred et maintenant nous avons des bugs ‘impossibles’ »
Symptômes : des compteurs reculent ; des transitions d’état semblent hors ordre.
Cause racine : lectures périmées depuis les secondaires ; les hypothèses sur les lectures monotoniques ne sont pas respectées.
Correction : Utilisez des lectures primaires pour les machines à états ; si vous utilisez des lectures secondaires, acceptez explicitement la staleness et concevez l’interface/logic autour.
8) « Notre logique de retry a aggravé l’incident »
Symptômes : pic de QPS et connexions pendant la panne ; la DB s’effondre davantage.
Cause racine : retries non bornés, sans jitter, retries sur des erreurs non retryables, et absence de clés d’idempotence.
Correction : Implémentez un backoff exponentiel borné avec jitter ; distinguez les erreurs retryables ; ajoutez des tokens d’idempotence ; envisagez des coupe-circuits.
Listes de contrôle / plan pas à pas
Checklist de décision : ce workload doit-il utiliser les transactions Postgres ou MongoDB ?
- Avez-vous besoin d’invariants cross-entity ? Si oui, privilégiez PostgreSQL. Si MongoDB, attendez-vous à utiliser des transactions multi-documents et à en payer le prix.
- Avez-vous besoin de garanties d’unicité fortes sur de nombreux documents ? PostgreSQL est simple. MongoDB nécessite un indexage et une conception de transaction soigneux.
- La perte de données au basculement est-elle inacceptable ? Postgres : réplication synchrone et politique de basculement prudente. MongoDB :
w:majorityet lectures majority quand nécessaire. - Votre pattern d’accès est-il majoritairement mono-document ? MongoDB excelle ; ne le transformez pas accidentellement en système relationnel via des transactions multi-documents constantes.
- Les développeurs vont-ils implémenter les retries de manière fiable ? Si non, évitez des réglages qui les forcent fréquemment (Serializable partout ; transactions MongoDB longues sous contention).
- Pouvez-vous mobiliser de l’expertise opérationnelle ? Les deux en nécessitent. L’expertise Postgres ressemble souvent à tuning de requêtes + vacuum + discipline de réplication. L’expertise MongoDB ressemble souvent à hygiène de replica set/shard + discipline write/read concern.
Plan d’implémentation : Postgres bien fait pour la correction transactionnelle
- Modélisez les invariants avec des contraintes (FK, uniques, CHECKs).
- Gardez les transactions courtes ; évitez le modèle « transaction comme workflow » bavard.
- Implémentez l’idempotence pour les effets secondaires externes (emails, paiements, webhooks).
- Choisissez le niveau d’isolation intentionnellement ; ajoutez des retries si vous utilisez Serializable.
- Décidez de votre consistance au basculement : async (perte possible) vs synchrone (latence plus élevée).
- Instrumentez : temps d’attente de verrou, deadlocks, lag de réplication, timing des checkpoints, latence fsync à l’hôte.
- Mettez des garde-fous :
statement_timeout,idle_in_transaction_session_timeout, limites de pool de connexions.
Plan d’implémentation : transactions MongoDB sans auto-sabotage
- Favorisez les patterns atomiques mono-document quand c’est possible (embed, opérateurs atomiques).
- Quand vous avez besoin de transactions multi-documents, gardez-les courtes et peu nombreuses en nombre de documents.
- Définissez le write concern explicitement pour les écritures critiques (
w:majority+ timeout). - Choisissez le read concern et le read preference intentionnellement ; ne mélangez pas « lectures secondaires » avec des attentes de correction stricte.
- Implémentez des clés d’idempotence ; concevez pour le « résultat de commit inconnu ».
- Surveillez élections, lag de réplication, aborts de transaction, et métriques de verrous/queues.
- Dans les clusters sharded, évitez les transactions cross-shard sur les chemins chauds si vous aimez dormir.
FAQ
1) Les transactions MongoDB sont-elles « de vraies ACID » ?
À l’intérieur de la transaction, oui : atomicité et isolation sont fournies, et la durabilité dépend du write concern et de la journalisation.
Le piège en production est que votre topologie de cluster et votre write concern déterminent ce que « durable » signifie lors d’un basculement.
2) PostgreSQL garantit-il l’absence de perte de données ?
Sur un nœud unique avec des réglages durables et un stockage honnête, les transactions commises survivent aux crashs.
Dans un setup répliqué, la politique de basculement importe : promouvoir un réplica asynchrone peut perdre des transactions commises.
3) Quelle est l’erreur la plus courante avec les transactions MongoDB ?
Supposer que le write concern par défaut implique des commits survivables. Si vous avez besoin d’une durabilité sûre au basculement, soyez explicite avec w:majority.
4) Quelle est l’erreur la plus courante avec les transactions PostgreSQL ?
Garder les transactions ouvertes trop longtemps (souvent « idle in transaction »). Cela cause du bloat, la rétention de verrous, et un effondrement des performances qui semble sans rapport — jusqu’à ce que ce ne le soit plus.
5) PostgreSQL Serializable est-il identique à « exécution strictement sérielle » ?
Il vise un comportement sérialisable mais utilise une approche optimiste qui peut aborter des transactions.
Vous devez gérer correctement les retries, sinon vous transformerez la correction en indisponibilité.
6) MongoDB peut-il fournir des lectures linéarisables ?
MongoDB supporte les lectures linéarisables dans des scénarios limités, typiquement depuis le primaire avec des réglages spécifiques.
Elles sont plus lentes et plus restrictives ; utilisez-les seulement quand vous avez vraiment besoin de cette garantie.
7) Pourquoi les transactions multi-documents MongoDB sont-elles plus lentes que les opérations mono-document ?
Parce que la base doit coordonner plusieurs écritures, suivre l’état de la transaction, et garantir le commit atomique.
Dans les clusters sharded, le coût de coordination augmente encore.
8) Pourquoi Postgres « gèle » parfois pendant des changements de schéma ?
Beaucoup d’opérations DDL prennent des verrous lourds qui bloquent lectures/écritures.
Utilisez des patterns de migration plus sûrs (indexes concurrents, migrations en phases, et évitez les gros verrous aux heures de pointe).
9) Si j’utilise MongoDB avec w:majority, suis-je en sécurité ?
Plus sûr, pas magiquement sûr. Vous avez toujours besoin d’une logique de retry correcte, d’idempotence, et d’un plan pour les timeouts et résultats inconnus.
Majority augmente aussi la sensibilité aux secondaires lents et aux problèmes réseau.
10) Si j’utilise la réplication synchrone Postgres, suis-je en sécurité ?
Vous réduisez le risque de perdre des commits acquittés, mais vous tradez contre une latence de commit plus élevée et une dépendance à la santé des réplicas.
Vous avez toujours besoin d’une discipline opérationnelle : surveillance, capacity planning, et basculement testé.
Conclusion : prochaines étapes réalisables
Arrêtez de traiter les transactions comme une case à cocher. Traitez-les comme un contrat que vous devez vérifier en cas de défaillance.
PostgreSQL et MongoDB peuvent tous deux exécuter des systèmes transactionnels fiables, mais ils promettent des choses différentes par défaut,
et ils punissent différents types de paresse.
- Écrivez vos exigences de correction : ce qui peut être périmé, ce qui peut être perdu, ce qui doit être unique, ce qui doit être atomique.
- Rendez la durabilité explicite : mode de réplication et politique de basculement pour Postgres ; write concern et read concern pour MongoDB.
- Exercez la défaillance : simulez des stepdowns, tuez des clients en plein commit, et vérifiez que l’application gère l’ambiguïté sans duplication.
- Instrumentez la vérité : attentes de verrou, aborts, lag de réplication, latence fsync, élections. Si vous ne pouvez pas le voir, vous ne pouvez pas le maîtriser.
- Choisissez la base qui correspond à vos invariants : si vous avez besoin de contraintes relationnelles et de correction cross-row, privilégiez Postgres. Si votre modèle est document-first et majoritairement mono-document atomique, MongoDB est un excellent choix — mais ne faites pas semblant que c’est Postgres.