MySQL vs MongoDB : l’erreur « NoSQL parce que c’est tendance » qui tue les performances d’un VPS

Cet article vous a aidé ?

Vous louez un VPS modeste. Deux vCPU, quelques gigaoctets de RAM, un stockage « SSD » qui s’avère être le voisin bruyant de quelqu’un d’autre, et une charge de production
qui n’est pas sophistiquée—juste des utilisateurs, des commandes, des sessions et quelques jobs d’arrière-plan. Puis quelqu’un dit : « Utilisons MongoDB, c’est NoSQL, ça scale. »
Vous le déployez. Ça marche. Pendant une semaine. Puis la latence explose, la charge augmente, et vos graphiques ressemblent à un sismographe lors d’une apocalypse mineure.

Ce mode de défaillance est tragiquement courant : un choix de base de données fait sur des impressions plutôt que sur la physique. La physique, ce sont la RAM, les E/S disque,
et le comportement de votre moteur quand il ne tient pas en mémoire. Sur de petits VPS, c’est tout le jeu.

Ce qui détruit réellement les performances d’un VPS

« La performance de la base de données » sur un VPS ne concerne généralement pas des plans de requête exotiques ou un débit théorique. Il s’agit de ce qui arrive quand votre jeu de travail ne tient pas en RAM,
que votre disque est plus lent que prévu, et que votre configuration suppose une machine de classe serveur plutôt qu’une VM économique.

1) Les défauts de RAM deviennent des lectures disque, et les lectures disque deviennent de la latence

MySQL (InnoDB) et MongoDB (WiredTiger) reposent fortement sur le caching. Quand votre cache est trop petit, ils paginent constamment des données depuis le stockage. Sur un VPS partagé,
ce stockage peut être un « SSD » dont la profondeur de file d’attente est tenue en otage par un autre locataire. Votre application perçoit des blocages aléatoires de 200–800ms et commence à relancer.
Les relances augmentent la concurrence. La concurrence augmente la pression disque. Vous avez alors une boucle de rétroaction avec la personnalité d’un broyeur de bois.

2) L’overcommit et l’OOM killer ne négocient pas

Sur de petites machines, « donnez juste plus de cache à la base de données » signifie souvent « laissez Linux la tuer plus tard ». MongoDB en particulier peut sembler stable jusqu’à ce que le jeu de travail
grossisse légèrement, puis il commence à rivaliser avec le cache de pages de l’OS et tout le reste. Quand le noyau décide qu’il n’y a plus de mémoire, il n’envoie pas un email poli. Il choisit un
processus et le termine.

3) L’amplification d’écriture est le tueur de budget silencieux

Les bases de données n’écrivent pas ce que vous pensez qu’elles écrivent. Elles écrivent WAL/journal, elles écrivent des pages modifiées, elles compactent/fusionnent, elles fsyncent, elles font des mises à jour de métadonnées.
L’« une mise à jour de document » que vous imaginiez peut être plusieurs petites écritures aléatoires plus du travail de maintenance en arrière-plan. Sur les stockages de VPS, les écritures aléatoires sont coûteuses,
et des écritures aléatoires soutenues sont la façon dont vous découvrez que votre « SSD » est en réalité « une carte RAID de 2013 partagée avec 40 inconnus ».

4) Le mauvais modèle de données transforme le CPU en chaleur et les E/S en larmes

La requête la plus coûteuse est celle que vous n’avez pas indexée parce que vous avez supposé « NoSQL signifie pas de schéma, pas de planification ». La deuxième requête la plus coûteuse est celle
qui ne peut pas être efficacement indexée parce que le modèle de données encourage des documents changeants de forme, des imbrications profondes, ou « stocker des tableaux et filtrer dans le code applicatif ».
C’est là que MongoDB est mal utilisé : pas parce que MongoDB est mauvais, mais parce qu’il pardonne jusque-ce qu’il ne pardonne plus soudainement.

Une citation qui devrait figurer à toutes les rotations on-call : « L’espoir n’est pas une stratégie. » — Général Gordon R. Sullivan.

Guide de diagnostic rapide

Quand un VPS fond, vous n’avez pas le temps pour des débats philosophiques sur la théorie relationnelle. Vous avez besoin d’une séquence de triage qui trouve le goulot d’étranglement en quelques minutes.
Voici l’ordre qui résout la plupart des incidents.

Première étape : est-ce le CPU, la mémoire ou le disque ?

  • Lié au disque : iowait élevé, fsync lent, profondeur de file d’attente qui augmente, pics de latence corrélés à des rafales d’écriture.
  • Lié à la mémoire : activité de swap, défauts de page majeurs, OOM kills, cache de la base de données manquant, chute soudaine de performance quand le dataset croît.
  • Lié au CPU : CPU utilisateur élevé, requêtes lentes intensives en calcul, scans regex, parsing JSON, surcharge de compression ou cryptage.

