La plupart des pannes de base de données ne sont pas causées par un bug inédit. Elles proviennent d’une hypothèse que vous ne saviez pas avoir faite — parce que la valeur par défaut de la base donnait l’impression que tout était sûr. Vous déployez un code qui « fonctionne » en staging, puis la production vous apprend une nouvelle définition du mot « fonctionne ».
MariaDB et PostgreSQL sont tous deux excellents. Ils ont cependant des opinions opposées. Les valeurs par défaut de MariaDB tendent à maintenir l’application en marche, même si les données deviennent un peu… interprétatives. Les valeurs par défaut de PostgreSQL ont plus de chances d’arrêter le train en gare et de vous demander des explications. Cette différence n’est pas académique. Elle change la fréquence des incidents, le temps de débogage et les erreurs que votre équipe est autorisée à commettre.
La thèse : pardonner vs sanctionner
En production, « pardonner » ne signifie pas « sympathique ». Cela signifie que la base de données va tenter d’interpréter votre intention, et que cette interprétation peut être différente de ce que vos auditeurs, vos analystes ou votre futur vous souhaitiez.
MariaDB (surtout dans l’univers compatible MySQL) a une longue tradition de permissivité : coercition de types, acceptation de valeurs de date bizarres selon les modes, troncature silencieuse, et en général tout faire pour ne pas casser les applications. PostgreSQL a tendance à être plus strict : il lève des erreurs quand il y a ambiguïté, exige des conversions explicites et vous force à affronter les contraintes tôt. L’un maintient l’application en ligne. L’autre préserve l’intégrité des données.
Les deux approches peuvent être correctes. L’astuce est de choisir le mode d’échec que vous préférez :
- Ambiance par défaut de MariaDB : « On enregistrera quelque chose ; ne réveillez personne. » Utile quand l’uptime est roi et que vous avez une validation solide en amont. Dangereux quand l’exactitude des données est primordiale et que ce n’est pas le cas.
- Ambiance par défaut de PostgreSQL : « C’est ambigu ; corrigez votre code. » Utile quand la correction compte et que vous voulez que les erreurs remontent tôt. Dangereux quand vous ne pouvez pas tolérer des erreurs d’écriture et que votre application ne sait pas gérer les retry correctement.
Blague 1 : MariaDB ressemble parfois à une base de données qui répond « bien sûr » comme un barman fatigué. Vous le regretterez au matin.
Si vous construisez des systèmes qui touchent à l’argent, au stock, au contrôle d’accès, à la conformité, ou à tout ce qui finit par une réunion avec le service juridique : préférez « sanctionner les erreurs tôt ». Si vous gérez un pipeline d’ingestion d’événements à fort volume avec une validation solide aux frontières : les valeurs permissives peuvent convenir, tant que vous les rendez explicites et observables.
Faits et histoire intéressants (les éléments encore pertinents)
- MariaDB existe à cause de la crainte d’une acquisition : elle a été créée après le rachat de MySQL (Sun, puis Oracle) pour maintenir un fork dirigé par la communauté avec la compatibilité comme promesse centrale.
- Les racines de PostgreSQL sont académiques mais sa culture est conservatrice : il descend des recherches POSTGRES ; le projet a historiquement privilégié l’exactitude et les standards plutôt que le « tout ce qui marche ».
- Les comportements permissifs de MySQL ont façonné un écosystème : les ORMs et les bases de code applicatives ont appris à compter sur des coercitions silencieuses. Migrer ces applications vers Postgres révèle souvent des « bugs » qui existaient déjà.
- InnoDB est devenu le moteur par défaut car MyISAM perdait la bataille : transactions et récupération après crash cessent d’être optionnelles après votre première vraie panne.
- Le MVCC de PostgreSQL est central à son récit opérationnel : il évite le blocage lecteur/écrivain dans de nombreux cas, mais il « coûte » des besoins de vacuum et du risque de bloat si vous négligez l’entretien.
- L’écosystème MariaDB s’est fragmenté : fonctionnalités et valeurs par défaut ont divergé entre MariaDB, MySQL et Percona Server ; les recettes opérationnelles ne sont pas toujours portables même si la syntaxe SQL l’est.
- La culture d’extensions de Postgres est une superpuissance : des capacités opérationnelles clés arrivent souvent sous forme d’extensions (pour les stats, l’indexation, l’outillage de partitionnement). C’est de la flexibilité, mais aussi plus de réglages à maîtriser.
- La conformité aux standards SQL est un outil pratique, pas un trophée : Postgres s’en rapproche davantage ; cela rend le comportement des requêtes plus prévisible lors des migrations et entre drivers.
Valeurs par défaut qui décident de votre sort
Modes SQL et coercitions « utiles »
S’il y a une raison pour laquelle MariaDB paraît indulgent, c’est qu’il acceptera souvent votre entrée et la reformera en quelque chose de stockable. Parfois il avertit ; parfois vous ne le remarquerez pas parce que votre bibliothèque cliente ne remonte pas les warnings. Votre monitoring ne les suit probablement pas non plus.
MariaDB : le comportement est fortement influencé par sql_mode. Sans modes stricts, des INSERTs qui débordent, tronquent ou contiennent des valeurs invalides peuvent réussir avec des warnings. En modes stricts, beaucoup de ces cas deviennent des erreurs bloquantes. La base peut être indulgente ou punitive ; le problème est que beaucoup d’environnements ne font pas ce choix explicitement.
PostgreSQL : tend à lever des erreurs sur les conversions invalides, valeurs hors plage, timestamps invalides, etc. Cela est opérationnellement « bruyant », mais c’est aussi la façon d’empêcher que de mauvaises données ne deviennent un incident silencieux qui ressortira dans les analytics six mois plus tard.
Ce que cela change en pratique :
- Dans MariaDB, vous devez traiter
sql_modecomme un contrat de production et le versionner comme du code. - Dans Postgres, vous devez traiter les retries applicatifs et la gestion des erreurs comme un contrat de production et les tester sous concurrence réaliste.
Transactions et isolation : ce que vous obtenez gratuitement
Les niveaux d’isolation par défaut ne sont pas triviaux. Ils font la différence entre « nos compteurs sont parfois bizarres » et « nous avons un incident de réconciliation financière ».
MariaDB (InnoDB) : par le passé, les défauts sont REPEATABLE READ. Cela réduit certaines anomalies mais en introduit d’autres (comme les gap locks et des comportements de verrouillage plus surprenants). Cela peut rendre certains motifs d’écriture sujets aux deadlocks en concurrence d’une manière que les équipes n’anticipent pas.
PostgreSQL : utilise par défaut READ COMMITTED. C’est raisonnable pour beaucoup de charges et réduit certaines surprises liées aux verrous, mais cela permet des lectures non répétables à moins d’utiliser une isolation plus forte ou un verrouillage explicite.
Implication opérationnelle : aucun des deux comportements par défaut n’est « sûr » au sens moral. Ils sont sûrs pour des hypothèses de développeurs différentes. Si votre appli suppose « je lis, donc c’est vrai jusqu’à ma validation », Postgres peut violer cela à moins que vous ne le demandiez explicitement. Si votre appli suppose « le verrouillage est simple », InnoDB vous punira avec un deadlock qui n’apparaît qu’à la pointe du trafic.
Jeux de caractères et collations : la corruption silencieuse
Les problèmes d’encodage et de collation ne vous réveillent pas à 2h du matin. Ils vous réveillent lors d’une migration, d’une fusion de données pendant une acquisition, ou quand vous commencez à servir des utilisateurs dans une nouvelle langue. Ce qui est pire, car le problème devient alors autant politique que technique.
MariaDB : les déploiements ont historiquement varié : latin1 était courant, utf8 (qui dans d’anciennes versions MySQL/MariaDB pouvait signifier « UTF-8 sur 3 octets ») a causé des surprises, et les nouveaux défauts tendent vers utf8mb4 selon la version et la configuration. Les collations diffèrent, et certaines collations ont changé de comportement entre les versions.
PostgreSQL : l’encodage est fixé à la création de la base et est typiquement UTF-8 dans les déploiements modernes, mais les collations dépendent des réglages OS/ICU. C’est excellent jusqu’à ce que vous restauriez sur une image OS différente et que le tri change subtilement.
Mon conseil ferme : choisissez UTF-8 de bout en bout, et dans les deux systèmes, explicitez les collations pour le tri/la recherche visibles par les utilisateurs. « Collation par défaut » n’est pas une stratégie ; c’est une dépendance non testée.
Contraintes, clés étrangères et comment les erreurs apparaissent
Les contraintes sont la façon de transformer le comportement de la base de « meilleur effort » en « contrat ». Si vous ne définissez pas le contrat, vous en avez toujours un — vous ne le connaissez juste pas.
PostgreSQL : les contraintes sont de première classe et couramment utilisées. Les contraintes différées existent et sont utiles quand on sait ce que l’on fait. Postgres appliquera vos règles et rejettera bruyamment les violations.
MariaDB : prend en charge les clés étrangères dans InnoDB, mais beaucoup d’écosystèmes MariaDB/MySQL les ont historiquement sous-utilisées, souvent à cause de l’héritage MyISAM, de préoccupations sur la réplication ou de la peur du surcoût en écriture. Le résultat : l’intégrité est appliquée côté application — jusqu’à ce que ce ne soit plus le cas.
Si vous migrez de MariaDB vers Postgres, vous découvrirez souvent que vos données violent déjà des contraintes que vous aviez supposées vraies. Postgres ne « fait pas le difficile ». Il fait le travail que vous n’avez pas demandé à MariaDB de faire.
Autovacuum vs purge : l’entretien est une politique
Le MVCC de PostgreSQL signifie que les lignes ne disparaissent pas quand vous les supprimez ; elles deviennent des tuples morts et doivent être vacuumées. Autovacuum s’exécute par défaut, mais il est réglé pour la « moyenne » et votre charge n’est jamais la moyenne. Si vous l’ignorez, les performances se dégradent lentement, puis soudainement, et le premier symptôme est souvent « disque plein » ou « requêtes inexplicablement lentes ».
Dans InnoDB, la dynamique d’entretien est différente : logs undo, threads de purge et longueur de la history list. Ce n’est pas « pas de vacuum », c’est « une plomberie différente ». Dans les deux systèmes, les valeurs par défaut visent la sécurité et la généralité, pas votre motif d’écriture spécifique.
Ici, la division pardonner/punir s’inverse. Postgres maintiendra vos lectures cohérentes, mais il vous punira plus tard si vous ne faites pas le vacuum. MariaDB/InnoDB peut continuer à tourner, mais il peut vous punir par de longues pauses, un retard de purge ou des problèmes de réplication quand le nettoyage interne ne suit plus.
Valeurs par défaut de réplication : compromis cohérence vs disponibilité
La réplication, c’est là où les valeurs par défaut deviennent politiques : voulez-vous reconnaître des écritures avant qu’elles ne soient durables sur une réplique ? Voulez-vous que le primaire attende ? Voulez-vous lire des réplicas potentiellement obsolètes ?
Réplication MariaDB : souvent asynchrone par défaut. Facile à mettre en place, facile à mal comprendre, et extrêmement efficace pour produire des post-mortems « nous avons perdu des données durant le basculement » si vous ne la concevez pas explicitement.
Réplication streaming PostgreSQL : est aussi fréquemment asynchrone par défaut, mais la réplication synchrone est un schéma courant et bien supporté. La base vous pousse à être explicite sur si vous voulez des garanties « zéro perte de données », et elle vous fera payer cela en latence.
Les valeurs par défaut ne vous sauvent pas des lois de la physique. Elles choisissent juste qui sera surpris en premier : vous ou vos clients.
Mode opératoire pour un diagnostic rapide
Quand la latence monte ou que les erreurs augmentent, vous avez besoin d’une séquence qui fonctionne sous pression. Pas une discussion philosophique. Voici l’ordre qui trouve le goulot d’étranglement le plus vite entre MariaDB et Postgres.
Première étape : confirmez quel type de douleur vous subissez (CPU, IO, verrous ou saturation)
- Les requêtes sont-elles lentes ou en attente ? « En attente » signifie généralement verrous, IO ou saturation du pool de connexions.
- Le CPU de la base est-il saturé ? Pensez à un mauvais plan, index manquants, dérive des statistiques, ou trop de workers concurrents.
- L’IO est-il saturé ? Pensez bloat/vacuum, checkpointing, buffer pool trop petit, lectures aléatoires, ou voisins bruyants.
- Les connexions sont-elles au maximum ? Pensez mauvaise configuration du pool, clients hors de contrôle, ou requêtes lentes tenant les connexions.
Deuxième étape : identifiez le principal coupable des attentes/verrous
- PostgreSQL : regardez
pg_stat_activityet les wait events ; trouvez les bloqueurs et les transactions longues. - MariaDB : regardez
SHOW PROCESSLIST, le statut InnoDB, et les vues transaction/verrou.
Troisième étape : validez la santé d’entretien du moteur de stockage
- PostgreSQL : progression de l’autovacuum, tuples morts, suspicion de bloat, pression de checkpoint.
- MariaDB/InnoDB : longueur de la history list, retard de purge, hit rate du buffer pool, réglages de capacité IO.
Quatrième étape : vérifiez la réplication et les sauvegardes (parce que la réponse à un incident empire si vous ne pouvez pas basculer)
- Délai et erreurs de réplication.
- Tendances d’espace disque et croissance WAL/binlog.
- Fraîcheur des sauvegardes et confiance en la restauration.
Idée paraphrasée (attribuée) : Gene Kim a souvent défendu la vérité opérationnelle que le feedback rapide bat les exploits héroïques ; raccourcir la boucle évite les surprises de minuit.
Tâches pratiques avec commandes (ce que signifie la sortie, quelle décision vous prenez)
Voici les vérifications de base à exécuter lors d’incidents, de travaux de performance et de migrations. Chaque tâche inclut une commande, une sortie d’exemple, ce que cela signifie et la décision qu’elle motive.
Task 1: Identify MariaDB SQL mode (strictness and coercion risk)
cr0x@server:~$ mysql -uroot -p -e "SELECT @@GLOBAL.sql_mode, @@SESSION.sql_mode\G"
Enter password:
*************************** 1. row ***************************
@@GLOBAL.sql_mode: STRICT_TRANS_TABLES,ERROR_FOR_DIVISION_BY_ZERO,NO_ENGINE_SUBSTITUTION
@@SESSION.sql_mode: STRICT_TRANS_TABLES,ERROR_FOR_DIVISION_BY_ZERO,NO_ENGINE_SUBSTITUTION
Ce que cela signifie : le mode strict est activé, donc les troncatures/valeurs invalides ont plus de chances de produire des erreurs plutôt que des warnings.
Décision : si les modes stricts manquent en production, alignez-les avec le staging, mettez à jour la gestion d’erreurs de l’app et planifiez une réparation des données avant d’activer la stricte.
Task 2: Check MariaDB warnings after a suspicious insert (silent data damage detector)
cr0x@server:~$ mysql -uroot -p -e "INSERT INTO t(amount) VALUES ('999999999999'); SHOW WARNINGS;"
Enter password:
Level Code Message
Warning 1264 Out of range value for column 'amount' at row 1
Ce que cela signifie : l’insert a réussi mais la valeur a été coercée/tronquée selon le type de colonne et le mode.
Décision : si vous voyez des warnings sur des chemins critiques, soit renforcez sql_mode, soit corrigez la validation applicative et commencez à collecter les warnings dans les logs/métriques.
Task 3: Confirm PostgreSQL isolation level (debugging “it changed under me” reads)
cr0x@server:~$ psql -U postgres -d appdb -c "SHOW default_transaction_isolation;"
default_transaction_isolation
-----------------------------
read committed
(1 row)
Ce que cela signifie : chaque instruction voit un snapshot frais ; dans une transaction, des instructions ultérieures peuvent voir des changements déjà commités par d’autres sessions.
Décision : si l’appli suppose des lectures répétables, corrigez l’appli ou utilisez explicitement REPEATABLE READ pour la transaction critique.
Task 4: Find Postgres sessions waiting on locks (fast path to “who is blocking?”)
cr0x@server:~$ psql -U postgres -d appdb -c "SELECT pid, usename, state, wait_event_type, wait_event, now()-query_start AS age, left(query,120) FROM pg_stat_activity WHERE wait_event_type IS NOT NULL ORDER BY age DESC LIMIT 10;"
pid | usename | state | wait_event_type | wait_event | age | left
------+--------+--------+-----------------+---------------+----------+-----------------------------------------------------------
8421 | app | active | Lock | transactionid | 00:03:12 | UPDATE orders SET status='paid' WHERE id=$1
8399 | app | active | Lock | relation | 00:01:48 | ALTER TABLE orders ADD COLUMN notes text
(2 rows)
Ce que cela signifie : l’UPDATE attend un verrou d’ID de transaction ; l’ALTER attend un verrou de relation. Le DDL peut bloquer le DML.
Décision : identifiez le bloqueur ensuite (Task 5). S’il s’agit d’un DDL accidentel en période de pointe, tuez-le ou reportez-le avec un plan de migration approprié.
Task 5: Identify the blocking query in Postgres (turn “mystery” into a name)
cr0x@server:~$ psql -U postgres -d appdb -c "SELECT blocked.pid AS blocked_pid, blocker.pid AS blocker_pid, now()-blocker.query_start AS blocker_age, left(blocker.query,120) AS blocker_query FROM pg_locks blocked JOIN pg_locks blocker ON blocker.locktype=blocked.locktype AND blocker.database IS NOT DISTINCT FROM blocked.database AND blocker.relation IS NOT DISTINCT FROM blocked.relation AND blocker.page IS NOT DISTINCT FROM blocked.page AND blocker.tuple IS NOT DISTINCT FROM blocked.tuple AND blocker.virtualxid IS NOT DISTINCT FROM blocked.virtualxid AND blocker.transactionid IS NOT DISTINCT FROM blocked.transactionid AND blocker.classid IS NOT DISTINCT FROM blocked.classid AND blocker.objid IS NOT DISTINCT FROM blocked.objid AND blocker.objsubid IS NOT DISTINCT FROM blocked.objsubid AND blocker.pid!=blocked.pid JOIN pg_stat_activity blocked_act ON blocked_act.pid=blocked.pid JOIN pg_stat_activity blocker ON blocker.pid=blocker.pid WHERE NOT blocked.granted AND blocker.granted LIMIT 5;"
blocked_pid | blocker_pid | blocker_age | blocker_query
------------+------------+-------------+---------------------------------------------------------
8421 | 8302 | 00:06:40 | BEGIN; SELECT * FROM orders WHERE id=$1 FOR UPDATE;
(1 row)
Ce que cela signifie : une longue transaction tenant un verrou de ligne bloque les mises à jour.
Décision : corrigez le chemin applicatif (commit/rollback manquant), ajoutez des timeouts, et envisagez de tuer le bloqueur si c’est sûr et que l’incident l’exige.
Task 6: MariaDB quick lock/transaction clue via processlist
cr0x@server:~$ mysql -uroot -p -e "SHOW FULL PROCESSLIST;"
Enter password:
Id User Host db Command Time State Info
21 app 10.0.2.15 appdb Query 187 Waiting for table metadata lock ALTER TABLE orders ADD COLUMN notes TEXT
34 app 10.0.2.16 appdb Query 183 Updating UPDATE orders SET status='paid' WHERE id=?
Ce que cela signifie : un DDL attend un verrou de métadonnée ; il peut aussi en bloquer d’autres selon le timing.
Décision : cessez de faire du DDL en ligne de façon naïve. Utilisez une approche de migration qui évite les longs verrous de métadonnées et planifiez-la.
Task 7: Check InnoDB engine health quickly (purge lag and deadlock hints)
cr0x@server:~$ mysql -uroot -p -e "SHOW ENGINE INNODB STATUS\G" | sed -n '1,120p'
Enter password:
*************************** 1. row ***************************
Type: InnoDB
Name:
Status:
=====================================
2025-12-30 01:42:11 0x7f4a4c1f9700 INNODB MONITOR OUTPUT
=====================================
...
History list length 148732
...
LATEST DETECTED DEADLOCK
------------------------
*** (1) TRANSACTION:
TRANSACTION 923847, ACTIVE 3 sec starting index read
...
Ce que cela signifie : une grande history list length suggère que la purge est en retard, souvent à cause de transactions longues. La section deadlock montre des motifs.
Décision : recherchez les transactions longues, corrigez le périmètre des transactions dans l’appli, et n’ajustez purge/IO qu’après avoir éliminé les transactions longues.
Task 8: PostgreSQL autovacuum health by table (bloat early warning)
cr0x@server:~$ psql -U postgres -d appdb -c "SELECT relname, n_live_tup, n_dead_tup, last_autovacuum, vacuum_count, autovacuum_count FROM pg_stat_user_tables ORDER BY n_dead_tup DESC LIMIT 10;"
relname | n_live_tup | n_dead_tup | last_autovacuum | vacuum_count | autovacuum_count
------------+------------+------------+------------------------+--------------+------------------
events | 81234921 | 23999110 | 2025-12-29 22:11:05+00 | 0 | 148
orders | 392110 | 88112 | 2025-12-29 20:03:12+00 | 2 | 67
(2 rows)
Ce que cela signifie : beaucoup de tuples morts sur events et des autovacuums fréquents. Il se peut qu’il tienne la charge — ou juste à peine.
Décision : si les tuples morts restent élevés, ajustez les seuils d’autovacuum par table, envisagez le partitionnement, et vérifiez les transactions longues empêchant le nettoyage.
Task 9: Postgres “are we checkpoint thrashing?” sanity check
cr0x@server:~$ psql -U postgres -d appdb -c "SELECT checkpoints_timed, checkpoints_req, checkpoint_write_time, checkpoint_sync_time, buffers_checkpoint FROM pg_stat_bgwriter;"
checkpoints_timed | checkpoints_req | checkpoint_write_time | checkpoint_sync_time | buffers_checkpoint
------------------+-----------------+-----------------------+----------------------+-------------------
214 | 987 | 8241132 | 119883 | 18933211
(1 row)
Ce que cela signifie : beaucoup de checkpoints demandés par rapport aux timed : le système force des checkpoints, souvent à cause du volume WAL et des réglages.
Décision : ajustez les paramètres de checkpoint/WAL et/ou réduisez l’amplification d’écriture (index, bloat). Vérifiez aussi la latence du stockage ; des disques lents transforment le checkpointing en panne visible.
Task 10: MariaDB buffer pool hit rate and dirty pages (is the cache working?)
cr0x@server:~$ mysql -uroot -p -e "SHOW GLOBAL STATUS LIKE 'Innodb_buffer_pool_read%'; SHOW GLOBAL STATUS LIKE 'Innodb_buffer_pool_pages_dirty';"
Enter password:
Variable_name Value
Innodb_buffer_pool_read_requests 9134829123
Innodb_buffer_pool_reads 18239481
Variable_name Value
Innodb_buffer_pool_pages_dirty 48211
Ce que cela signifie : si Innodb_buffer_pool_reads est élevé par rapport aux requests, vous manquez le cache et effectuez des lectures disque. Les pages sales montrent la pression d’écriture.
Décision : si les misses de cache dominent et que vous avez de la RAM, augmentez le buffer pool. Si les pages sales restent élevées et que l’IO est saturé, regardez les réglages de flush et les motifs d’écriture.
Task 11: Identify top SQL in Postgres via pg_stat_statements (the “who’s burning CPU?” list)
cr0x@server:~$ psql -U postgres -d appdb -c "SELECT calls, total_exec_time::int AS total_ms, mean_exec_time::numeric(10,2) AS mean_ms, rows, left(query,100) FROM pg_stat_statements ORDER BY total_exec_time DESC LIMIT 5;"
calls | total_ms | mean_ms | rows | left
-------+----------+---------+--------+----------------------------------------------------
89122 | 9123412 | 102.38 | 89122 | SELECT * FROM events WHERE tenant_id=$1 AND ts>=$2 ORDER BY ts DESC LIMIT 100
12211 | 3421901 | 280.21 | 12211 | UPDATE orders SET status=$1, updated_at=now() WHERE id=$2
(2 rows)
Ce que cela signifie : vous avez une requête « top talker » consommant du temps d’exécution.
Décision : lancez EXPLAIN (ANALYZE, BUFFERS) sur la requête, ajoutez/ajustez des index, ou changez la forme de la requête. Vérifiez aussi si l’ORDER FORCE un tri et un débordement mémoire.
Task 12: Identify top SQL in MariaDB using the slow query log (the boring, effective hammer)
cr0x@server:~$ sudo tail -n 20 /var/log/mysql/mysql-slow.log
# Time: 2025-12-30T01:38:44.112233Z
# User@Host: app[app] @ 10.0.2.15 []
# Query_time: 4.982 Lock_time: 0.001 Rows_sent: 100 Rows_examined: 81234921
SET timestamp=1735522724;
SELECT * FROM events WHERE tenant_id=42 ORDER BY ts DESC LIMIT 100;
Ce que cela signifie : comportement de full scan (Rows_examined énorme) et un ORDER BY qui force probablement du travail supplémentaire.
Décision : ajoutez un index composite (par exemple (tenant_id, ts)), validez le plan avec EXPLAIN, et gardez le slow log activé en production avec des seuils sages.
Task 13: Postgres disk usage by table (find the real storage hogs)
cr0x@server:~$ psql -U postgres -d appdb -c "SELECT relname, pg_size_pretty(pg_total_relation_size(relid)) AS total_size FROM pg_catalog.pg_statio_user_tables ORDER BY pg_total_relation_size(relid) DESC LIMIT 10;"
relname | total_size
---------+-----------
events | 412 GB
orders | 38 GB
(2 rows)
Ce que cela signifie : events domine le stockage. C’est la table qui dominera le vacuum, le comportement du cache et le temps de sauvegarde.
Décision : envisagez le partitionnement, des politiques de rétention et une revue des index. Ne tenez pas pour acquis que vous devez tuner toute la base quand une table fait tout le temps.
Task 14: MariaDB replication lag check (failover readiness)
cr0x@server:~$ mysql -uroot -p -e "SHOW SLAVE STATUS\G" | egrep 'Seconds_Behind_Master|Slave_IO_Running|Slave_SQL_Running|Last_SQL_Error'
Enter password:
Slave_IO_Running: Yes
Slave_SQL_Running: Yes
Seconds_Behind_Master: 87
Last_SQL_Error:
Ce que cela signifie : la réplication fonctionne mais a 87 secondes de retard. Si vous basculez maintenant, vous perdez jusqu’à 87 secondes d’écritures reconnues.
Décision : si votre RPO est quasi-zéro, ne prétendez pas que c’est acceptable. Corrigez la cause du retard (IO, transactions longues, gros writes) ou adoptez des patterns synch/semi-sync quand approprié.
Task 15: Postgres replication status and lag (primary visibility)
cr0x@server:~$ psql -U postgres -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
------------------+-----------+------------+-----------+-----------+-----------
replica1 | streaming | async | 00:00:02 | 00:00:03 | 00:00:05
(1 row)
Ce que cela signifie : la réplique est légèrement en retard ; elle est asynchrone, donc une certaine perte est possible en cas de basculement.
Décision : décidez si l’asynchrone est acceptable. Sinon, implémentez la réplication synchrone pour le chemin de données critique et budgétez le coût en latence.
Task 16: Confirm Postgres long transactions (vacuum and bloat killer)
cr0x@server:~$ psql -U postgres -d appdb -c "SELECT pid, usename, now()-xact_start AS xact_age, state, left(query,120) FROM pg_stat_activity WHERE xact_start IS NOT NULL ORDER BY xact_age DESC LIMIT 10;"
pid | usename | xact_age | state | left
------+--------+----------+--------+----------------------------------------------------------
8302 | app | 01:12:44 | idle | BEGIN; SELECT * FROM orders WHERE id=$1 FOR UPDATE;
(1 row)
Ce que cela signifie : une session en idle_in_transaction tient des verrous et empêche le vacuum d’avancer.
Décision : tuez-la si nécessaire, puis corrigez l’appli (paramètres du pool de connexions, périmètre des transactions), et définissez idle_in_transaction_session_timeout.
Trois mini-récits d’entreprise issus du terrain
Mini-récit 1 : L’incident causé par une mauvaise hypothèse (les valeurs permissives ont caché de mauvaises données)
Un marketplace de taille moyenne utilisait MariaDB depuis des années. L’équipe applicative était fière de sa « résilience ». Si un service en amont envoyait des données légèrement malformées, le pipeline d’ingestion continuait. Pas d’erreur visible client. Dashboards au vert. Tout le monde dormait.
Puis la finance a posé une question simple : « Pourquoi les remboursements ne correspondent-ils pas aux rétrofacturations du dernier trimestre ? » Les chiffres étaient proches, mais pas assez pour être ignorés. L’enquête a été lente et humiliante parce qu’il n’y avait pas un « mauvais déploiement » unique. La mauvaise qualité était étalée sur des mois.
La cause racine était une hypothèse : « les timestamps invalides échoueront les inserts ». Dans leur environnement MariaDB, le sql_mode avait divergencé entre clusters. Certains clusters acceptaient certaines dates invalides avec des warnings ; d’autres les rejetaient. Le pipeline ne vérifiait pas les warnings. Il stockait des valeurs coercées qui semblaient légitimes mais qui se triant mal. Quelques rapports en aval ont traité ces dates comme la vérité et ont produit des écarts subtils.
La correction n’a pas été héroïque. Ils ont standardisé sql_mode, ajouté de la validation en amont et écrit un job de réparation des données. La correction culturelle a été plus difficile : ils ont cessé de considérer « pas d’erreurs » comme « correct ». Ils ont commencé à traiter les warnings comme un budget de défaut visible et à le réduire.
Mini-récit 2 : L’optimisation qui s’est retournée contre eux (les valeurs punitives ont révélé un défaut de conception applicative)
Une entreprise SaaS a migré un chemin chaud de MariaDB vers PostgreSQL. Leur raison était solide : meilleures options d’indexation, meilleur planificateur pour des requêtes type analytics, et moins de pièges autour de la coercition de types. La migration s’est bien déroulée en tests. Puis en production ils ont essuyé des échecs d’écriture sporadiques.
Les échecs n’étaient pas aléatoires. Ils corrélaient avec les pics de trafic. L’application utilisait un pattern qui « marchait généralement » en MariaDB : lire une ligne, faire une logique métier, la mettre à jour, répéter. Sous les comportements par défaut de MariaDB et la manière dont leur code gérait les conflits, ils obtenaient un « last write wins » plus souvent qu’ils ne l’avaient réalisé. Pas correct, mais l’UX avançait.
Sur Postgres, ils ont resserré les contraintes et utilisé les transactions de façon plus rigoureuse. Les conflits ont alors émergé sous forme d’erreurs. La base ne les punissait pas par plaisir ; elle refusait de mentir. L’application, en revanche, n’était pas conçue pour réessayer les transactions en sécurité. Elle réessayait des requêtes entières, dupliquant des effets secondaires et produisant des comportements visibles par l’utilisateur.
Ils ont tenté d’« optimiser » en augmentant la taille du pool de connexions et en supprimant certains verrous côté code. Cela a empiré : plus de concurrence a augmenté le taux de conflits, et le pool a saturé le CPU avec du changement de contexte et de la contention. La requête la plus rapide reste lente quand elle est en concurrence avec 2 000 semblables.
Ils se sont rétablis en appliquant la correction ennuyeuse : clés d’idempotence, retries transactionnels corrects, et politique claire sur quelles opérations nécessitent de la sérialisation. Les performances se sont améliorées après l’amélioration de la correction. Cet ordre n’est pas optionnel.
Mini-récit 3 : La pratique ennuyeuse mais correcte qui a sauvé la mise (les valeurs par défaut importent peu si on peut voir)
Une entreprise liée aux paiements utilisait à la fois MariaDB et Postgres dans différentes lignes produits. Ils avaient une règle simple : chaque base avait un « manifeste des valeurs par défaut » en contrôle de version — paramètres de démarrage, modes SQL, attentes d’isolation, choix d’encodage/collation et les SLO opérationnels qu’ils impliquaient.
Ce n’était pas un travail glamour. Cela ne rapportait pas de prix d’architecture. Mais cela transformait les migrations en ingénierie prévisible plutôt qu’en archéologie. Quand une équipe voulait cloner un environnement, elle ne « copiait pas ce que prod ressemble ». Elle appliquait le manifeste, puis le validait avec un script qui exécutait des requêtes de sanity.
Un jour, une mise à jour d’image OS a changé le comportement de locale dans une réplique Postgres. L’ordre de tri différait subtilement. Normalement, cela aurait dégénéré en incident lent : utilisateurs qui se plaignent des résultats de recherche, support qui ouvre des tickets, ingénierie qui débat de ranking. Au lieu de cela, leur validation par manifeste a détecté un mismatch de collation immédiatement après le provisioning. Aucun impact en production. Ils ont corrigé l’image et sont passés à autre chose.
Blague 2 : Le bug de base de données le plus cher est celui qui attend poliment que vous soyez en plein créneau de migration.
Erreurs courantes : symptômes → cause racine → correction
1) « Les données semblent incorrectes, mais rien n’a signalé d’erreur »
Symptôme : écarts analytiques, dates étranges, chaînes tronquées, valeurs numériques arrondies de manière inattendue.
Cause racine : MariaDB exécuté sans modes SQL stricts ; warnings ignorés ; casts implicites et troncatures acceptés.
Correction : définir un sql_mode strict de façon cohérente, faire remonter les warnings dans les logs applicatifs, ajouter de la validation à l’ingestion et lancer un audit unique des données pour les dégâts de coercition.
2) « Postgres lance des erreurs en production après la migration »
Symptôme : échecs d’insert/update pour des valeurs qui « marchaient » avant, violations de contraintes inattendues, erreurs de cast.
Cause racine : l’ancien système acceptait des entrées ambiguës ; Postgres refuse. L’application comptait sur ce comportement permissif.
Correction : corriger les types et validations applicatives, ajouter des casts explicites quand approprié, et intégrer retries/idempotence dans la conception.
3) « Deadlocks aléatoires dans MariaDB en pointe »
Symptôme : erreurs de deadlock sous mises à jour concurrentes ; même chemin de code fonctionne hors pointe.
Cause racine : comportement de verrou InnoDB sous REPEATABLE READ, gap locks, ordre d’accès incohérent et transactions longues.
Correction : réduisez le périmètre des transactions, accédez aux lignes dans un ordre cohérent, ajoutez des index pour éviter les scans de plage, et n’envisagez un changement d’isolation qu’après corrections des requêtes/index.
4) « Postgres devient plus lent sur plusieurs semaines ; le disque croit sans cesse »
Symptôme : latence en hausse, usage disque en hausse, pics d’activité de vacuum, orages IO occasionnels.
Cause racine : autovacuum qui ne suit pas, transactions longues empêchant le nettoyage, bloat table/index.
Correction : tuez/limitez les transactions longues, réglez autovacuum pour les tables chaudes, implémentez partitionnement/rétention, et programmez vacuum/reindex manuel seulement avec preuve.
5) « Basculement et perte d’écritures récentes »
Symptôme : après un basculement, certaines transactions récentes disparaissent ; les utilisateurs signalent des mises à jour manquantes.
Cause racine : réplication asynchrone avec retard non nul ; le basculement a été traité comme « sûr » sans mesurer le RPO.
Correction : mesurez en continu le retard de réplication, définissez un RPO acceptable, adoptez la réplication synchrone quand nécessaire, et adaptez l’UX applicative pour la cohérence éventuelle quand c’est tolérable.
6) « Tempêtes de connexions : la base est up mais tout timeoute »
Symptôme : CPU DB modéré, mais pics de timeouts applicatifs ; nombre de connexions au max.
Cause racine : mauvaise configuration du pool, requêtes lentes tenant des connexions, ou boucles de retry amplifiant la charge (surtout sur Postgres où les erreurs sont plus fréquentes sous stricte).
Correction : limitez la concurrence, utilisez un pooler approprié (et configurez-le), corrigez les requêtes lentes, et ajoutez jitter/backoff aux retries.
Listes de contrôle / plan pas à pas
Checklist A: If you run MariaDB and want “Postgres-like” safety
- Standardisez
sql_modesur tous les clusters et sessions. Faites-en partie intégrante du provisioning, pas du folklore. - Transformez les warnings en signaux : collectez-les dans les logs et alertez quand ils montent.
- Utilisez InnoDB partout (si ce n’est pas déjà le cas). Si quelque chose est encore en MyISAM, traitez-le comme une dette technique avec intérêts.
- Ajoutez contraintes et clés étrangères là où le domaine est strict (argent, droits, inventaire). Laissez la base dire « non ».
- Mesurez deadlocks et attentes de verrous. Corrigez le périmètre des transactions et les index avant de toucher aux réglages magiques.
- Définissez explicitement l’encodage/collation. Préférez UTF-8 (
utf8mb4) et testez les migrations avec des données multilingues réelles. - Rendez le retard de réplication visible et liez-le à la politique de basculement. Ne « basculez pas automatiquement » dans une perte de données sans décision.
Checklist B: If you run Postgres and want “MariaDB-like” uptime without losing correctness
- Concevez des retries applicatifs pour les échecs de sérialisation et erreurs transitoires. Testez-les sous concurrence.
- Fixez des statement timeouts et
idle_in_transaction_session_timeoutsensés. - Installez et utilisez
pg_stat_statementset suivez les requêtes principales comme opération standard. - Réglez autovacuum pour les tables importantes ; les défauts sont un point de départ, pas une promesse.
- Surveillez checkpoints et volume WAL ; assurez-vous que la latence du stockage est connue et acceptable.
- N’utilisez la réplication synchrone que lorsque le business l’exige ; sinon soyez honnête sur le RPO.
- Pour les grandes tables, planifiez partitionnement et rétention tôt. Le bloat est plus facile à prévenir qu’à guérir.
Checklist C: Migration sanity plan (MariaDB ↔ Postgres)
- Auditez l’utilisation des types : dates, timestamps, booléens et précision numérique. Listez chaque cast implicite sur lequel l’app compte actuellement.
- Gelez les valeurs par défaut : SQL mode, attentes d’isolation, encodage/collation, gestion des fuseaux horaires.
- Exécutez un scan qualité des données : dates invalides, valeurs tronquées, enregistrements orphelins (FK manquantes), clés dupliquées là où l’unicité était supposée.
- Répétez le cutover en conditions proches de la production et injectez des pannes (connexions tuées, deadlocks, tempêtes de retry).
- Définissez des critères de rollback et validez les sauvegardes avec un vrai test de restauration, pas seulement un log « backup succeeded ».
- Après le cutover, surveillez les graphiques ennuyeux : retard de réplication, taux de checkpoint, tuples morts/history list, et top talkers des requêtes lentes.
FAQ
1) MariaDB est-il « dangereux » par défaut ?
Non. Mais il peut être permissif de façon à laisser entrer de mauvaises données silencieusement si vous ne définissez pas de modes SQL stricts et ne surveillez pas les warnings. Vous pouvez le rendre strict ; il faut juste le faire délibérément.
2) PostgreSQL est-il « meilleur » parce qu’il est plus strict ?
Être plus strict signifie que vos erreurs remontent plus tôt. C’est généralement mieux pour les systèmes où l’exactitude compte. Mais cela signifie aussi que vous avez besoin d’une gestion d’erreurs robuste et d’une logique de retry, sinon vous échangerez des problèmes de données silencieux contre des pannes bruyantes.
3) Quelle base a une isolation transactionnelle par défaut plus sûre ?
Aucune n’est universellement plus sûre. Le REPEATABLE READ de MariaDB peut réduire certaines anomalies mais augmenter les surprises liées aux verrous. Le READ COMMITTED de Postgres est plus simple mais permet des lectures non répétables sauf si vous demandez une isolation plus forte.
4) Pourquoi les migrations MariaDB→Postgres « découvrent » des problèmes de données ?
Parce que Postgres applique les types et contraintes plus strictement, et ne fait pas autant de coercitions silencieuses. La migration agit comme un sérum de vérité pour les hypothèses.
5) Quel est le principal piège opérationnel des défauts Postgres ?
Supposer qu’autovacuum suivra toujours. Il le fait en général — jusqu’à ce que vous ayez une table chaude, des transactions longues ou une charge d’écriture importante. Alors cela devient un problème de performances et d’espace disque.
6) Quel est le principal piège opérationnel des défauts MariaDB ?
Supposer que « ça a inséré » signifie « ça a stocké ce que je voulais ». Sans modes stricts et visibilité des warnings, vous pouvez accumuler des données incorrectes alors que tout semble sain.
7) Puis-je obtenir un basculement « sans perte de données » avec l’un ou l’autre ?
Oui, mais il faut en payer le prix. Il vous faut des garanties de réplication synchrone (ou équivalentes) et un modèle applicatif qui tolère la latence ajoutée. La réplication asynchrone est une décision RPO, pas une commodité par défaut.
8) Dois-je me fier aux valeurs par défaut de la base ?
Ne vous fiez aux valeurs par défaut que si vous les avez écrites, validées et surveillées. Sinon, vous vous fiez à la version que vous avez installée par hasard.
9) Si je ne peux faire qu’une amélioration ce mois-ci, laquelle ?
Rendez vos valeurs par défaut explicites et observables : modes SQL / attentes d’isolation, encodage/collation, et un tableau de bord « top queries + lock waits + replication lag ». Cela prévient à la fois la corruption lente et les pannes surprises.
Prochaines étapes réalisables cette semaine
- Notez vos valeurs par défaut actuelles (MariaDB
sql_mode, isolation Postgres, encodage/collation, fuseau horaire) et commitez-les en contrôle de version comme un « contrat d’exécution ». - Exécutez les requêtes de diagnostic rapide pendant une période calme, pas en plein incident. Les baselines sont la façon de reconnaître le « bizarre ».
- Choisissez une garde-fou de correction et appliquez-la : modes stricts sur MariaDB, ou timeouts et transactions retry-safe sur Postgres.
- Rendez l’entretien visible : stats de tuples morts/autovacuum sur Postgres ; history list length et deadlocks sur MariaDB.
- Décidez de votre vérité de réplication : mesurez le lag, définissez un RPO acceptable, et concevez le basculement en conséquence. Ne laissez pas la valeur par défaut décider pendant une panne.
Si vous ne retenez rien d’autre : les valeurs par défaut de la base font partie de votre API de production. Traitez-les comme du code, car elles se comportent comme du code — juste sans les tests unitaires.