Vous pouvez faire tourner beaucoup d’activités sur un seul VPS 16 Go. Jusqu’au jour où vous ne le pouvez plus. Le mode de panne n’est que rarement « ça a crashé instantanément ». C’est pire : il boîte, votre app accumule des connexions, puis la base de données devient comme un restaurant bondé avec un seul cuisinier et 200 personnes agitant des cartes.
C’est à ce moment que « MySQL vs MariaDB » cesse d’être un débat de détail et devient une décision opérationnelle : lequel offre les valeurs par défaut les plus sûres, la meilleure observabilité et le moins de surprises quand vous ajoutez réplication et pooling — parce que vous ajouterez réplication et pooling si vous tenez à la disponibilité.
La réalité du VPS 16 Go : vous n’êtes pas riche en capacité
Un VPS 16 Go est la « berline moyenne » de l’hébergement de bases de données. C’est suffisant pour avoir confiance. C’est aussi assez petit pour qu’un mauvais réglage transforme la machine en incident lié au CPU, asphyxié par l’I/O, swapant, avec un acompagnement du type « pourquoi la charge moyenne est à 40 ? »
Voici ce que vous achetez vraiment avec 16 Go :
- Budget mémoire : pas 16 Go nets. Le noyau, le cache du système de fichiers, vos agents divers et la base de données veulent tous une part. Comptez sur 12–13 Go utilisables pour les processus MySQL/MariaDB avant de risquer du swap et des variations.
- Plafond I/O : « NVMe » sur VPS signifie souvent « NVMe partagé ». Les pics de latence ne sont pas un bug ; ce sont vos voisins.
- Contention CPU : même avec des vCPU dédiés, les voisins bruyants apparaissent comme du %steal, des délais du scheduler et une latence de queue imprévisible.
- Rayon d’explosion d’un seul hôte : panic noyau, corruption du système de fichiers, maintenance du fournisseur, rm fait par erreur, et vous êtes hors ligne.
Sur cette machine, la réplication et le pooling ne sont pas des « améliorations » de performance. Ce sont la différence entre une mauvaise journée et une journée qui ruine une carrière.
Faits et historique encore importants en production
Quelques points de contexte semblent triviaux jusqu’à ce qu’ils vous mordent pendant une fenêtre de mise à jour.
- MariaDB a été forkée depuis MySQL en 2009 après qu’Oracle ait acquis Sun. Ce fork n’était pas qu’idéologique ; il a façonné le rythme des versions et les valeurs par défaut.
- MySQL 5.6/5.7 a fait d’InnoDB le centre incontesté ; l’époque MyISAM appartient en grande partie au passé, mais beaucoup de schémas hérités gardent leurs suppositions.
- MariaDB a maintenu plus longtemps différents moteurs de stockage (et en a ajouté d’autres). C’est à la fois une puissance et un risque : plus d’options signifie plus de façons de mal choisir.
- MySQL 8.0 a supprimé complètement le query cache (c’était une fête du mutex global). MariaDB a gardé une version plus longtemps, mais ce n’est toujours pas une bonne idée sous forte concurrence.
- Les implémentations GTID diffèrent entre MySQL et MariaDB. « GTID activé » n’est pas la fin de la conversation ; c’est le début des questions de compatibilité.
- Le plugin d’authentification par défaut de MySQL a changé (caching_sha2_password s’est généralisé). Les anciens clients et certains proxies en ont souffert.
- MariaDB et MySQL ont divergé dans le comportement de l’optimiseur au fil du temps. Même SQL, plans différents. Sur un VPS, les régressions de plan sont coûteuses car vous n’avez pas de marge pour les absorber.
- La réplication est passée de « meilleur effort » à pilier opérationnel : métadonnées de réplication sûres en cas de crash, appliers multi-threads, options semi-sync. Mais vous devez toujours tester le basculement comme si vous le pensiez vraiment.
MySQL vs MariaDB : comment choisir sur un VPS
Cadre de décision : garanties opérationnelles, pas listes de fonctionnalités
Sur un VPS 16 Go, vous êtes contraint par deux choses : la latence et la variance. Le débit de pointe importe moins que la constance du temps de réponse sous charge et la récupération sûre quand les choses tournent mal.
Choisissez donc le moteur en fonction de :
- Sécurité des mises à jour : à quel point les montées de version majeure/mineure sont-elles prédictibles et votre équipe les maîtrise-t-elle ?
- Compatibilité des outils : votre proxy, vos outils de sauvegarde, votre ORM et votre stack de monitoring se comportent-ils correctement ?
- Ergonomie de la réplication : obtient-on un statut clair, un reseed facile et moins de surprises aux limites ?
- Stabilité des performances : évite-t-il des blocages pathologiques (verrous de métadonnées, pauses de flush, thrash du buffer pool) pour votre charge ?
MySQL : le « choix ennuyeux » qui est ennuyeux pour une raison
Si vous exécutez une charge web typique (tables InnoDB, requêtes OLTP, quelques rapports), MySQL 8.0 est souvent le pari le plus sûr. Il est largement testé dans les écosystèmes, proxies, drivers et services managés. Opérationnellement, ça compte plus qu’une fonctionnalité brillante que vous n’utiliserez jamais.
Points forts de MySQL sur un VPS :
- Support d’écosystème prédictible : drivers, connecteurs et outils cloud supposent en priorité le comportement MySQL.
- Maturité de Performance Schema : excellent pour diagnostiquer les waits, verrous et SQL principaux sans deviner.
- « Normalité » de la réplication : beaucoup d’outils de basculement et de runbooks sont écrits avec la sémantique MySQL en tête.
MariaDB : une option solide, mais traitez la compatibilité comme un projet
MariaDB peut être un excellent choix lorsque vous avez besoin de ses capacités spécifiques ou que votre organisation la standardise. Mais ne la traitez pas comme « MySQL avec un logo différent ». La divergence est réelle, et sur une petite machine le coût opérationnel des surprises est amplifié.
Points forts de MariaDB sur un VPS :
- Choix de réplication et de clustering différents : MariaDB + Galera est un schéma courant (avec réserves), et les fonctionnalités de réplication de MariaDB peuvent être attractives selon la version.
- Vitesse communautaire et packaging : certaines distributions et fournisseurs ont des defaults MariaDB et des rétroports utiles.
Avis tranché : si vous démarrez et que votre graphe de dépendances est typique (app web + proxies + connecteurs standards), choisissez MySQL 8.0. Si vous êtes déjà sur MariaDB et stable, ne migrez pas par ennui — migrez parce que vous avez un point douloureux clairement défini et un chemin testé.
Une citation de fiabilité à garder sur un post-it
Werner Vogels : « Tout échoue, tout le temps. » (idée paraphrasée)
Quand le pooling de connexions devient obligatoire (pas « agréable »)
Sur un VPS 16 Go, votre serveur de base de données n’exécute pas seulement des requêtes. Il surveille aussi des connexions. Threads, mémoire par connexion, changements de contexte, handshakes d’authentification, TLS et tenue interne. Ce surcoût est invisible jusqu’au moment où il ne l’est plus.
Le pooling devient obligatoire quand vous observez l’un de ces schémas
- Tempêtes de connexions : autoscaling des instances app, pics de cron, déploiements ou retries créent des centaines/milliers de nouvelles connexions en quelques secondes.
- Beaucoup de requêtes courtes : OLTP web classique où les requêtes sont rapides mais nombreuses ; le coût de connexion devient le goulot.
- Forte concurrence avec une petite machine : même si chaque requête est légère, la base passe son temps à scheduler plutôt qu’à travailler.
- Sensibilité à la latence tail : vous vous souciez du p95/p99, pas de la moyenne. La mise en place d’une nouvelle connexion et le scheduling de threads dégradent fortement les queues.
Voici la vérité : si votre app ouvre et ferme des connexions par requête, le pooling n’est pas optionnel. La base finira par vous throttler par le CPU, la contention des mutex ou la pression mémoire. Et ça arrivera au pire moment.
Blague n°1 : Une base de données sans pooling, c’est comme une boîte de nuit où le videur vérifie votre carte d’identité à chaque fois que vous clignez des yeux.
Ce que « pooling » signifie réellement en production
Il y a deux couches :
- Pooling côté application (baseline préférée) : votre app maintient un pool stable de connexions par instance. C’est bien, mais ça scale linéairement avec le nombre d’instances.
- Pooling réseau/proxy (obligatoire en croissance) : un pooler/proxy comme ProxySQL multiplexe de nombreuses sessions clients sur moins de connexions serveur, applique des règles de routage et protège la BD des tempêtes.
Dimensionnement des pools sur un VPS 16 Go : arrêtez de penser « max_connections = 2000 »
Un max_connections élevé n’est pas un badge d’honneur. C’est souvent une confession que vous n’avez pas fait de pooling et que vous compensez par le déni.
Sur 16 Go, vous voulez généralement :
- Des connexions serveur raisonnables : souvent dans les quelques centaines ou moins, selon la charge.
- Des timeouts stricts : tuer les clients inactifs avant qu’ils ne deviennent des fuites de ressources.
- Backpressure : des poolers qui mettent en file et rejettent la charge plutôt que de laisser la BD mourir.
Quand la réplication devient obligatoire (même si vous détestez la complexité)
La réplication n’est pas seulement « pour scaler les lectures ». Sur un seul VPS, elle sert surtout la survivabilité : sauvegardes, temps de récupération, maintenance et tests. La première fois que votre seul hôte de base de données tombe et que la restauration prend des heures, vous apprendrez à aimer les réplicas.
La réplication devient obligatoire quand vous avez besoin de l’un des éléments suivants
- RPO < 1 heure sans parier sur des sauvegardes de binlog et des procédures de restauration parfaites.
- Fenêtres de maintenance sans downtime : patch OS, mises à jour DB, migration de stockage.
- Sauvegardes qui n’écrasent pas la production : prendre des sauvegardes logiques ou effectuer des lectures lourdes sur un réplique.
- Expériences sûres : tester changements de schéma, plans de requête, constructions d’index.
- Santé mentale en réponse aux incidents : vous avez besoin d’un endroit propre pour exécuter des requêtes forensiques sans lutter contre le trafic live.
Un réplique sur un autre VPS : l’architecture viable minimale
Pour un primaire 16 Go, un mouvement « adulte » courant est :
- Primaire (16 Go) : accepte les écritures
- Réplique (8–16 Go) : sert les lectures, exécute les sauvegardes, peut être promue
- Pooler/proxy (petite VM ou sur les hôtes app) : contrôle la concurrence et le routage
Cela n’est pas glamour. C’est fiable. Comme un extincteur : ennuyeux jusqu’au jour où il devient essentiel.
Réplication asynchrone vs semi-sync sur un VPS
Asynchrone est le défaut : le primaire retourne le succès avant que la réplique n’acquitte. C’est rapide, et on peut perdre les dernières transactions en cas de perte du primaire.
Semi-sync peut réduire ce risque en attendant l’acquittement d’au moins une réplique avant de valider le commit. Sur des réseaux VPS avec jitter, le semi-sync peut aussi amplifier la latence tail. Utilisez-le si vous pouvez tolérer la latence et que le lien vers la réplique est stable ; sinon concentrez-vous sur de bonnes sauvegardes et un basculement rapide.
Blague n°2 : La réplication, c’est comme le fil dentaire : tout le monde jure qu’il va commencer après la première urgence.
Playbook de diagnostic rapide : trouver le goulot tout de suite
Quand la latence monte ou que le débit s’effondre, ne commencez pas par éditer des configs. Commencez par comprendre ce que fait la machine maintenant. C’est le chemin le plus court du panic à la bonne décision.
Première étape : est-ce le CPU, la mémoire ou l’I/O ?
- Saturation CPU / file d’exécution : si le CPU est saturé et la charge moyenne élevée, cherchez des requêtes coûteuses, des waits de mutex ou trop de threads.
- Pression mémoire : si du swapping ou du reclaim se produit, vous êtes en train de couler. Réduisez l’empreinte mémoire et le nombre de connexions, puis réajustez la taille des buffers.
- Latence I/O : si le temps d’attente disque augmente, vous pouvez avoir beaucoup de CPU et être quand même « down ». Le comportement de flush d’InnoDB, le doublewrite, la fréquence des fsync et le bruit des voisins comptent.
Deuxième étape : y a-t-il trop de connexions ou trop peu de ressources par connexion ?
- Vérifiez le nombre de threads, de connexions actives, de connexions avortées.
- Vérifiez si des requêtes sont bloquées sur des verrous ou en attente d’I/O.
- Cherchez des tempêtes de connexions après des déploiements et des événements d’autoscaling.
Troisième étape : régression de plan de requête ou dette de schéma/index ?
- Identifiez les requêtes principales par temps total et par latence p95.
- Confirmez l’utilisation des index et les estimations de lignes.
- Vérifiez l’absence d’index composites et les scans complets accidentels.
Quatrième étape : santé de la réplication (si vous en avez)
- Lag de la réplique : secondes de retard, croissance des relay logs.
- Threads appliers bloqués sur une transaction, waits sur verrous.
- Disque plein sur la réplique à cause des binlogs/relay logs.
Tâches pratiques : commandes, sorties et décisions
Voici les vraies vérifications que je lance sur un VPS 16 Go quand quelque chose cloche. Chaque élément inclut une commande, une sortie typique, ce que cela signifie et la décision que j’en tire.
Task 1: Confirm memory pressure and swap activity
cr0x@server:~$ free -h
total used free shared buff/cache available
Mem: 15Gi 12Gi 520Mi 310Mi 2.6Gi 1.8Gi
Swap: 2.0Gi 1.2Gi 820Mi
Ce que cela signifie : Le swap est activement utilisé. « Available » est faible. Attendez-vous à des pauses, surtout sous charge d’écriture.
Décision : Réduire immédiatement le nombre de connexions et la mémoire par connexion. Si vous utilisez de gros buffers de tri/join, stoppez. Puis redimensionnez le buffer pool InnoDB et implémentez du pooling.
Task 2: Check CPU saturation and steal time
cr0x@server:~$ mpstat -P ALL 1 5
Linux 6.1.0 (server) 12/31/2025 _x86_64_ (4 CPU)
12:00:01 AM CPU %usr %sys %iowait %steal %idle
12:00:02 AM all 72.4 10.8 1.2 8.6 7.0
12:00:03 AM all 70.1 11.2 0.9 9.3 8.5
Ce que cela signifie : Le CPU est à plat et le %steal est élevé. Vous vous battez pour le CPU de l’hôte.
Décision : Réduire la concurrence (pooling), baisser max_connections, et prioriser l’efficacité des requêtes. Envisagez de déplacer la BD vers une offre moins contende ou avec CPU dédié.
Task 3: Check disk latency quickly
cr0x@server:~$ iostat -x 1 3
Device r/s w/s rkB/s wkB/s await svctm %util
nvme0n1 35.2 210.7 2200.1 9800.4 18.40 0.52 96.8
Ce que cela signifie : Le disque est presque saturé et l’attente moyenne est élevée. InnoDB va staller sur les fsync et les flush.
Décision : Cherchez l’amplification d’écritures (binlogs, doublewrite, redo), vérifiez le flush des pages sales, et réduisez les rafales (batching, taille des transactions). Si c’est du jitter de stockage partagé, la réplication ne le corrigera pas ; mieux vaut un meilleur stockage.
Task 4: Confirm MySQL/MariaDB version and engine
cr0x@server:~$ mysql -e "SELECT VERSION() AS version, @@version_comment AS comment\G"
*************************** 1. row ***************************
version: 8.0.36
comment: MySQL Community Server - GPL
Ce que cela signifie : Connaître la saveur et la version exactes détermine quels compteurs de performance, champs de statut de réplication et valeurs par défaut s’appliquent.
Décision : Alignez les étapes de dépannage sur la version. N’appliquez pas aveuglément des conseils MariaDB à MySQL 8.0 (et inversement).
Task 5: Check connection pressure and thread usage
cr0x@server:~$ mysql -e "SHOW GLOBAL STATUS LIKE 'Threads_%'; SHOW VARIABLES LIKE 'max_connections';"
+-------------------+-------+
| Variable_name | Value |
+-------------------+-------+
| Threads_cached | 12 |
| Threads_connected | 385 |
| Threads_created | 98123 |
| Threads_running | 42 |
+-------------------+-------+
+-----------------+-------+
| Variable_name | Value |
+-----------------+-------+
| max_connections | 800 |
+-----------------+-------+
Ce que cela signifie : Beaucoup de threads créés suggèrent du churn. Un grand nombre de connexions connectées sur une petite machine est souvent overhead plutôt que travail utile.
Décision : Faire appliquer du pooling. Baisser max_connections. Définir des timeouts raisonnables. Si le churn de threads est élevé, vérifiez thread_cache_size et la réutilisation des connexions.
Task 6: Identify top waits (MySQL Performance Schema)
cr0x@server:~$ mysql -e "SELECT event_name, COUNT_STAR, SUM_TIMER_WAIT/1000000000000 AS total_s
FROM performance_schema.events_waits_summary_global_by_event_name
WHERE event_name LIKE 'wait/io/file/innodb/%'
ORDER BY SUM_TIMER_WAIT DESC
LIMIT 5;"
+----------------------------------------+------------+---------+
| event_name | COUNT_STAR | total_s |
+----------------------------------------+------------+---------+
| wait/io/file/innodb/innodb_log_file | 8421132 | 912.21 |
| wait/io/file/innodb/innodb_data_file | 20122341 | 311.45 |
| wait/io/file/innodb/innodb_temp_file | 1023311 | 88.17 |
+----------------------------------------+------------+---------+
Ce que cela signifie : De lourds waits sur le fichier de log impliquent une pression sur les fsync/logs. Les waits sur les fichiers de données indiquent un I/O-bound sur lectures/écritures.
Décision : Si les waits de log dominent, revoyez la taille des transactions, les réglages de durabilité et le sizing du redo log. Si les waits de données dominent, regardez le ratio cache et les plans de requête.
Task 7: Check InnoDB buffer pool efficiency
cr0x@server:~$ mysql -e "SHOW GLOBAL STATUS LIKE 'Innodb_buffer_pool_read%';"
+---------------------------------------+-----------+
| Variable_name | Value |
+---------------------------------------+-----------+
| Innodb_buffer_pool_read_requests | 987654321 |
| Innodb_buffer_pool_reads | 12345678 |
+---------------------------------------+-----------+
Ce que cela signifie : Reads vs read requests donne un ratio approximatif de misses du cache. Les misses ici sont coûteux sur le stockage VPS.
Décision : Si les misses sont élevés et que de la mémoire est disponible, augmentez le buffer pool. Si la mémoire n’est pas disponible, corrigez les requêtes/index et réduisez le working set.
Task 8: Find the worst queries by total time
cr0x@server:~$ mysql -e "SELECT DIGEST_TEXT, COUNT_STAR, ROUND(SUM_TIMER_WAIT/1e12,2) AS total_s, ROUND(AVG_TIMER_WAIT/1e9,2) AS avg_ms
FROM performance_schema.events_statements_summary_by_digest
ORDER BY SUM_TIMER_WAIT DESC
LIMIT 3\G"
*************************** 1. row ***************************
DIGEST_TEXT: SELECT * FROM orders WHERE user_id = ? ORDER BY created_at DESC LIMIT ?
COUNT_STAR: 1243321
total_s: 812.44
avg_ms: 0.65
Ce que cela signifie : La requête est bon marché par appel mais domine le temps à cause du volume. C’est une histoire de pooling et d’indexation, pas de « serveur plus gros ».
Décision : Assurez-vous d’un index composite correct (user_id, created_at). Envisagez du caching côté app. Ajoutez du pooling pour éviter l’overhead de connexion à fort QPS.
Task 9: Spot lock contention quickly
cr0x@server:~$ mysql -e "SHOW ENGINE INNODB STATUS\G"
TRANSACTIONS
------------
Trx id counter 123456789
Purge done for trx's n:o < 123456700 undo n:o < 0 state: running
History list length 9821
LATEST DETECTED DEADLOCK
------------------------
*** (1) TRANSACTION:
... WAITING FOR THIS LOCK TO BE GRANTED:
RECORD LOCKS space id 123 page no 456 n bits 72 index PRIMARY of table `app`.`accounts` trx id 123456780 lock_mode X locks rec but not gap waiting
Ce que cela signifie : Deadlocks et waits indiquent des écritures conflictuelles ou des longues transactions qui maintiennent des verrous.
Décision : Raccourcissez les transactions, ajoutez des index appropriés et rendez les mises à jour déterministes. Si un ORM fait des patterns « select puis update », corrigez-le.
Task 10: Check replication status (MySQL)
cr0x@replica:~$ mysql -e "SHOW REPLICA STATUS\G"
Replica_IO_Running: Yes
Replica_SQL_Running: Yes
Seconds_Behind_Source: 4
Last_SQL_Error:
Ce que cela signifie : Réplication saine avec un faible lag.
Décision : Vous pouvez déléguer des lectures/sauvegardes. Si le lag augmente, arrêtez d’y pousser des lectures lourdes et vérifiez les goulots I/O/applier.
Task 11: Confirm binlog retention and disk pressure
cr0x@server:~$ mysql -e "SHOW BINARY LOGS;"
+------------------+-----------+
| Log_name | File_size |
+------------------+-----------+
| binlog.000231 | 1073741824|
| binlog.000232 | 1073741824|
| binlog.000233 | 1073741824|
+------------------+-----------+
Ce que cela signifie : Les binlogs sont volumineux et nombreux. Sur le disque d’un VPS, les binlogs peuvent silencieusement manger le filesystem root et transformer une bonne semaine en une boucle de reboot.
Décision : Fixez une rétention explicite (expire settings), surveillez l’utilisation du filesystem, et assurez-vous que les réplicas ne bloquent pas les purges en étant hors ligne.
Task 12: Check filesystem capacity and inode exhaustion
cr0x@server:~$ df -h /var/lib/mysql
Filesystem Size Used Avail Use% Mounted on
/dev/vda1 200G 186G 14G 94% /
Ce que cela signifie : Vous êtes proche du plein. MySQL se comportera mal bien avant 100 % car il a besoin d’espace scratch pour les tables temporaires, logs et la récupération après crash.
Décision : Libérez de l’espace maintenant. Purgez les vieux logs, déplacez les sauvegardes hors hôte, augmentez le disque ou réduisez la rétention. Si vous êtes à 94 % en heures ouvrées, il est déjà trop tard.
Task 13: Check for temp table pressure (memory vs disk)
cr0x@server:~$ mysql -e "SHOW GLOBAL STATUS LIKE 'Created_tmp%tables';"
+-------------------------+----------+
| Variable_name | Value |
+-------------------------+----------+
| Created_tmp_disk_tables | 842113 |
| Created_tmp_tables | 902331 |
+-------------------------+----------+
Ce que cela signifie : Un ratio élevé de tmp tables sur disque signifie que les requêtes déversent sur le disque. Sur un VPS, c’est une taxe de latence.
Décision : Ajoutez des index, réduisez la taille des jeux de résultats, corrigez les patterns GROUP BY/ORDER BY, et évitez les gros sorts. Ne « résolvez » pas cela en gonflant aveuglément tmp_table_size jusqu’à atteindre un OOM.
Task 14: Check table/index sizes to understand your working set
cr0x@server:~$ mysql -e "SELECT table_schema, table_name,
ROUND((data_length+index_length)/1024/1024,1) AS mb
FROM information_schema.tables
WHERE table_schema='app'
ORDER BY (data_length+index_length) DESC
LIMIT 5;"
+-------------+----------------+------+
| table_schema| table_name | mb |
+-------------+----------------+------+
| app | events | 8420 |
| app | orders | 3120 |
| app | users | 980 |
+-------------+----------------+------+
Ce que cela signifie : Vos plus grandes tables définissent les besoins du buffer pool. Si le hot set ne tient pas, vous paierez de l’I/O indéfiniment.
Décision : Archivez/partitionnez les données froides, ajoutez des tables de synthèse ou déplacez l’analytics ailleurs. Monter en puissance ne résoudra pas un working set qui croît en permanence sur un seul VPS.
Trois mini-histoires d’entreprise issues du terrain
Mini-histoire 1 : L’incident causé par une mauvaise supposition
Ils faisaient tourner une application SaaS sur un unique VM 16 Go. Ça fonctionnait pendant des mois. Une nouvelle intégration est arrivée, et l’équipe a supposé « la base peut gérer plus de connexions ; ce n’est qu’une valeur de config ». Ils ont augmenté max_connections et ont passé à autre chose.
Le lundi suivant, le trafic a monté. L’app ouvrait des connexions par requête parce que le pool était mal configuré et effectivement désactivé. La base a accepté les connexions, bien sûr. Elle a aussi créé des threads, alloué des buffers par connexion et commencé à switcher de contexte comme un colibri caféiné.
La latence a grimpé. L’app a retryé. Ça a créé encore plus de connexions. L’OS a commencé à swapper. Maintenant chaque requête avait la spécialité des défauts de pages mémoire. Les ingénieurs regardaient les graphs CPU et se demandaient pourquoi « ajouter plus de connexions » n’augmentait pas le débit.
Ce qui a résolu le problème n’a pas été un tuning héroïque. Ils ont appliqué le pooling, réduit max_connections à une valeur que la machine pouvait réellement tenir, et ajouté une petite couche proxy pour absorber les rafales. La réplication est venue ensuite — pas pour les lectures, mais pour pouvoir faire des sauvegardes sans traîner la prod dans la mélasse.
Mini-histoire 2 : L’optimisation qui a mal tourné
Une autre entreprise avait des requêtes de rapport lentes. Quelqu’un a décidé que la meilleure solution était d’augmenter les tailles de tables temporaires et d’allouer plus de mémoire pour les sorts. Ils ont gonflé agressivement les buffers de session et globaux : sort buffers, join buffers, tmp table sizes. Les rapports allaient mieux en staging. Tout le monde a applaudi et a déployé.
La production n’a pas échoué immédiatement. Elle a échoué poliment. Sous concurrence, chaque connexion pouvait allouer de gros morceaux de mémoire. Des centaines de connexions multipliées par des buffers « raisonnables » par connexion deviennent « surprise, vous n’avez plus de RAM ». La machine a commencé à swapper. Ensuite la BD a atteint un mur de pauses : I/O disque a explosé, les fsync se sont mis en file, et le temps de réponse est devenu asymptotique.
L’équipe a chassé des fantômes : réseau ? fournisseur ? noyau ? En réalité, ils avaient transformé un budget mémoire contrôlé en roulette russe par connexion.
Le rollback a été humiliant : réduire les buffers par connexion, utiliser des index appropriés, réécrire les requêtes de rapport et déplacer le reporting lourd sur une réplique. Ils ont aussi appris une leçon précieuse : sur de petits hôtes, des limites de ressources prévisibles valent mieux que « rapide quand il est seul » à chaque fois.
Mini-histoire 3 : La pratique ennuyeuse mais correcte qui a sauvé la mise
Une troisième équipe faisait tourner MySQL avec une réplique dans une autre région. Rien de fancy : réplication asynchrone, sauvegardes logiques quotidiennes et un test de restauration hebdomadaire. Personne n’en parlait en réunion parce que c’était trop ennuyeux pour impressionner.
Puis leur primaire VPS a eu un incident de stockage. Le système de fichiers est devenu en lecture seule en plein milieu de la journée. La base de données a fait ce que font les bases dans cette situation : elle a râlé, arrêté de progresser, et a laissé le choix entre downtime et mauvaises décisions.
Ils n’ont pas paniqué en tunant. Ils ont promu la réplique en suivant leur runbook pratiqué, ont basculé l’application vers le nouveau primaire via le pooler, et ont retiré l’ancien primaire de la rotation. Le service s’est dégradé brièvement ; il ne s’est pas effondré. Plus tard, ils ont reconstruit l’ancien noeud depuis zéro et l’ont reseedé proprement.
Le détail clé : ils avaient répété. Le test ennuyeux de restauration signifiait qu’ils savaient que la réplique était utilisable, que les identifiants fonctionnaient et que l’app pouvait pointer ailleurs sans une expédition archéologique de configuration de trois heures.
Erreurs courantes : symptôme → cause racine → correctif
C’est là que la plupart des déploiements de bases sur VPS 16 Go périssent : pas à cause d’un gros bug, mais à cause d’un petit nombre de malentendus opérationnels courants.
1) Symptom: sudden spike in latency after deploy
Cause racine : tempête de connexions (nouveaux pods app démarrent, chacun ouvre un pool ; ou pooling désactivé et connexions par requête).
Fix : appliquer le pooling, limiter la concurrence au proxy, régler wait_timeout/interactive_timeout adéquatement, et garder max_connections réaliste. Si vous utilisez ProxySQL, configurez le multiplexage soigneusement et testez avec vos patterns de transaction.
2) Symptom: CPU high, QPS flat, “Threads_running” high
Cause racine : trop de requêtes concurrentes, contention de verrous, ou régression de plan provoquant des scans coûteux.
Fix : identifier les principaux digests, ajouter des index, réduire la concurrence via le pooling, et corriger les longues transactions. Ne « réglez » pas ça en augmentant les limites CPU avant de savoir pourquoi les threads tournent.
3) Symptom: load average huge, but CPU not fully used
Cause racine : attente I/O ou threads bloqués (pression de fsync, jitter disque, verrous metadata).
Fix : utilisez iostat -x, vérifiez les waits du Performance Schema, et cherchez de longues opérations DDL ou de sauvegarde sur le primaire. Déplacez les sauvegardes et lectures lourdes vers la réplique.
4) Symptom: replica lag increases steadily during peak
Cause racine : goulot I/O sur la réplique, applier single-thread, ou lectures lourdes qui privent la réplication.
Fix : augmentez les ressources de la réplique ou réduisez sa charge de lecture ; tunez la réplication parallèle si applicable ; assurez-vous que les relay logs et le disque ne sont pas saturés ; évitez d’exécuter des rapports massifs sur la réplique destinée au basculement.
5) Symptom: “Too many connections” errors even though the server seems idle
Cause racine : fuites de connexions ou transactions inactives longue durée bloquant les connexions ; épuisement du pool ; max_connections trop bas pour le pattern de concurrence réel.
Fix : trouvez le client qui fuit, imposez des timeouts, et mettez un proxy pour mettre en file. Augmentez max_connections seulement après avoir borné la mémoire par connexion et stabilisé le pooling.
6) Symptom: periodic stalls every few minutes
Cause racine : checkpoints ou flush qui bloquent, déversements de tmp tables, ou jobs en background qui causent des rafales.
Fix : lissez la charge d’écriture, vérifiez le comportement des pages sales, évitez les grosses transactions et planifiez les jobs batch. Sur le stockage VPS, les grosses rafales sont punies par des pics de latence.
Checklists / step-by-step plan
Étape par étape : rendre le pooling réel sur un VPS 16 Go
- Inventaire des clients : listez tous les services/jobs qui se connectent à la BD (app, workers, cron, outils BI).
- Corrigez le pooling côté app en premier : définissez une taille de pool explicite, une durée de vie max et un timeout d’inactivité. Évitez les « pools non bornés ».
- Mettez un proxy/pooler en frontal quand vous avez plusieurs nœuds app ou des rafales imprévisibles.
- Fixez server max_connections à un nombre que l’hôte peut soutenir. C’est un fusible de sécurité, pas un bouton de débit.
- Définissez des timeouts pour tuer les connexions inactives et les sessions coincées.
- Surveillez Threads_created, Threads_connected, aborted connects. Vous voulez des graphes stables et ennuyeux.
Étape par étape : ajouter la réplication sans en faire un loisir
- Choisissez la topologie : un primaire + un réplique est la baseline. Restez simple.
- Provisionnez la réplique dans un autre domaine de panne : hôte VPS différent, idéalement zone/zone provider différente.
- Activez le binary logging et fixez la rétention en fonction de vos besoins de récupération et de la taille disque.
- Seedez la réplique correctement : utilisez une méthode de snapshot cohérente (outil de backup physique ou dump logique avec locking/GTID appropriés).
- Vérifiez la santé de la réplication et alertez sur le lag et les erreurs.
- Rédigez et répétez les étapes de promotion. Répétez à nouveau lors des montées de version.
- Déplacez les sauvegardes et lectures lourdes vers la réplique. Gardez la réplique de basculement propre et non surchargée.
Étape par étape : tuner InnoDB pour un VPS 16 Go sans se saborder
- Dimensionnez correctement le buffer pool selon la marge mémoire et le working set. Ne tuez pas complètement le cache OS.
- Gardez les buffers par connexion modestes. Les buffers globaux sont plus sûrs que les surprises par connexion.
- Surveillez les déversements de tmp tables et corrigez les requêtes avant d’augmenter les limites mémoire.
- Confirmez que les réglages de durabilité correspondent aux besoins métier. Ne cargo-cultivez pas des paramètres dangereux juste pour gagner des benchs.
- Mesurez, changez une chose, mesurez de nouveau. « Tuner » en éditant 30 paramètres est la façon de créer des mystères.
FAQ
1) Dois-je choisir MySQL ou MariaDB pour un seul VPS 16 Go ?
Si vous êtes greenfield et voulez le support d’écosystème le plus fluide, choisissez MySQL 8.0. Si vous êtes déjà stable sur MariaDB, restez-y sauf si vous avez une raison opérationnelle ou de compatibilité clairement définie pour migrer.
2) Quand le pooling de connexions est-il vraiment obligatoire ?
Quand vous avez des rafales, de nombreuses instances app ou des requêtes courtes à haut QPS. Si vous voyez du churn de threads (Threads_created qui monte vite) ou des tempêtes de connexions lors des déploiements, c’est déjà obligatoire.
3) Puis-je juste augmenter max_connections au lieu de faire du pooling ?
Vous pouvez, comme on peut enlever un détecteur de fumée parce qu’il est bruyant. Un max_connections plus élevé augmente souvent l’usage mémoire et le contexte switching, aggravant les pannes.
4) La réplication n’est-elle que pour scaler les lectures ?
Non. Sur un VPS, la réplication sert surtout à la récupération, aux sauvegardes et à la maintenance. Le scaling en lecture est un bénéfice secondaire, pas la justification principale.
5) La réplication asynchrone est risquée. Dois-je utiliser semi-sync ?
Peut-être. Le semi-sync peut réduire la perte de données en cas de panne du primaire, mais sur les réseaux VPS il peut aussi ajouter de la latence tail. Décidez selon votre tolérance RPO et la stabilité réseau, puis testez en charge.
6) Pourquoi ma réplique prend-elle du retard quand je lance des rapports ?
Parce que les lectures concurrencent l’applier de réplication pour le CPU et l’I/O. Si la réplique est votre cible de basculement, gardez les reports lourds hors d’elle ou provisionnez une réplique analytics séparée.
7) Quel est le premier metric à surveiller sur un petit hôte DB ?
La latence disque (iostat await / utilisation) et la pression mémoire (activité du swap). Les graphs CPU peuvent mentir quand le vrai goulot est l’I/O wait ou le swapping.
8) Galera (MariaDB) est-ce une bonne idée sur des instances VPS ?
Ça peut fonctionner, mais ce n’est pas gratuit. La coordination d’écriture quasi-synchrone peut punir la latence, et les risques de split-brain augmentent si vous ne gérez pas correctement le quorum. Si vous n’êtes pas prêt à opérer un cluster, préférez primaire+réplique.
9) Ai-je besoin d’un proxy comme ProxySQL si mon app pool déjà des connexions ?
Pas toujours, mais souvent. Le pooling côté app scale encore les connexions serveur avec le nombre d’instances app. Un proxy apporte du backpressure, du routage, du multiplexage et une protection contre les tempêtes.
10) Quelle est l’amélioration sûre la plus rapide que je peux faire ce mois-ci ?
Ajoutez une réplique pour les sauvegardes et un basculement répété, et implémentez du pooling de connexions avec des limites strictes. Ces deux changements évitent la plupart des incidents « un seul VPS fondu ».
Conclusion : que faire la semaine prochaine
Si vous êtes sur un VPS 16 Go, vous n’avez pas le luxe d’une concurrence négligente. Vous n’avez pas non plus le luxe de « on ajoutera la réplication plus tard ». Le « plus tard » est le moment où vous restaurez depuis des sauvegardes pendant que votre CEO apprend un nouveau vocabulaire.
Étapes pratiques :
- Implémentez le pooling (côté app immédiatement ; ajoutez un proxy si vous avez plusieurs nœuds app ou du trafic en rafales).
- Fixez des limites de connexions réalistes et des timeouts ; traitez
max_connectionscomme un disjoncteur. - Ajoutez une réplique sur un VPS séparé et déplacez les sauvegardes/reporting là-bas.
- Rédigez un runbook de basculement et répétez-le en heures ouvrées avec des garde-fous.
- Exécutez le playbook de diagnostic rapide lors de votre prochaine montée de latence et documentez les enseignements.
MySQL vs MariaDB ne vous sauvera pas tout seul. La réplication et le pooling, si. Choisissez la base que votre équipe peut opérer calmement à 2h du matin, puis concevez le système pour que 2h du matin reste ennuyeux.