Deuxième étape : la base de données est-elle le goulot d’étranglement ou l’application ?

  • La saturation du pool de connexions et l’accumulation de threads ressemblent souvent à « la base de données est lente » mais résultent d’une mauvaise dimension de l’app.
  • La contention de verrous ressemble à des requêtes lentes mais provient souvent d’une ligne/document « chaude » qui fait faire la queue à tout le monde.

Troisième étape : identifier les 1–3 requêtes/opérations qui causent le plus de dégâts

  • Dans MySQL : slow query log + EXPLAIN + état InnoDB.
  • Dans MongoDB : profiler + explain() + currentOp.

Quatrième étape : vérifier le comportement du cache

  • InnoDB : taux de hit du buffer pool, vidange des pages sales, pression du redo log.
  • WiredTiger : utilisation du cache, pression d’éviction, comportement des checkpoints.

Blague #1 : Si vous « corrigez » la latence en redémarrant la base de données chaque matin, vous avez inventé un ordonnanceur, pas la fiabilité.

MySQL vs MongoDB : les vrais compromis

MySQL : prévisible, ennuyeux, et brutalement efficace quand on le modélise bien

MySQL avec InnoDB est une bête de somme OLTP polyvalente. Il aime les données structurées, les schémas de requêtes stables et des indexes bien choisis. Ce n’est pas à la mode.
C’est une fonctionnalité.

Sur un petit VPS, MySQL l’emporte souvent parce que :

  • Le caching InnoDB est clair : vous dimensionnez le buffer pool, et vous pouvez généralement raisonner sur les taux de hit.
  • L’optimiseur de requêtes + les indexes gèrent de nombreux patterns d’accès sans vous forcer à tout intégrer dans un seul enregistrement.
  • Les outils opérationnels sont mûrs : slow logs, performance_schema, tableaux de bord courants, workflows de sauvegarde/restauration prévisibles.
  • La surcharge des données est souvent plus faible que pour des modèles documentaires lourds en JSON, surtout si vous normalisez les champs répétitifs.

MongoDB : puissant, flexible, et facile à malmener jusqu’à en faire une machine qui thrashe le disque

MongoDB brille quand vos données sont naturellement en forme de documents, quand vous avez besoin d’évolution de schéma flexible, quand la dénormalisation simplifie les lectures, et quand vous acceptez
les compromis opérationnels. Mais sur de petits VPS, il est souvent choisi pour la mauvaise raison : « NoSQL scale ». Ce n’est pas un plan ; c’est un horoscope.

MongoDB peut être parfaitement rapide sur un VPS si :

  • Votre jeu de travail tient en RAM ou vous avez un stockage rapide et constant.
  • Vous concevez des indexes comme si votre travail en dépendait (c’est le cas).
  • Vous contrôlez la croissance des documents et évitez les mises à jour pathologiques.
  • Vous comprenez l’éviction WiredTiger et le dimensionnement du cache.

La réalité du VPS : les petites machines punissent la surcharge

Les bases de données documentaires sont souvent utilisées avec des documents plus gros que nécessaire. Des documents plus gros signifient que moins tiennent dans le cache. Moins tiennent dans le cache signifie plus de lectures depuis le disque.
Sur un VPS contraint, chaque manque de cache est une taxe que vous payez avec intérêts.

Pendant ce temps, les « tables ennuyeuses » de MySQL tendent à être compactes, indexées et accédées via des plans de requête prévisibles. Ce n’est pas de la magie ; c’est simplement moins de surcharge par unité de données utile.

Transactions, contraintes et correctitude sous pression

Pour de nombreuses applications de production, le tueur de performance caché n’est pas la vitesse—c’est la logique compensatoire que vous ajoutez quand la correctitude est incertaine. Les contraintes de MySQL,
les clés étrangères (si utilisées prudemment) et la sémantique transactionnelle réduisent la quantité de travail « vérifier dans le code applicatif ».

MongoDB dispose d’un bon support des transactions dans les versions modernes, mais les transactions multi-documents ajoutent de la surcharge et peuvent déplacer votre goulot d’étranglement vers les verrous,
la pression sur l’oplog ou le lag de réplication. Si votre appli a fréquemment besoin d’invariants cross-entités, vous devrez soit imbriquer agressivement (ce qui crée de gros documents chauds), soit
construire un système relationnel sur un store documentaire. Ce n’est rarement le chemin le moins cher sur un VPS.

Les coûts de réplication et de durabilité sont des coûts réels

Si vous exécutez MongoDB correctement, vous gérez un replica set. Si vous exécutez MySQL correctement, vous gérez la réplication (ou au moins des sauvegardes et des binary logs). Les deux ont une surcharge.
Mais la « posture de correction par défaut » de MongoDB pousse souvent les équipes à déployer trois nœuds alors qu’elles n’avaient budgété qu’un seul. Sur un petit déploiement, ce n’est pas seulement un coût—c’est de la complexité opérationnelle et plus de surfaces de défaillance.

Contexte historique et faits intéressants

