Si vous avez déjà assisté à une réunion où quelqu’un dit « Nous devrions changer de base de données, ce sera plus rapide », vous connaissez la sensation : votre pagette devient plus lourde. La plupart des affirmations « plus rapide » proviennent de benchmarks avec le mauvais schéma, le mauvais jeu de données, la mauvaise concurrence et les mauvais modes de panne.
PostgreSQL et Percona Server (une variante MySQL axée performance) peuvent tous deux être terriblement rapides. Ils peuvent aussi tous deux être douloureusement lents. La différence n’est pas une question d’ambiance. C’est la forme de la charge de travail, la discipline opérationnelle et si vous faites involontairement du benchmark sur votre cache.
Mythes de performance qui persistent
Mythe 1 : « Percona Server est plus rapide que PostgreSQL »
Percona Server peut être plus rapide que PostgreSQL pour l’OLTP typique à la MySQL : beaucoup de lectures/écritures point à point indexées courtes, des schémas d’accès prévisibles, et des équipes qui savent bien opérer InnoDB. PostgreSQL peut aussi être plus rapide que Percona Server pour la même charge si vos motifs de requête favorisent le comportement du planificateur de Postgres, ou si les instincts de tuning de votre équipe collent mieux aux réglages et à l’observabilité de Postgres.
« Plus rapide » est ici une assertion qui a besoin d’une unité : latence p95 ? Débit à latence fixe ? Temps de récupération après basculement ? Heures passées à chasser la dérive de réplication ? La base de données qui « gagne » dépend de la douleur mesurée.
Mythe 2 : « PostgreSQL est lent à cause du bloat MVCC »
MVCC n’est pas une taxe inévitable. C’est un compromis de conception qui fournit des lectures cohérentes et de la concurrence. La taxe arrive quand on ignore le vacuum, les transactions longue durée et le bloat de tables/index. Exploité correctement, Postgres gère proprement une forte concurrence. Exploité paresseusement, il transformera lentement votre stockage en album de coupures de tuples morts.
Mythe 3 : « MySQL est toujours plus rapide pour les lectures »
Les lectures ne sont pas une chose unique. Il y a des lectures point, des scans de plage, des jointures à fan-out, des agrégations analytiques, et des « lectures » qui sont en réalité des attentes de verrous. InnoDB peut être fulgurant sur des lectures point avec un buffer pool chaud et un schéma simple. Postgres peut aussi être fulgurant — particulièrement avec de bons index, un cache efficace et des plans qui évitent les mauvaises surprises en boucle imbriquée.
Mythe 4 : « Les benchmarks tranchent »
Les benchmarks tranchent exactement une chose : si une base donnée spécifique, sur une configuration spécifique, sur un jeu de données spécifique, avec un générateur de charge spécifique, sur une machine spécifique, dans un fantasme sans panne spécifique, a atteint un certain nombre. Utile. Pas universel.
Une plaisanterie courte, pour nettoyer le palais : Les benchmarks sont comme les CV — tout semble incroyable jusqu’à ce que vous vérifiiez les références sous charge.
Mythe 5 : « Changer de moteur résout la performance »
Changer de moteur modifie votre goulot d’étranglement, pas votre responsabilité. Vous n’échappez pas à la physique. Vous échangez un ensemble de pièges opérationnels contre un autre. Si votre problème est des index manquants, des requêtes mal écrites ou un stockage sous-dimensionné, migrer n’est qu’un moyen coûteux de reporter le même travail.
Faits intéressants et contexte historique (ce qui explique le comportement d’aujourd’hui)
- Lignée de PostgreSQL remonte à POSTGRES à l’Université de Californie Berkeley dans les années 1980 ; son nom « PostgreSQL » reflète l’ajout précoce de SQL qui a perduré.
- InnoDB est devenu le moteur par défaut pour MySQL en 5.5 ; avant cela, MyISAM était courant et apportait une histoire de verrouillage et de durabilité très différente.
- Percona Server est né parce que de nombreuses équipes voulaient la compatibilité MySQL plus de l’instrumentation et des fonctionnalités de performance sans attendre les cycles de publication en amont.
- Postgres a ajouté la query parallèle progressivement (pas d’un seul coup), et cela a changé la donne pour certaines charges reporting qui étaient auparavant « export vers Spark » par défaut.
- L’histoire de la réplication MySQL a commencé par la réplication basée sur les déclarations, plus rapide dans certains cas mais nondéterministe ; la réplication basée sur les lignes a amélioré la correction au prix de la charge.
- Le WAL de Postgres (write-ahead log) a été central depuis toujours, mais des fonctionnalités comme le logical decoding ont facilité la construction de pipelines de changement de données sans triggers.
- Le buffer pool d’InnoDB est un des leviers les plus importants pour la performance MySQL/Percona ; se tromper dessus peut faire paraître un serveur rapide comme un stockage lent.
- Vacuum n’est pas optionnel dans Postgres ; c’est une partie de la façon dont MVCC maintient la performance stable. L’autovacuum existe parce que les humains sont peu fiables.
- Les deux écosystèmes ont mûri en systèmes « de classe entreprise » ; la discussion aujourd’hui porte rarement sur les capacités et davantage sur l’adéquation et l’exploitation.
Ce que « plus rapide » signifie réellement : archétypes de charge
1) OLTP à clé chaude : beaucoup de lectures/écritures point sur un petit working set
C’est ici que Percona Server (InnoDB) brille souvent : les pages chaudes restent dans le buffer pool, le change buffering et les comportements adaptatifs s’enclenchent, et le moteur est optimisé pour le style « beaucoup de petites requêtes ». Postgres peut aussi le faire, mais vous devrez davantage vous soucier de la gestion des connexions, des choix d’index et d’éviter des schémas de transaction qui bloquent le vacuum.
Piège typique : vous faites un benchmark sur un petit jeu de données qui tient en RAM et vous vous proclamez vainqueur. En production, le working set est plus grand, et soudain vous mesurez la latence du stockage et le churn du cache plutôt que le SQL.
2) OLTP à forte écriture : inserts/updates soutenus avec durabilité
Ici « plus rapide » est contraint par le volume de log, le comportement d’fsync et le checkpointing. Dans Postgres, le volume WAL et le tuning des checkpoints comptent, tout comme l’autovacuum qui doit suivre. Dans InnoDB, la capacité des redo logs, le comportement de flushing et les implications du doublewrite peuvent dominer. Les deux peuvent rencontrer un mur d’I/O ; ils touchent juste des murs différents en premier selon les réglages et le schéma.
3) Charge mixte avec jointures complexes
Le planificateur de Postgres est puissant, et ses statistiques et options d’indexation (y compris les index d’expression et partiels) peuvent produire d’excellents plans pour des requêtes relationnelles complexes. MySQL/Percona s’est beaucoup amélioré, mais il existe encore des charges où Postgres tend à être le moteur « moins surprenant » pour les jointures et les sémantiques SQL avancées.
Point de décision : si votre application s’appuie sur un design relationnel et des requêtes reporting complexes en parallèle de l’OLTP, Postgres réduit souvent le nombre d’incidents « on a réécrit le SQL pour apaiser l’optimiseur ».
4) Réplicas de lecture et fan-out de lectures
La réplication est de la performance. Si vous ne pouvez pas scaler les lectures sans que la latence de réplication devienne un bug produit, votre primaire « rapide » n’a pas d’importance. Les shops Percona comptent souvent sur des flottes de réplicas ; les shops Postgres aussi, mais les pratiques opérationnelles diffèrent (streaming replication physique, réplication logique, et extensions).
5) Opérations « ennuyeuses » : sauvegardes, changements de schéma et récupérations
La vitesse inclut la rapidité à livrer des changements. Les modifications de schéma en ligne, le temps de création d’un index, le vacuuming et le temps de restauration décident si vous dormez la nuit. Les deux moteurs peuvent être exploités en toute sécurité ; les deux peuvent être menés dans le fossé si vous traitez la maintenance comme optionnelle.
Où les moteurs diffèrent (dans les parties qui vous pénalisent)
Contrôle de la concurrence : MVCC n’est pas identique partout
Les deux systèmes utilisent les concepts MVCC, mais les conséquences opérationnelles diffèrent. Postgres conserve plusieurs versions de lignes dans la table elle-même, et la visibilité est déterminée par des IDs de transaction. Cela signifie que des tuples morts s’accumulent jusqu’à ce que le vacuum récupère l’espace. Ce n’est pas un défaut ; c’est le compromis.
InnoDB utilise aussi MVCC avec des undo logs. Les anciennes versions vivent dans des segments undo plutôt que sous forme de tuples morts dans les pages de table. Cela change la forme du « bloat » et les symptômes que vous observez : les transactions longue durée peuvent empêcher le purge, faire croître l’historique et dégrader la performance d’une manière différente.
Durabilité et schémas d’E/S : WAL vs redo/undo + doublewrite
Postgres écrit des enregistrements WAL puis flush les pages sales ; les checkpoints peuvent provoquer des rafales d’écriture si mal réglés. InnoDB a des redo logs et des fichiers de données, plus des mécanismes comme le doublewrite pour protéger contre les écritures partielles de page. Les réglages diffèrent, mais la physique est la même : votre sous-système de stockage a un budget, et la base de données le dépense.
La « taxe de l’optimiseur » : les planificateurs sont des machines opiniâtres
Le planificateur de Postgres peut faire des choix brillants et parfois terribles quand les statistiques sont obsolètes ou que les distributions sont bizarres. L’optimiseur de MySQL/Percona a ses propres bizarreries, surtout autour des jointures complexes et de certains motifs de sous-requêtes. Vous ne choisissez pas le « meilleur optimiseur ». Vous choisissez l’optimiseur que vous pouvez prédire, instrumenter et corriger.
Connexions : Postgres sanctionne les tempêtes de connexions
Par défaut, Postgres utilise un modèle processus-par-connexion. Un essaim de connexions courtes peut être une attaque DDoS autoinfligée. La solution n’est pas « agrandir la base ». C’est le pooling (souvent PgBouncer), dimensionner correctement max connections, et apprendre à l’application à réutiliser les connexions.
MySQL gère typiquement beaucoup de connexions avec des caractéristiques de surcharge différentes. Cela ne veut pas dire que vous devez laisser les clients faire n’importe quoi. Cela signifie que le mode de défaillance peut arriver plus tard, avec une signature différente.
Observabilité : les deux ont des outils, mais les valeurs par défaut diffèrent
Postgres avec pg_stat_statements vous donne une visibilité requêtes à haute valeur. Percona Server est connu pour son instrumentation supplémentaire et l’écosystème Percona toolkit. Quoi qu’il en soit, si vous ne collectez pas les bons métriques et traces de requêtes, vous finirez par « tuner » sur la base de superstitions.
Une citation, parce que les ops méritent au moins une bonne phrase : L’espoir n’est pas une stratégie.
— Vince Lombardi
Recette de diagnostic rapide
Voici ce que vous faites quand quelqu’un dit « La base de données est lente » et que vous avez 15 minutes avant que le canal d’incident ne devienne une œuvre de performance.
Premier point : confirmer le symptôme et l’unité
- S’agit‑il de latence ou de débit ? Latence p95 des requêtes vs QPS total. Ils échouent différemment.
- Est‑ce la base de données ? Les pools de threads de l’application, le réseau et les appels en aval peuvent se faire passer pour une lenteur de BD.
- Est‑ce une classe de requête ? Une mauvaise requête peut empoisonner tout un pool.
Deuxième point : classer le goulot en une des quatre catégories
- CPU-bound : CPU élevé, I/O stable, requêtes lentes avec calcul intensif (tris, hash joins, traitement JSON).
- I/O-bound : latence de lecture/écriture élevée, faible taux de hit cache, pression de checkpoint/flush.
- Lock/transaction-bound : attentes, contention de verrous, transactions longues, délais d’application de réplication.
- Mismatch mémoire/working-set : thrash du cache, churn du buffer pool, usage fréquent de fichiers temporaires.
Troisième point : choisir le chemin le plus court vers la preuve
- Sur Postgres : commencez par
pg_stat_activity,pg_stat_statements, le ratio de cache hit, les stats de checkpoint, et la progression du vacuum. - Sur Percona : commencez par
SHOW PROCESSLIST, les métriques du buffer pool InnoDB, les stats redo/flush, et les résumés de slow query. - Sur l’hôte : vérifiez iostat, les stalls de pression, la latence du système de fichiers, et si le stockage crie silencieusement.
Deuxième plaisanterie courte (et on a fini) : La base de données la plus rapide est celle sur laquelle vous n’avez pas accidentellement pointé le job de reporting.
Tâches pratiques : commandes, ce que signifie la sortie et la décision à prendre
Ce sont des tâches réelles que vous pouvez exécuter pendant un incident ou une revue de performance. Chaque tâche inclut : commande, sortie d’exemple, interprétation et décision suivante. Les commandes sont séparées entre PostgreSQL et Percona/MySQL, plus des contrôles au niveau OS qui décident dans quelle direction creuser.
Task 1 (PostgreSQL) : Voir ce qui s’exécute et ce qui attend
cr0x@server:~$ psql -X -d appdb -c "select pid, usename, state, wait_event_type, wait_event, now()-query_start as age, left(query,80) as query from pg_stat_activity where state <> 'idle' order by age desc limit 10;"
pid | usename | state | wait_event_type | wait_event | age | query
------+--------+--------+-----------------+---------------+----------+--------------------------------------------------------------------------------
8123 | app | active | Lock | transactionid | 00:02:11 | update orders set status='paid' where id=$1
7991 | app | active | IO | DataFileRead | 00:01:05 | select * from order_items where order_id=$1
7902 | app | active | | | 00:00:40 | select count(*) from orders where created_at > now()-interval '1 day'
Ce que cela signifie : Vous avez une attente de verrou sur transactionid (souvent des conflits au niveau ligne qui s’étendent en attentes transactionnelles) et une attente I/O sur des lectures de fichiers de données.
Décision : Si les verrous dominent, trouvez le PID bloquant et corrigez le comportement transactionnel. Si l’I/O domine, vérifiez le taux de cache et la latence du stockage avant de toucher le SQL.
Task 2 (PostgreSQL) : Identifier le bloqueur
cr0x@server:~$ psql -X -d appdb -c "select blocked.pid as blocked_pid, blocker.pid as blocker_pid, blocked.query as blocked_query, blocker.query as blocker_query from pg_locks blocked_locks join pg_stat_activity blocked on blocked_locks.pid=blocked.pid join pg_locks blocker_locks on blocked_locks.locktype=blocker_locks.locktype and blocked_locks.database is not distinct from blocker_locks.database and blocked_locks.relation is not distinct from blocker_locks.relation and blocked_locks.page is not distinct from blocker_locks.page and blocked_locks.tuple is not distinct from blocker_locks.tuple and blocked_locks.virtualxid is not distinct from blocker_locks.virtualxid and blocked_locks.transactionid is not distinct from blocker_locks.transactionid and blocked_locks.classid is not distinct from blocker_locks.classid and blocked_locks.objid is not distinct from blocker_locks.objid and blocked_locks.objsubid is not distinct from blocker_locks.objsubid and blocker_locks.pid != blocked_locks.pid join pg_stat_activity blocker on blocker_locks.pid=blocker.pid where not blocked_locks.granted;"
blocked_pid | blocker_pid | blocked_query | blocker_query
------------+------------+------------------------------------------+---------------------------------------------
8123 | 7001 | update orders set status='paid' where id=$1 | begin; update orders set ...; -- long txn
Ce que cela signifie : Le PID 7001 détient le verrou. Notez le motif begin; : transaction longue.
Décision : Corrigez la portée des transactions côté application. Pendant l’incident : envisagez de terminer le bloqueur si c’est sans risque. À long terme : réduisez le temps des transactions, évitez les flux « ouverture de transaction pendant que l’utilisateur réfléchit ».
Task 3 (PostgreSQL) : Requêtes top par temps total (nécessite pg_stat_statements)
cr0x@server:~$ psql -X -d appdb -c "select calls, round(total_exec_time::numeric,1) as total_ms, round(mean_exec_time::numeric,2) as mean_ms, rows, left(query,100) as query from pg_stat_statements order by total_exec_time desc limit 5;"
calls | total_ms | mean_ms | rows | query
-------+----------+---------+-------+-----------------------------------------------------------------------------------------------------
12000 | 980000.0 | 81.67 | 12000 | select * from order_items where order_id = $1
800 | 410000.0 | 512.50 | 800 | select count(*) from orders where created_at > now()-interval '1 day'
300 | 220000.0 | 733.33 | 9000 | select * from orders o join customers c on c.id=o.customer_id where c.email like $1
Ce que cela signifie : Une requête domine le temps total à cause du nombre d’appels élevé ; une autre est lente par appel.
Décision : Pour la requête à fort nombre d’appels, concentrez-vous sur l’index et le cache. Pour la requête lente, lancez EXPLAIN (ANALYZE, BUFFERS) et corrigez la forme du plan ou ajoutez le bon index.
Task 4 (PostgreSQL) : Vérifier le ratio de cache (signal, pas religion)
cr0x@server:~$ psql -X -d appdb -c "select datname, round(100*blks_hit/nullif(blks_hit+blks_read,0),2) as cache_hit_pct, blks_read from pg_stat_database where datname='appdb';"
datname | cache_hit_pct | blks_read
--------+---------------+-----------
appdb | 93.41 | 1829934
Ce que cela signifie : 93% peut être correct ou catastrophique selon la charge. Mais si c’est passé de 99% hier, votre working set a grandi ou la mémoire a diminué.
Décision : Si le cache hit chute et que les lectures augmentent, vérifiez la pression RAM, la sanity de shared_buffers et si un nouveau motif de requête scanne de grandes tables.
Task 5 (PostgreSQL) : Trouver les tables avec signaux de bloat (tuples morts)
cr0x@server:~$ psql -X -d appdb -c "select relname, n_live_tup, n_dead_tup, round(100*n_dead_tup/nullif(n_live_tup+n_dead_tup,0),2) as dead_pct from pg_stat_user_tables order by n_dead_tup desc limit 5;"
relname | n_live_tup | n_dead_tup | dead_pct
------------+------------+------------+---------
orders | 12500000 | 4100000 | 24.69
sessions | 800000 | 2900000 | 78.38
events | 90000000 | 1200000 | 1.32
Ce que cela signifie : sessions est une usine à bloat. Probablement des mises à jour/suppressions fréquentes plus une progression d’autovacuum insuffisante, peut‑être des transactions longue durée.
Décision : Corrigez le motif de churn (partitionnement TTL, moins de mises à jour), et tunez l’autovacuum pour cette table. Si elle est déjà énorme, planifiez un VACUUM (FULL) contrôlé ou une réécriture de table pendant une fenêtre maintenance.
Task 6 (PostgreSQL) : Vérifier si les checkpoints vous punissent
cr0x@server:~$ psql -X -d appdb -c "select checkpoints_timed, checkpoints_req, round(checkpoint_write_time/1000.0,1) as write_s, round(checkpoint_sync_time/1000.0,1) as sync_s, buffers_checkpoint from pg_stat_bgwriter;"
checkpoints_timed | checkpoints_req | write_s | sync_s | buffers_checkpoint
------------------+-----------------+---------+--------+-------------------
124 | 198 | 905.2 | 210.3 | 88412211
Ce que cela signifie : Beaucoup de checkpoints demandés suggèrent une pression WAL ou des réglages forçant des checkpoints fréquents. Cela peut créer des pics d’I/O et de la latence.
Décision : Envisagez d’augmenter max_wal_size, de tuner checkpoint_completion_target, et de vérifier la latence d’écriture du stockage. Ne « corrigez » pas cela en désactivant la durabilité.
Task 7 (Percona/MySQL) : Qui s’exécute et qui est bloqué
cr0x@server:~$ mysql -e "SHOW FULL PROCESSLIST\G"
*************************** 1. row ***************************
Id: 22341
User: app
Host: 10.0.2.19:51244
db: appdb
Command: Query
Time: 132
State: Waiting for row lock
Info: update orders set status='paid' where id=?
*************************** 2. row ***************************
Id: 22110
User: app
Host: 10.0.2.20:49821
db: appdb
Command: Query
Time: 141
State: Sending data
Info: select * from order_items where order_id=?
Ce que cela signifie : Vous avez des attentes de verrou de ligne et une requête bloquée en « Sending data » (souvent signifiant qu’elle scanne/retourne beaucoup de lignes, ou attend l’I/O).
Décision : Pour les verrous de ligne, identifiez la transaction bloquante. Pour « Sending data », vérifiez le plan de requête et les index et confirmez le hit du buffer pool et les lectures disque.
Task 8 (Percona/MySQL) : Trouver les attentes de verrou InnoDB et le bloqueur
cr0x@server:~$ mysql -e "SELECT r.trx_id waiting_trx_id, r.trx_mysql_thread_id waiting_thread, r.trx_query waiting_query, b.trx_id blocking_trx_id, b.trx_mysql_thread_id blocking_thread, b.trx_query blocking_query FROM information_schema.innodb_lock_waits w JOIN information_schema.innodb_trx b ON b.trx_id=w.blocking_trx_id JOIN information_schema.innodb_trx r ON r.trx_id=w.requesting_trx_id\G"
*************************** 1. row ***************************
waiting_trx_id: 9012231
waiting_thread: 22341
waiting_query: update orders set status='paid' where id=?
blocking_trx_id: 9012198
blocking_thread: 21990
blocking_query: update orders set status='processing' where id=?; -- txn open
Ce que cela signifie : Le thread 21990 bloque. Probablement une transaction longue ou un flux applicatif retenant les verrous trop longtemps.
Décision : Si c’est sûr, tuez le bloqueur. À long terme, réduisez la portée des transactions et assurez-vous que les index soutiennent les updates ciblés (pour éviter de verrouiller des lignes supplémentaires).
Task 9 (Percona/MySQL) : Vérifier l’efficacité du buffer pool
cr0x@server:~$ mysql -e "SHOW GLOBAL STATUS LIKE 'Innodb_buffer_pool_read%';"
+---------------------------------------+------------+
| Variable_name | Value |
+---------------------------------------+------------+
| Innodb_buffer_pool_read_requests | 9834421190 |
| Innodb_buffer_pool_reads | 8832211 |
+---------------------------------------+------------+
Ce que cela signifie : Le ratio est correct (les lectures depuis le disque sont une petite fraction des requêtes). Si Innodb_buffer_pool_reads augmente rapidement, vous manquez de cache.
Décision : Si les lectures disque sont élevées, augmentez la taille du buffer pool (dans les limites de la RAM) ou réduisez le working set via des index et des modifications de requête.
Task 10 (Percona/MySQL) : Rechercher la pression sur les redo logs / checkpoints
cr0x@server:~$ mysql -e "SHOW ENGINE INNODB STATUS\G" | sed -n '1,120p'
=====================================
2025-12-30 10:12:01 0x7f2c3c0a0700 INNODB MONITOR OUTPUT
=====================================
Log sequence number 146990331122
Log flushed up to 146990220981
Last checkpoint at 146988102000
0 pending log flushes, 0 pending chkp writes
Ce que cela signifie : Les positions LSN, flushed et checkpoint indiquent à quel point le flushing/checkpointing est à jour. De grands écarts sous charge peuvent signifier une pression redo ou des limites I/O.
Décision : Si le checkpointing prend du retard pendant les pics, revoyez la taille des redo logs et les réglages de flush, et vérifiez la latence d’écriture du stockage. Ne changez pas aveuglément les réglages de durabilité sans approbation risque.
Task 11 (OS) : Vérifier la latence disque et si vous êtes I/O-bound
cr0x@server:~$ iostat -xz 1 3
Linux 6.5.0 (db01) 12/30/2025 _x86_64_ (32 CPU)
avg-cpu: %user %nice %system %iowait %steal %idle
18.21 0.00 4.12 22.33 0.00 55.34
Device r/s rkB/s rrqm/s %rrqm r_await rareq-sz w/s wkB/s w_await wareq-sz aqu-sz %util
nvme0n1 220.0 18944.0 0.0 0.00 9.40 86.10 480.0 61200.0 18.70 127.50 12.30 98.00
Ce que cela signifie : %iowait élevé, w_await élevé, et %util proche de 100% crient saturation du stockage. Votre base attend que le disque tienne des promesses qu’il ne peut pas tenir.
Décision : Arrêtez de tuner le SQL en premier. Réduisez l’amplification d’écriture (index, churn), répartissez l’I/O (séparez WAL/redo et données si possible), ou améliorez le stockage. Vérifiez aussi les voisins bruyants sur des disques partagés.
Task 12 (OS) : Vérifier la pression mémoire et le swap
cr0x@server:~$ vmstat 1 5
procs -----------memory---------- ---swap-- -----io---- -system-- ------cpu-----
r b swpd free buff cache si so bi bo in cs us sy id wa st
2 0 0 81264 14000 612000 0 0 120 980 9200 8800 18 4 56 22 0
3 1 0 64220 13890 590100 0 0 110 1100 9400 9100 17 4 54 25 0
Ce que cela signifie : Pas de swap (si/so sont 0), mais il y a de l’I/O wait. Si vous voyiez du swap, attendez-vous à une latence dramatique et des rapports « c’est lent » aléatoires.
Décision : Si le swap est présent, arrêtez et corrigez le dimensionnement mémoire (buffer pools, shared_buffers, work_mem, tmp tables). Laisser une base de données swapper est un passe-temps, pas une stratégie.
Task 13 (PostgreSQL) : Confirmer l’utilisation des fichiers temporaires (tris/hashes vers disque)
cr0x@server:~$ psql -X -d appdb -c "select datname, temp_files, pg_size_pretty(temp_bytes) as temp_size from pg_stat_database where datname='appdb';"
datname | temp_files | temp_size
--------+------------+-----------
appdb | 41290 | 98 GB
Ce que cela signifie : 98 Go de données temporaires suggèrent des débordements. Souvent dus à de mauvais index, de mauvais plans, ou un work_mem insuffisant pour la charge spécifique (pas globalement).
Décision : Identifiez les requêtes qui débordent via EXPLAIN (ANALYZE, BUFFERS). Préférez corriger la requête/l’index d’abord. Si vous augmentez work_mem, faites-le prudemment pour éviter des OOM sous concurrence.
Task 14 (Percona/MySQL) : Valider que le slow query log vous dit la vérité
cr0x@server:~$ mysql -e "SHOW VARIABLES LIKE 'slow_query_log%'; SHOW VARIABLES LIKE 'long_query_time';"
+---------------------+-------+
| Variable_name | Value |
+---------------------+-------+
| slow_query_log | ON |
| slow_query_log_file | /var/log/mysql/slow.log |
+---------------------+-------+
+-----------------+-------+
| Variable_name | Value |
+-----------------+-------+
| long_query_time | 0.2 |
+-----------------+-------+
Ce que cela signifie : Le slow log est activé et le seuil est assez agressif pour capturer un comportement proche du p95 (0,2s ici). S’il est réglé à 10s, c’est essentiellement un outil « postmortem uniquement ».
Décision : Assurez-vous que le slow logging est activé en production avec un seuil sensé et une rotation. Sinon vous finirez par deviner quelle requête vous a nui.
Task 15 (PostgreSQL) : Vérifier rapidement la latence de réplication
cr0x@server:~$ psql -X -d appdb -c "select client_addr, state, write_lag, flush_lag, replay_lag from pg_stat_replication;"
client_addr | state | write_lag | flush_lag | replay_lag
-------------+-----------+-----------+-----------+-----------
10.0.3.12 | streaming | 00:00:01 | 00:00:02 | 00:00:08
Ce que cela signifie : Le replay lag est de 8 secondes. Cela peut être acceptable ou catastrophique selon les exigences read-your-writes.
Décision : Si le lag est élevé et croissant, vérifiez l’I/O/CPU du replica, les requêtes longues sur les replicas, et le volume WAL sur le primaire. Envisagez de déplacer le reporting hors des replicas qui servent les lectures produit.
Task 16 (MySQL/Percona) : Vérifier la santé et la latence de réplication
cr0x@server:~$ mysql -e "SHOW SLAVE STATUS\G" | egrep 'Seconds_Behind_Master|Slave_IO_Running|Slave_SQL_Running|Relay_Log_Space'
Slave_IO_Running: Yes
Slave_SQL_Running: Yes
Seconds_Behind_Master: 37
Relay_Log_Space: 1849423872
Ce que cela signifie : Le thread SQL tourne mais le lag est de 37 secondes et les relay logs sont volumineux. Le replica n’applique pas assez vite.
Décision : Analysez les limites de ressources du replica, les lectures lourdes sur le replica, et si l’application en mono‑fil d’exécution est un goulot (et si la réplication parallèle est configurée correctement pour votre charge).
Trois mini-histoires d’entreprise depuis les tranchées de la performance
1) Incident causé par une mauvaise hypothèse : « Le benchmark disait que Percona était plus rapide »
L’entreprise était en pleine croissance, avec une charge de type paiements : beaucoup de transactions courtes, mais aussi une pénalité reporting désagréable qui faisait des filtrages ad-hoc sur quelques grandes tables. Le lead ingénierie a lancé un benchmark : des lookups simples par clé primaire, quelques inserts, et un jeu de données propre qui tenait confortablement en mémoire. Percona Server affichait de meilleurs chiffres que Postgres sur le test choisi. Décision prise.
La migration a eu lieu. La première semaine allait bien. La troisième semaine, la réalité est arrivée : les requêtes de reporting n’étaient pas celles du benchmark. Elles étaient des jointures larges avec des filtres sélectifs, et les utilisateurs métiers changeaient sans cesse les filtres. L’optimiseur choisissait des plans « raisonnables » jusqu’à ce que le skew des données augmente. Ensuite il a commencé à scanner, déborder, et transformer la couche de réplica en radiateur.
L’incident lui‑même était classique : un rapport trimestriel a tourné en période de pic. La latence de réplication a grandi. Les lectures produit ont commencé à frapper des replicas obsolètes et à afficher un état incohérent. Le support a parlé de « corruption de données ». Ce n’était pas une corruption. C’était du lag plus des hypothèses. Les ingénieurs ont chassé des bugs fantômes pendant des heures avant que quelqu’un vérifie Seconds_Behind_Master et le slow query log.
La correction a exigé d’admettre la vérité inconfortable : le moteur n’était pas le problème ; c’était la compréhension de la charge. Ils ont déplacé le reporting vers un système séparé et redessiné la stratégie d’indexation. La base de données est devenue « plus rapide » du jour au lendemain sans changer un seul binaire — parce qu’ils ont arrêté de demander à la mauvaise machine de faire le mauvais travail au mauvais moment.
2) Optimisation qui s’est retournée contre eux : « Nous avons augmenté la mémoire et supprimé les attentes fsync »
Une autre organisation utilisait Postgres pour une pipeline d’ingestion d’événements à fort débit. Ils voyaient des pics de latence périodiques. Quelqu’un a pointé les checkpoints. Effectivement : des rafales d’écriture liées aux checkpoints étaient visibles, et les p99 montraient un rythme cardiaque.
Le tuning initial était sensé : ajuster le pacing des checkpoints, augmenter la taille WAL, surveiller l’I/O. Mais ensuite l’« optimisation » a escaladé : ils ont augmenté work_mem globalement parce que quelques requêtes débordaient, et ont relevé les limites de connexions parce que l’équipe applicative voulait plus de parallélisme.
Deux jours plus tard la base a commencé à se tuer elle‑même par OOM sous charge. Pas parce que work_mem est maléfique, mais parce que c’est alloué par tri/par hash, multiplié par les sessions concurrentes. Ajoutez à cela trop de connexions et vous avez une bombe mémoire à retardement. Les pics de checkpoint étaient le problème visible ; le réglage mémoire était le tueur silencieux.
La correction a été ennuyeuse : limiter les connexions et utiliser du pooling, fixer work_mem conservativement, et appliquer des overrides par rôle pour les quelques requêtes analytiques qui en avaient besoin. L’équipe a appris que « plus de mémoire » n’est pas une stratégie de tuning ; c’est une facture que vous payez plus tard en disponibilité.
3) Pratique ennuyeuse mais correcte qui a sauvé la mise : « Nous avons répété les restaurations »
Celle‑ci n’a pas d’héroïsme. Elle a des adultes dans la pièce.
Une société SaaS utilisait Percona Server avec des replicas et des sauvegardes nocturnes. Ils faisaient aussi quelque chose d’imparfait : des drills de restauration trimestriels. Pas juste « la sauvegarde a réussi », mais restaurer réellement dans un environnement de staging, valider des requêtes au niveau application, et chronométrer les étapes de récupération. Le runbook avait de vraies commandes et de vraies estimations.
Un jour, un bug de firmware de stockage sur le primaire a provoqué des pauses d’écriture intermittentes. La base n’a pas planté ; elle est juste devenue lente et peu fiable. L’équipe a décidé de basculer. Pendant le basculement, ils ont découvert que la chaîne de sauvegarde la plus récente avait un trou parce qu’un job de rétention était mal configuré. Normalement c’est là que l’incident tourne à la nuit longue et en sueur.
À la place, ils ont calmement restauré depuis la dernière sauvegarde saine, appliqué les binlogs jusqu’à un point sûr, et mis en ligne un primaire de remplacement. Cela a quand même pris du temps, mais c’était un temps qu’ils avaient déjà mesuré. Le business a constaté une fenêtre dégradée, pas une catastrophe. La pratique « que personne n’a le temps de faire » s’est payée en un incident.
Erreurs fréquentes : symptômes → cause profonde → correction
1) Symptôme : pics de latence p99 toutes les quelques minutes (Postgres)
Cause profonde : rafales d’écriture de checkpoints dues à un checkpointing agressif et à la pression WAL ; le stockage ne peut pas absorber la rafale.
Correction : Augmenter max_wal_size, tuner le pacing des checkpoints, vérifier la latence du stockage, et envisager de séparer le WAL sur un média plus rapide si applicable.
2) Symptôme : dégradation régulière des performances sur plusieurs jours (Postgres)
Cause profonde : autovacuum qui ne suit pas ; transactions longues empêchant le nettoyage ; bloat qui croît.
Correction : Trouver les transactions longues, corriger les patterns applicatifs, tuner l’autovacuum par table, et planifier la remédiation du bloat (reindex, rewrite) si nécessaire.
3) Symptôme : « Le CPU est correct mais les requêtes sont lentes » (les deux)
Cause profonde : attente I/O et misses de cache ; le dataset a dépassé la mémoire ; un nouveau motif de requête provoque des scans.
Correction : Vérifier les ratios de cache/lectures du buffer pool, valider les index, réduire les requêtes scan, et upgrader le stockage ou la mémoire en fonction des misses mesurés.
4) Symptôme : tempêtes de verrous soudaines après une release (les deux)
Cause profonde : nouvelle portée de transaction, index manquant sur le prédicat d’update, ou pattern « select puis update » augmentant les conflits.
Correction : Identifier le bloqueur, ajouter le bon index, réduire la tenue des verrous, et repenser les lignes chaudes (sharder les compteurs, éviter les updates de ligne globale).
5) Symptôme : replicas en retard pendant les pics mais récupèrent hors pic (MySQL/Percona)
Cause profonde : le thread d’application du replica ne suit pas, lectures lourdes sur le replica, ou pics de volume de binlog dus à des mises à jour massives.
Correction : Optimiser les patterns d’écriture (éviter les updates multi-lignes massifs), utiliser des paramètres de réplication parallèle appropriés, et isoler le reporting des replicas critiques.
6) Symptôme : Postgres « trop de connexions » et commutation de contexte élevée
Cause profonde : l’application ouvre trop de connexions ; le coût processus-par-connexion domine.
Correction : Ajouter PgBouncer, limiter les tailles de pool applicatives, réduire max_connections, et traiter le nombre de connexions comme une métrique de capacité.
7) Symptôme : MySQL « Sending data » partout
Cause profonde : requêtes retournant trop de lignes, index manquants, ou scans liés au disque. Parfois aussi backpressure réseau.
Correction : EXPLAINer la requête, ajouter des indexes couvrants, paginer, et vérifier la saturation réseau vs lectures disque.
8) Symptôme : la performance empire après « ajout d’un index » (les deux)
Cause profonde : vous avez augmenté l’amplification d’écriture ; chaque insert/update paye maintenant la maintenance d’index supplémentaire. Ou l’optimiseur a choisi un plan pire.
Correction : Ajouter uniquement les index que vous pouvez vous permettre. Vérifier les plans de requête avant/après. Utiliser des index partiels/d’expression (Postgres) ou des indexes couvrants réfléchiement (MySQL).
Checklists / plan étape par étape
Étape par étape : choisir entre PostgreSQL et Percona Server pour la performance (sans se mentir)
- Notez votre charge en 10 requêtes importantes, avec la concurrence et les ratios lecture/écriture. Si vous ne pouvez pas, vous n’êtes pas prêt à choisir.
- Définissez les métriques de succès : latence p95/p99 pour les requêtes clés, débit à latence fixe, budget de lag de réplication, objectif de temps de récupération.
- Testez avec des données proches de la production et un skew réaliste. Au minimum, dépassez la RAM pour mesurer le comportement stockage.
- Testez les modes de panne : lag de réplica sous pic, comportement de basculement, et rapidité de restauration.
- Instrumentez en premier : activez
pg_stat_statementsou les slow logs MySQL, et collectez les métriques système. - Faites le tuning ennuyeux : dimensionnement mémoire, taille checkpoints/logs, pooling de connexions, et réglages autovacuum/flush sensés.
- Comparez ensuite seulement. Si un moteur gagne, vous saurez pourquoi — et vous saurez le maintenir gagnant en production.
Checklist opérationnelle : garder Postgres rapide
- Surveiller l’activité autovacuum et les tuples morts ; alerter sur les transactions longues bloquant le vacuum.
- Limiter les connexions et utiliser du pooling pour les trafics éclatés.
- Suivre la croissance des fichiers temporaires ; traiter les débordements comme un problème requête/index avant d’augmenter la mémoire.
- Surveiller les stats de checkpoint et le volume WAL ; tuner pour des écritures lisses, pas des rafales héroïques.
- Répéter les restaurations et valider les sauvegardes avec de vrais tests de restauration.
Checklist opérationnelle : garder Percona Server rapide
- Dimensionner l’InnoDB buffer pool pour le working set et le vérifier avec les métriques de lecture.
- Activer le slow query logging avec un seuil qui correspond à vos SLO.
- Surveiller les attentes de verrou et les transactions longues ; tuner les lignes chaudes et la portée transactionnelle.
- Suivre la latence de réplication et la croissance des relay logs ; garder le reporting à l’écart des replicas critiques.
- Valider que les réglages de durabilité correspondent au risque métier, pas aux fantasmes de benchmark.
FAQ
1) Percona Server, c’est « juste MySQL » ?
C’est compatible MySQL mais il embarque des fonctionnalités de performance et d’instrumentation et un écosystème opérationnel différent. La compatibilité est le point ; les extras sont la raison pour laquelle on le choisit.
2) PostgreSQL est‑il toujours plus lent pour l’OLTP ?
Non. Postgres peut très bien faire de l’OLTP. Il tend à être moins tolérant à une gestion des connexions négligente et à un autovacuum ignoré. Si vous l’exploitez bien, il est très compétitif.
3) Quelle est la manière la plus rapide de savoir si je suis I/O-bound ?
Vérifiez iostat -xz pour la latence et l’utilisation, et corrélez avec les métriques de cache côté BD (Postgres blks_read, lectures buffer pool InnoDB). Si la latence disque augmente avec la latence des requêtes, vous avez votre réponse.
4) Dois‑je tuner Postgres en augmentant fortement shared_buffers ?
Pas aveuglément. shared_buffers compte, mais le cache page OS compte aussi. Concentrez‑vous sur le working set, les tendances de cache hit, et éviter les scans. De grands chiffres sans mesures achètent généralement de nouveaux modes de panne.
5) Dois‑je juste mettre un énorme InnoDB buffer pool ?
Dimensionnez‑le pour tenir le working set tout en laissant de la marge pour l’OS, les autres services et les pics. Le surdimensionnement peut provoquer du swap ou priver le cache du système de fichiers, ce qui est une façon coûteuse de ralentir.
6) Pourquoi mes benchmarks montrent des gains massifs que la production ne voit jamais ?
Parce que vous avez probablement benchmarké des caches chauds, de petits jeux de données et une parfaite localité. La production a du skew, des jobs en arrière‑plan, des sauvegardes, des changements de schéma, et des utilisateurs qui font des choses bizarres en période de pic.
7) Lequel est meilleur pour le SQL complexe et le reporting ?
Souvent Postgres, surtout si vous exploitez les index avancés et le requêtage relationnel. Mais « meilleur » dépend de vos requêtes exactes et si vous pouvez isoler le reporting de l’OLTP.
8) Comment éviter de choisir la mauvaise base sur des impressions ?
Définissez les requêtes clés, testez à l’échelle réaliste, testez sous concurrence, et testez les modes de panne. Basez votre choix sur un comportement mesurable et reproductible.
9) La latence de réplication est‑elle un problème de performance ou de correction ?
Les deux. Le lag est un problème de « performance » quand il casse vos SLOs et de « correction » quand votre produit suppose read‑your‑writes. Traitez le lag comme une métrique de première classe.
Conclusion : prochaines étapes qui ne vous ridiculiseront pas
Si vous voulez une vraie réponse à « PostgreSQL vs Percona Server : lequel est plus rapide », cessez de le demander comme si c’était un nombre unique. Demandez lequel est plus rapide pour votre charge, avec vos contraintes opérationnelles, les habitudes de votre équipe, et les modes de panne que vous ne pouvez pas éviter.
- Inventoriez la charge : top 10 des patterns de requêtes, volume d’écriture, concurrence et objectifs de latence.
- Instrumentez les deux mondes : Postgres avec
pg_stat_statementset vues d’activité ; Percona avec slow logs et InnoDB status, plus les métriques hôtes. - Réalisez un test réaliste : dataset plus grand que la RAM, mêmes index, même mix de requêtes, mêmes attentes de durabilité.
- Faites un drill de diagnostic rapide : introduisez volontairement de la charge, trouvez le goulot avec la recette, et voyez quel système votre équipe comprend le mieux.
- Choisissez le moteur que vous pouvez garder rapide : pas celui qui gagne une démo, mais celui que vous pouvez exploiter proprement à 3h du matin.