Un peu d’histoire aide à expliquer pourquoi ces systèmes se comportent comme ils le font, et pourquoi « NoSQL parce que c’est tendance » est une erreur organisationnelle récurrente.

  1. MySQL a été créé au milieu des années 1990 et est devenu la base de données par défaut du web précoce parce qu’il était rapide, simple et facile à déployer.
  2. InnoDB est devenu le moteur de stockage par défaut de MySQL dans MySQL 5.5 (2010), apportant la récupération après crash et le verrouillage au niveau ligne comme expérience standard.
  3. MongoDB est apparu en 2009, né à une époque où les développeurs luttaient contre des schémas rigides et où le scaling horizontal des SQL était encore considéré comme « difficile ».
  4. Le terme « NoSQL » s’est popularisé vers 2009 comme bannière pour les alternatives aux bases relationnelles, pas comme une garantie de performance.
  5. Le moteur WiredTiger de MongoDB est devenu le défaut dans MongoDB 3.2 (2016), remplaçant MMAPv1 et changeant drastiquement le comportement mémoire et disque.
  6. Le type JSON de MySQL est arrivé dans MySQL 5.7 (2015), une admission discrète que les données semi-structurées sont normales—et que les moteurs relationnels peuvent les gérer.
  7. Les stores documentaires échangent souvent l’amplification d’écriture contre la commodité de lecture via la dénormalisation ; sur des disques lents, ces écritures apparaissent comme de la latence et une pression sur les checkpoints.
  8. Les replica sets et le consensus (par ex. élections) introduisent des états opérationnels que les déploiements mono-nœud ne voient jamais, comme les rollbacks et le comportement visant à prévenir le split-brain.

Trois mini-récits d’entreprise du terrain

Mini-récit n°1 : L’incident causé par une mauvaise hypothèse

Une équipe SaaS de taille moyenne a lancé une nouvelle fonctionnalité : des timelines d’activité utilisateur. Quelqu’un a soutenu qu’un store documentaire était « évidemment le bon choix » parce que
les entrées de timeline ressemblaient à des événements JSON. Ils ont choisi MongoDB et stocké la timeline de chaque utilisateur comme un tableau à l’intérieur d’un seul document : un document par utilisateur,
en ajoutant de nouveaux événements à l’array.

En staging, c’était parfait. Les lectures étaient rapides : récupérer un document, afficher la timeline. Les écritures étaient aussi « rapides » parce que le dataset était minuscule et tout vivait en mémoire.
Le VPS semblait suffisant.

En production, quelques utilisateurs lourds ont créé d’énormes documents. Les mises à jour sont devenues coûteuses car chaque append n’était pas juste « ajouter un élément »—cela déclenchait un comportement de croissance du document,
plus d’écritures de pages fréquentes et du churn de cache. Soudain, le jeu de travail ne tenait plus. Les E/S disque ont grimpé aux heures de pointe, et la base de données a commencé à évincer constamment le cache.

L’hypothèse erronée était simple : « Une timeline utilisateur est un document. » Ce n’était pas le cas. C’était une collection avec des limites naturelles de pagination. La correction était aussi simple :
stocker les événements comme documents séparés avec un index sur (user_id, created_at), paginer les lectures, et limiter la rétention. Le post-mortem ne disait pas que MongoDB était lent.
Il disait qu’ils utilisaient MongoDB comme un store de blobs pratique et qu’ils furent choqués quand le blob devint gros.

Mini-récit n°2 : L’optimisation qui a mal tourné

Une autre entreprise faisait tourner MySQL sur un VPS et en avait assez de l’utilisation disque. Un ingénieur bien intentionné a décidé de compresser davantage : formats de lignes plus volumineux, champs plus compacts, et
utilisation agressive de JSON pour « éviter les jointures ». Ça avait l’air gagnant : les données ont rétréci, les backups sont devenus plus petits, et les tableaux de bord montraient une réduction de la croissance du stockage.

Puis l’utilisation CPU a grimpé, la latence p95 a doublé, et l’application a commencé à timeout pendant les pics de trafic. L’équipe a d’abord blâmé le réseau.
Ce n’était pas le réseau.

L’« optimisation » a échoué parce qu’elle a déplacé le travail du disque vers le CPU au pire moment. L’extraction JSON et les requêtes lourdes en fonctions empêchaient l’utilisation des indexes.
La compression a réduit le stockage mais augmenté le coût CPU et rendu les lectures moins amies du cache. Leur taux de hit du buffer pool semblait correct, pourtant les requêtes consommaient des cycles.

Ils ont annulé le design axé JSON, restauré des colonnes relationnelles pour les chemins chauds, et utilisé la compression seulement où elle n’entravait pas l’indexation.
La leçon n’était pas « ne jamais compresser ». La leçon était : ne pas optimiser pour le stockage sans mesurer le CPU et les plans de requête. Sur un petit VPS, vous n’avez pas de CPU de rechange à gaspiller.

Mini-récit n°3 : La pratique ennuyeuse mais correcte qui a sauvé la mise

Une équipe liée aux paiements utilisait MongoDB pour un event store interne et MySQL pour la facturation. Elle n’avait pas grand budget ops, alors elle a fait une chose peu sexy de façon cohérente :
tester les restaurations chaque mois. Pas « nous avons des backups ». De vraies restaurations, sur une machine propre, avec une checklist et un chronomètre.

Une nuit, leur fournisseur VPS a eu un problème de stockage au niveau hôte. La VM a redémarré vers un système de fichiers monté, mais les fichiers de base de données étaient incohérents. La base de données
ne démarrât pas proprement. La panique a duré environ dix minutes, ce qui est un record personnel.

Ils ont restauré le dernier snapshot, rejoué les logs quand applicable, et étaient de retour dans leur RTO. L’analyse post-incident montra que leur monitoring était correct,
mais leur avantage réel fut la confiance : ils savaient déjà que la chaîne de sauvegarde fonctionnait et combien de temps elle prenait.

La pratique ennuyeuse a sauvé la mise parce que les catastrophes ne sont jamais du genre amusant. Ce sont celles où vous réalisez que vous n’avez aucune idée si vos sauvegardes sont réelles.

Tâches pratiques : commandes, sorties, décisions

Voici les vérifications que j’exécute réellement sur de petits serveurs de production. Chaque tâche inclut : une commande, ce que signifie une sortie typique, et la décision suivante.
Exécutez-les pendant un incident, ou mieux : exécutez-les maintenant quand le système est calme et enregistrez des baselines.

Task 1: Confirm whether you’re CPU-bound or I/O-bound

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  41200  98200 981200    0    0    12    34  180  420 12  4 80  4  0
 4  2      0  19800  62100 712300    0    0   220   540  260  880 15  6 44 35  0
 3  1      0  20500  62100 710900    0    0   180   610  250  840 14  6 48 32  0

Sens : Le wa (iowait) est passé à ~35%. C’est un classique signature « le disque est le goulot d’étranglement », surtout si les plaintes de latence se recoupent.

Décision : Arrêtez de deviner les requêtes en premier ; vérifiez la latence disque et la pression sur le cache de la base ensuite.

Task 2: Check real disk latency and queueing

cr0x@server:~$ iostat -xz 1 3
Linux 6.5.0 (server)  12/30/2025  _x86_64_  (2 CPU)

avg-cpu:  %user   %nice %system %iowait  %steal   %idle
          14.22    0.00    5.11   31.67    0.40   48.60

Device            r/s     w/s   rKB/s   wKB/s  avgrq-sz avgqu-sz   await  r_await  w_await  %util
vda              35.0   120.0   980.0  6200.0     86.2     5.90   42.10    18.40    49.20  96.50

Sens : await à 42ms et %util proche de 100% signifie que le disque est saturé. Les écritures sont particulièrement lentes.

Décision : Réduisez la pression d’écriture (regroupement, indexes, tuning des checkpoints), ou améliorez le niveau de stockage. Sur un VPS, parfois la bonne solution est « payer pour de meilleures I/O ».

Task 3: See if you’re swapping (the quiet killer)

cr0x@server:~$ free -m
               total        used        free      shared  buff/cache   available
Mem:            3940        3605          45          60         289         110
Swap:           2047        1380         667

Sens : Le swap est activement utilisé et la mémoire available est minime. Si la DB swappe, chaque requête devient une opération de stockage.

Décision : Réduisez la taille du cache de la base, réduisez la concurrence de l’application, ou ajoutez de la RAM. Si vous ne faites rien, votre « tuning de base de données » devient « tuning du paging Linux ».

Task 4: Check for OOM kills

cr0x@server:~$ journalctl -k --since "2 hours ago" | tail -n 20
Dec 30 09:41:12 server kernel: Out of memory: Killed process 2145 (mongod) total-vm:5064820kB, anon-rss:2860100kB, file-rss:0kB, shmem-rss:0kB, UID:110 pgtables:7012kB oom_score_adj:0
Dec 30 09:41:12 server kernel: oom_reaper: reaped process 2145 (mongod), now anon-rss:0kB, file-rss:0kB, shmem-rss:0kB

Sens : Le noyau a tué mongod. Ce n’est pas un « bug MongoDB ». C’est une défaillance de capacité et de dimensionnement.

Décision : Réduisez immédiatement l’utilisation du cache, fixez des limites mémoire, et ajoutez de la marge. Ensuite, évaluez si ce VPS peut héberger la charge en toute sécurité.

Task 5: Confirm which process is eating memory

cr0x@server:~$ ps -eo pid,comm,rss,pcpu --sort=-rss | head
 2145 mongod   2923400  88.2
 1320 node      420800  12.1
  901 mysqld    210500   6.8
  755 redis-server 80400  1.2

Sens : MongoDB domine le RSS. Sur une petite machine, cela peut affamer tout le reste.

Décision : Si MongoDB est requis, limitez le cache WiredTiger. S’il est optionnel, reconsidérez l’architecture plutôt que de « combattre la physique ».

Task 6: MongoDB check: WiredTiger cache pressure

cr0x@server:~$ mongosh --quiet --eval 'db.serverStatus().wiredTiger.cache'
{
  "bytes currently in the cache" : 1702453248,
  "maximum bytes configured" : 2147483648,
  "tracked dirty bytes in the cache" : 392154112,
  "pages evicted by application threads" : 18342,
  "pages queued for eviction" : 742,
  "eviction server candidate queue empty when topping up" : 0
}

Sens : Le cache est proche du maximum et l’activité d’éviction est élevée. Les bytes sales sont importants, ce qui implique une pression d’écriture et du travail de checkpoint.

Décision : Si le jeu de travail ne tient pas, soit vous ajoutez de la RAM, soit vous réduisez documents/indexes, soit vous acceptez un stockage plus lent. Le tuning ne transformera pas un VPS 4GB en cache 64GB.

Task 7: MongoDB check: identify slow operations via profiler

cr0x@server:~$ mongosh --quiet --eval 'db.setProfilingLevel(1, { slowms: 50 })'
{ "was" : 0, "slowms" : 50, "sampleRate" : 1, "ok" : 1 }
cr0x@server:~$ mongosh --quiet --eval 'db.system.profile.find().sort({ts:-1}).limit(3).pretty()'
{
  "op" : "query",
  "ns" : "app.events",
  "command" : { "find" : "events", "filter" : { "userId" : "u_123" }, "sort" : { "ts" : -1 }, "limit" : 50 },
  "keysExamined" : 0,
  "docsExamined" : 51234,
  "millis" : 231,
  "planSummary" : "COLLSCAN"
}

Sens : COLLSCAN plus un grand docsExamined indique un index manquant/incorrect. Sur un VPS, les scans de collection sont la façon dont vous chauffez le datacenter.

Décision : Ajoutez un index correspondant au filter+sort (par ex. { userId: 1, ts: -1 }) et vérifiez avec explain(). Si les indexes dépassent la RAM, vous perdrez quand même—planifiez la capacité.

Task 8: MongoDB check: current operations and lock waits

cr0x@server:~$ mongosh --quiet --eval 'db.currentOp({ "secs_running": { "$gte": 2 } }).inprog.map(o => ({op:o.op, ns:o.ns, secs:o.secs_running, waiting:o.waitingForLock, desc:o.desc}))'
[
  {
    "op" : "command",
    "ns" : "app.events",
    "secs" : 18,
    "waiting" : true,
    "desc" : "conn1421"
  }
]

Sens : Des opérations longues en attente de verrous sont souvent causées par quelques écritures lourdes, des mises à jour mal bornées, ou des builds d’index au mauvais moment.

Décision : Stoppez l’hémorragie : mettez en pause les jobs d’arrière-plan, rate-limitez les écrivains, replanifiez les builds d’index, et trouvez le pattern de collection/document chaud.

Task 9: MySQL check: global status for buffer pool and reads

cr0x@server:~$ mysql -e "SHOW GLOBAL STATUS LIKE 'Innodb_buffer_pool%';"
+---------------------------------------+-----------+
| Variable_name                         | Value     |
+---------------------------------------+-----------+
| Innodb_buffer_pool_pages_total        | 131072    |
| Innodb_buffer_pool_pages_free         | 128       |
| Innodb_buffer_pool_read_requests      | 983420112 |
| Innodb_buffer_pool_reads              | 8420132   |
+---------------------------------------+-----------+

Sens : Le ratio de read_requests à reads suggère un taux de hit correct, mais 8.4M de lectures disque peuvent tout de même être douloureuses sur un stockage faible.

Décision : Si les lectures se corrèlent avec la latence, augmentez le buffer pool (si la mémoire le permet) ou réduisez l’empreinte du dataset/index. Si la mémoire est serrée, corrigez d’abord les requêtes et indexes.

Task 10: MySQL check: find top waits and hot statements

cr0x@server:~$ mysql -e "SELECT event_name, count_star, sum_timer_wait/1000000000000 AS total_seconds FROM performance_schema.events_waits_summary_global_by_event_name ORDER BY sum_timer_wait DESC LIMIT 5;"
+--------------------------------------+------------+--------------+
| event_name                           | count_star | total_seconds|
+--------------------------------------+------------+--------------+
| wait/io/file/innodb/innodb_log_file  |     182341 |        812.3 |
| wait/io/file/innodb/innodb_data_file |     491020 |        504.1 |
| wait/synch/mutex/innodb/buf_pool     |   12034011 |        220.7 |
| wait/io/file/sql/binlog              |     320114 |        118.9 |
| wait/lock/table/sql/handler          |      80012 |         62.2 |
+--------------------------------------+------------+--------------+

Sens : De lourdes attentes sur les fichiers de log signifient souvent une pression sur les fsync : trop de petites transactions, disque lent, ou réglages de durabilité forçant des syncs fréquents.

Décision : Envisagez de regrouper les écritures, tuner innodb_log_file_size, et vérifier les compromis de innodb_flush_log_at_trx_commit. Ne « optimisez » pas la durabilité sans décision métier.

Task 11: MySQL check: identify slow queries

cr0x@server:~$ sudo tail -n 5 /var/log/mysql/mysql-slow.log
# Time: 2025-12-30T09:52:41.123456Z
# Query_time: 2.184  Lock_time: 0.000  Rows_sent: 50  Rows_examined: 981230
SELECT * FROM events WHERE user_id='u_123' ORDER BY ts DESC LIMIT 50;

Sens : Beaucoup de rows examinées pour un petit jeu de résultats : index composite manquant, ou index existe mais ne peut pas supporter le tri.

Décision : Ajoutez ou corrigez l’index (par ex. (user_id, ts)), puis vérifiez avec EXPLAIN.

Task 12: MySQL check: verify plan with EXPLAIN

cr0x@server:~$ mysql -e "EXPLAIN SELECT * FROM events WHERE user_id='u_123' ORDER BY ts DESC LIMIT 50\G"
*************************** 1. row ***************************
           id: 1
  select_type: SIMPLE
        table: events
   partitions: NULL
         type: ALL
possible_keys: idx_user_ts
          key: NULL
      key_len: NULL
          ref: NULL
         rows: 980000
     filtered: 10.00
        Extra: Using where; Using filesort

Sens : type: ALL signifie scan complet ; Using filesort signifie un tri supplémentaire. MySQL vous dit exactement pourquoi c’est lent.

Décision : Créez l’index composite correct et confirmez que key est utilisé. Si ça scanne encore, vos types de données/collations ou la forme de la requête peuvent bloquer l’usage de l’index.

Task 13: Check connection pile-ups (app can DoS the DB)

cr0x@server:~$ ss -s
Total: 941 (kernel 0)
TCP:   811 (estab 640, closed 121, orphaned 0, synrecv 0, timewait 121/0), ports 0

Transport Total     IP        IPv6
RAW       0         0         0
UDP       7         6         1
TCP       690       651       39
INET      697       657       40
FRAG      0         0         0

Sens : Des centaines de connexions TCP établies peuvent indiquer une mauvaise configuration du pool de connexions ou un thundering herd lors de relances.

Décision : Limitez la concurrence de l’app, utilisez une taille de pool raisonnable, et envisagez un proxy (pour MySQL) si le churn de connexions est élevé.

Task 14: Check filesystem space and inode pressure

cr0x@server:~$ df -h
Filesystem      Size  Used Avail Use% Mounted on
/dev/vda1        60G   55G  2.1G  97% /
cr0x@server:~$ df -i
Filesystem      Inodes  IUsed   IFree IUse% Mounted on
/dev/vda1      3932160 3919000  13160  100% /

Sens : Le disque est presque plein et les inodes sont épuisés. Les bases de données détestent ça. Les logs aussi. Tout le monde aussi.

Décision : Libérez de l’espace immédiatement (rotation des logs, suppression des vieux backups), puis ajoutez du monitoring et des politiques de rétention pour ne plus atteindre 97%.

Task 15: Zapping “mystery latency” by measuring fsync cost

cr0x@server:~$ sudo dd if=/dev/zero of=/var/lib/db/fsync-test bs=4k count=4096 conv=fdatasync
4096+0 records in
4096+0 records out
16777216 bytes (17 MB, 16 MiB) copied, 1.84213 s, 9.1 MB/s

Sens : 16MiB avec fdatasync prenant ~1.8s est un signal d’alerte pour des charges d’écriture durables. Votre base de données percevra cela comme de la latence de commit.

Décision : Si votre charge est orientée écriture, migrez vers un meilleur stockage (NVMe avec latence prévisible) ou réduisez la fréquence des fsync via du batching. Ne camouflez pas cela en « désactivant la durabilité » sauf si l’entreprise accepte explicitement la perte de données.

Task 16: Verify MySQL durability settings before “optimizing” them

cr0x@server:~$ mysql -e "SHOW VARIABLES WHERE Variable_name IN ('innodb_flush_log_at_trx_commit','sync_binlog','innodb_doublewrite');"
+------------------------------+-------+
| Variable_name                | Value |
+------------------------------+-------+
| innodb_doublewrite           | ON    |
| innodb_flush_log_at_trx_commit | 1   |
| sync_binlog                  | 1     |
+------------------------------+-------+

Sens : Ces réglages privilégient la durabilité. C’est bien—jusqu’à ce que votre disque ne puisse pas suivre.

Décision : Si la latence de commit est le goulot d’étranglement, adressez d’abord les I/O et le batching. Ensuite seulement envisagez d’assouplir les réglages avec l’accord explicite des parties prenantes.

Blague #2 : « Nous avons choisi NoSQL pour les performances » revient à dire que vous avez acheté un pick-up pour gagner une course de vélos.

Erreurs courantes : symptômes → cause racine → correctif

Ce ne sont pas des théories. Ce sont les patterns qui apparaissent sur de vrais VPS à 3h du matin.

1) Symptom: p95 latency spikes every few minutes

Cause racine : Vagues de checkpoints/flush. Les checkpoints MongoDB ou la vidange des pages sales MySQL frappent un disque saturé.

Correctif : Réduisez les rafales d’écriture (batch, queue), assurez un espace disque libre suffisant, augmentez la RAM pour le cache, et migrez vers un stockage à latence plus faible. Pour MySQL, vérifiez la taille des logs et le comportement de flush ; pour MongoDB, surveillez l’éviction et le timing des checkpoints.

2) Symptom: MongoDB is “fast in dev, slow in prod”

Cause racine : Le dataset de dev tient en RAM. Celui de prod non. Les taux de miss du cache explosent ; les indexes ne tiennent pas ; le disque devient la base de données.

Correctif : Chargez des données réalistes en staging. Mesurez le jeu de travail. Faites respecter la discipline de taille des documents et créez des indexes pour les requêtes réelles, pas imaginaires.

3) Symptom: MySQL CPU high, but disk looks fine

Cause racine : Requêtes lourdes en fonctions, extraction JSON dans les chemins chauds, ou indexes manquants provoquant des scans et tris gourmands en CPU.

Correctif : Utilisez EXPLAIN, ajoutez des indexes appropriés, déplacez les champs calculés vers des colonnes stockées si nécessaire, et évitez de trier de grands ensembles sans index.

4) Symptom: OOM kills or random database restarts

Cause racine : Le dimensionnement du cache suppose plus de RAM que vous n’en avez ; fragmentation mémoire ; services additionnels sur le même VPS ; spirale de mort du swap.

Correctif : Limitez les caches (WiredTiger et InnoDB), réservez de la RAM pour l’OS et l’app, et réduisez les workloads co-localisés. Si vous avez besoin de la DB, donnez-lui la machine.

5) Symptom: “We added an index and it got slower”

Cause racine : L’index augmente l’amplification d’écriture ; les builds d’index en arrière-plan concourent pour les I/O ; l’index ne correspond pas à la forme de la requête et reste inutilisé.

Correctif : Confirmez l’utilisation (EXPLAIN ou MongoDB explain()). Construisez les indexes hors pic. Utilisez l’ensemble minimal d’indexes qui supporte les requêtes réelles.

6) Symptom: Replication lag grows during traffic peaks

Cause racine : Le primaire est saturé ; les secondaires ne peuvent pas appliquer les opérations assez vite ; le disque est le goulot ; trop de petites transactions.

Correctif : Améliorez les I/O du primaire, regroupez les écritures, assurez-vous que les secondaires ont des performances de stockage similaires, et évitez les transactions longues qui retardent l’application.

7) Symptom: “MongoDB uses all RAM; that must be bad”

Cause racine : Confusion entre usage du cache et fuite mémoire. Les bases de données veulent de la mémoire ; le problème est quand elles la volent à l’OS et déclenchent swap/OOM.

Correctif : Définissez des limites explicites de cache et laissez de la place pour le cache de fichiers et les autres processus. La mémoire sans marge n’est que du downtime futur.

Listes de contrôle / plan étape par étape

Checklist de décision : cette charge doit-elle être MySQL ou MongoDB sur un VPS ?

  • Choisir MySQL si vous avez besoin de jointures, de contraintes strictes, d’un schéma stable, d’updates transactionnels entre entités ou de performance prévisible sur une petite RAM.
  • Choisir MongoDB si le pattern d’accès principal est centré sur le document, si vos documents restent bornés, si vos indexes tiennent en mémoire, et si vous pouvez l’exploiter avec la discipline d’un replica set.
  • Ne choisissez pas aveuglément : modélisez les chemins chauds et mesurez le jeu de travail par rapport à la RAM. Si vous ne pouvez pas le faire, vous jouez au hasard.

Étape par étape : stabiliser une base de données VPS en difficulté en 60–120 minutes

  1. Arrêtez l’hémorragie : rate-limitez les écrivains lourds, mettez en pause les jobs non critiques, réduisez temporairement la concurrence des requêtes.
  2. Confirmez le goulot : vmstat + iostat + contrôles mémoire.
  3. Trouvez les principaux coupables : slow log MySQL / performance_schema ; profiler/currentOp MongoDB.
  4. Corrigez la pire requête en premier : ajoutez le bon index ou réécrivez la requête pour utiliser un index existant.
  5. Limitez les caches en sécurité : laissez de la RAM pour l’OS et l’app ; évitez le swap.
  6. Vérifiez l’état et l’espace disque : les disques presque pleins se comportent mal ; l’épuisement d’inodes est une panne furtive.
  7. Retestez sous charge : confirmez que p95 et await disque s’améliorent ; surveillez l’apparition de nouveaux goulots.
  8. Notez la baseline : taux de hit, latence disque, nombre de connexions, ops/sec typiques. Cela devient votre futur « diagnostic rapide ».

Étape par étape : prévenir l’échec « NoSQL parce que c’est tendance »

  1. Écrivez 5 requêtes réelles que votre appli exécutera chaque seconde et chaque minute.
  2. Pour MongoDB : définissez les limites des documents et les règles de croissance maximale.
  3. Pour MySQL : définissez des indexes pour les chemins chauds et conservez les colonnes chaudes typées et indexées (pas enterrées dans du JSON).
  4. Chargez des données réalistes en staging et forcez un scénario de miss cache en contraignant la RAM ou en utilisant une instance plus petite.
  5. Mesurez la latence fsync du disque et vérifiez qu’elle correspond à vos attentes de durabilité.
  6. Exercez des restaurations selon un planning. Votre futur vous n’aura pas le temps d’apprendre pendant une panne.

FAQ

1) Is MongoDB always slower than MySQL on a VPS?

Non. MongoDB peut être très rapide quand votre pattern d’accès est centré sur le document et que votre jeu de travail tient en RAM. L’échec typique sur VPS est « le dataset a grandi, le cache non ».

2) Why does MongoDB performance fall off a cliff when data grows?

Parce que les manques de cache se transforment en lectures disque, et la surcharge document/index peut gonfler le jeu de travail. Quand la pression d’éviction monte sur un stockage lent, la latence explose.

3) Can I just increase WiredTiger cache and be done?

Pas en toute sécurité sur un petit VPS. Si vous affamez l’OS et les autres services, vous échangez « requêtes lentes » contre tempêtes de swap et OOM kills. Limitez-le délibérément et laissez de la marge.

4) Is MySQL “better” because it’s relational?

Il est meilleur quand vos données nécessitent des relations, des contraintes et des requêtes indexées prévisibles. Il est pire quand votre application est vraiment en forme de document et que vous feriez des joins maladroits autrement.

5) Why do people say “NoSQL scales”?

Historiquement, beaucoup de systèmes NoSQL ont été conçus avec la distribution horizontale comme objectif principal. Mais l’échelle n’est pas automatique, et sur un seul petit VPS vous n’échelonnez rien horizontalement.

6) What’s the biggest MongoDB schema mistake on VPS?

Des documents non bornés (tableaux qui croissent sans fin, structures profondément imbriquées, ou « stocker tout l’historique dans un doc »). Ça paraît élégant jusqu’à ce que ça devienne un objet massif et chaud qui détruit la localité du cache et le comportement d’écriture.

7) What’s the biggest MySQL mistake on VPS?

Tourner avec des configs par défaut qui supposent plus de RAM et d’I/O que vous n’en avez, plus des indexes composites manquants. Puis vous essayez de « corriger » en ajoutant du CPU, ce qui ne résout pas les blocages disque.

8) Should I disable fsync/durability settings to get performance back?

Seulement si l’entreprise accepte explicitement la perte de données et que vous documentez le risque. La plupart des corrections « durabilité désactivée » sont des pansements temporaires qui transforment le prochain incident en théâtre de récupération de données.

9) If my app uses JSON heavily, does that mean MongoDB is the obvious choice?

Non. Le JSON comme format d’échange ne signifie pas automatiquement qu’une base documentaire est optimale. MySQL peut stocker et indexer des colonnes structurées et garder du JSON pour les champs froids.

10) What’s the simplest safe strategy for a tiny VPS?

Une base de données, un job, assez de RAM pour le cache, et un stockage prévisible. Si vous ne pouvez pas vous le permettre, vous ne pouvez pas non plus vous permettre la complexité surprise—choisissez le moteur le plus simple qui correspond au modèle de données.

Conclusion : prochaines étapes réalisables

L’erreur « NoSQL parce que c’est tendance » n’est pas le fait de choisir MongoDB. C’est le choix sans comprendre ce que votre VPS peut payer : manques de RAM, fsyncs, compaction et éviction de cache.
MySQL est souvent le gagnant ennuyeux sur les petites machines parce qu’il est compact, prévisible et facile à tuner pour des patterns OLTP courants. MongoDB peut être un excellent choix quand vos documents sont bornés et que vos indexes tiennent—
surtout si vous concevez autour de requêtes réelles, pas de rêves.

Prochaines étapes pratiques :

  • Exécutez le guide de diagnostic rapide une fois pendant une période calme et enregistrez les baselines iostat, mémoire et stats de cache DB.
  • Sélectionnez vos 5 requêtes principales et prouvez qu’elles utilisent des indexes (EXPLAIN ou MongoDB explain()).
  • Définissez des limites explicites de cache pour que l’OS n’ait jamais besoin de « négocier » avec le swap.
  • Testez une restauration. Pas « nous avons des sauvegardes ». Une vraie restauration sur une machine propre.
  • Si await disque est constamment élevé, arrêtez de tuner autour et améliorez le stockage ou déplacez la base hors du VPS.
← Précédent
Latences RAM sans douleur : MHz vs CL et quoi acheter
Suivant →
WordPress 404 sur les articles : réparer les permaliens sans casser le SEO

Laisser un commentaire