MySQL vs PostgreSQL sur un VPS 1 Go : ce qui est réellement utilisable (et les réglages qui le rendent possible)

Cet article vous a aidé ?

Vous louez un petit VPS, installez une base de données « réelle », et cinq minutes plus tard la machine commence à swapper comme si elle était facturée à la page. L’application semble fluide en dev, puis la production se transforme en catastrophe au ralenti : requêtes qui stagnent, moyenne de charge qui grimpe, et votre outil de supervision commence à écrire de la poésie.

Sur 1 Go de RAM, vous ne pouvez pas vous permettre d’être romantique à propos des bases de données. Il faut être précis. Ceci est un guide pratique et opinionné pour faire tourner MySQL (InnoDB) ou PostgreSQL sur un VPS 1 Go sans s’automutiler : le calcul mémoire, les réglages qui comptent, et les vérifications qui vous disent ce qui se passe réellement.

La seule question qui compte sur 1 Go : quel est votre goulot d’étranglement ?

Sur un VPS 1 Go vous payez toujours une sorte d’impôt : pression mémoire, latence disque, CPU steal, ou overhead des connexions. Le choix de la base compte, mais moins que la forme de votre charge et que vous ayez plafonné les pièges évidents. Si votre appli ouvre 200 connexions, MySQL comme PostgreSQL vont en souffrir. Si votre stockage est lent et que vous imposez une durabilité synchrone avec des caches minuscules, les deux donneront l’impression de fonctionner sur un pager des années 1999.

L’objectif n’est pas « rapide ». L’objectif est « prévisible ». Prévisible signifie :

  • L’usage mémoire a un plafond que vous pouvez expliquer.
  • Les écritures disque sont maîtrisées (ou au moins pas en rafales).
  • Les connexions sont bornées et poolées.
  • L’entretien en tâche de fond ne vous surprend pas à 3 h du matin.

C’est la norme. Tout le reste, c’est de l’improvisation comique, et vos utilisateurs n’ont pas acheté de billets pour ça.

Verdict d’utilisabilité : qui est réellement utilisable sur 1 Go ?

Si vous voulez la réponse directe

Les deux sont utilisables sur 1 Go de RAM pour de petites charges, mais ils échouent différemment :

  • PostgreSQL est plus sensible aux « trop nombreuses connexions » et aux surprises de mémoire par requête (sorts, hashes, work_mem). Il est extrêmement stable quand vous posez des limites strictes et utilisez du pooling. Il a aussi l’avantage de ne pas corrompre votre modèle mental aussi facilement.
  • MySQL (InnoDB) est plus sensible aux caches mal dimensionnés et au comportement de flush, et il peut brûler silencieusement de la mémoire dans des tampons par-connexion si vous le laissez faire. Il peut paraître plus réactif pour des lectures/écritures OLTP simples quand il est réglé de façon conservatrice, surtout avec peu de connexions.

Ce que je recommande (opinion)

Si vous construisez une application web générale, surtout avec des requêtes de reporting, migrations et « on ajoutera une fonctionnalité plus tard », utilisez PostgreSQL et placez PgBouncer devant. Les garde‑fous opérationnels sont plus clairs, et l’écosystème (migrations, contraintes, comportement transactionnel des DDL) réduit souvent les bizarreries applicatives.

Si vous exécutez une charge OLTP simple de type clé‑valeur avec un framework qui attend MySQL, ou si vous avez déjà de l’expérience opérationnelle avec MySQL, utilisez MySQL et plafonnez les connexions sévèrement. Ne prétendez pas que les configs par défaut sont « adaptées aux petits serveurs ». Elles sont « adaptées aux benchmarks éditeur ».

Une phrase à retenir : Sur 1 Go, PostgreSQL est un bon citoyen si vous le tenez en laisse ; MySQL est un bon citoyen si vous lui donnez les bonnes portions.

Blague courte #1 : Un VPS 1 Go, c’est comme un studio : vous pouvez recevoir des invités, mais pas s’ils apportent tous des meubles.

Modèles de mémoire : pourquoi « ça tient » n’est pas un plan

Le budget 1 Go dont vous disposez réellement

« 1 Go RAM » est du marketing. Votre noyau en prend une partie. Votre outil de monitoring en prend une autre. Vos sessions SSH, cron et démons de logs prennent aussi leur part. Si c’est une distro moderne, systemd existe pour vous rappeler que l’entropie est réelle.

Sur un VPS 1 Go, un budget sûr et réaliste pour la mémoire du processus de la base est souvent de 500–750 Mo, selon ce qui tourne d’autre. Si vous exécutez l’application sur le même VPS, réduisez ce budget. Si vous utilisez Docker, réduisez encore, puis questionnez vos choix de vie.

Mémoire PostgreSQL en un paragraphe

PostgreSQL a de la mémoire partagée (principalement shared_buffers) et beaucoup de mémoire par backend : chaque connexion est un processus (sauf si vous utilisez des options récentes comme les modes --single, mais ce n’est généralement pas le cas). La mémoire par requête est régie par work_mem (sort/hash) et peut se multiplier selon les opérations concurrentes. C’est le piège classique : vous réglez work_mem sur une valeur qui a l’air raisonnable, puis une requête en utilise plusieurs à la fois sur plusieurs connexions, et vous passez de « ça va » à « OOM killer » en un déploiement.

Mémoire MySQL en un paragraphe

MySQL avec InnoDB a un gros cache partagé (innodb_buffer_pool_size) plus des tampons par-connexion (sort/join/read buffers) et de la mémoire interne (dictionnaire, adaptive hash, etc.). Le buffer pool est généralement le levier principal : il doit être assez grand pour éviter trop de lectures disque mais pas assez pour affamer le cache de fichiers de l’OS et tout le reste. Sur 1 Go, le buffer pool « correct » est souvent plus petit que vos instincts.

Deux règles qui évitent la plupart des désastres sur 1 Go

  1. Plafonnez les connexions pour que la mémoire par-connexion ne vous multiplie pas jusqu’à la défaillance.
  2. Réservez de la mémoire pour l’OS et le cache du système de fichiers ; les bases de données n’ont pas de droit exclusif sur la RAM parce qu’elles sont dramatiques.

Faits intéressants et contexte historique (à sortir en réunion pour paraître sage)

  1. PostgreSQL descend du projet POSTGRES à UC Berkeley (années 1980), et la partie « SQL » a été ajoutée plus tard — l’ADN du design privilégie l’extensibilité et la justesse plutôt que « livrer vite ».
  2. MySQL est devenu un incontournable du web à la fin des années 1990/début 2000 en grande partie parce qu’il était léger et simple à déployer, pas parce qu’il était le meilleur en transactions à l’époque.
  3. InnoDB était à l’origine un moteur tiers ; il est devenu le défaut dans MySQL 5.5. Ce changement a modifié le jeu opérationnel : la récupération après crash et le MVCC sont devenus monnaie courante pour les utilisateurs MySQL.
  4. Le vacuuming de PostgreSQL existe parce que MVCC conserve d’anciennes versions de lignes ; si vous ne vacuumez pas, vous n’avez pas seulement du bloat — vous risquez un wraparound d’ID de transaction.
  5. MySQL avait historiquement des valeurs par défaut de durabilité différentes selon des réglages comme innodb_flush_log_at_trx_commit ; beaucoup d’histoires « MySQL est rapide » étaient en réalité « MySQL ne synchronisait pas chaque commit ».
  6. Le WAL (write‑ahead log) de PostgreSQL est conçu sur un principe similaire à d’autres bases sérieuses : la durabilité vient du fait d’écrire l’intention dans un journal avant de flush les pages de données.
  7. Le doublewrite buffer d’InnoDB existe pour protéger contre les écritures partielles de pages ; il échange des écritures supplémentaires contre moins de corruptions au reboot.
  8. Le planner de PostgreSQL peut être extrêmement intelligent, mais sur des petites machines il peut aussi coûter cher si vous lui demandez de trier de gros résultats intermédiaires en mémoire qu’il n’a pas.
  9. Le query cache de MySQL était jadis une « fonctionnalité de perf » qui devenait souvent un bug de perf ; il a été déprécié et supprimé pour de bonnes raisons.

Et une idée opérationnelle paraphrasée que j’ai vue survivre à chaque post‑mortem : idée paraphrasée de John Allspaw : la fiabilité vient du fait de concevoir des systèmes qui s’attendent à échouer, pas de prétendre qu’on peut l’empêcher.

Réglages de base : MySQL sur 1 Go qui ne se dévore pas lui‑même

Quand MySQL a du sens sur 1 Go

MySQL convient si votre charge est surtout des requêtes indexées simples, si vous gardez les connexions basses, et si vous n’essayez pas de transformer le VPS en trophée de benchmark. Le critère de réussite est une latence stable sous une concurrence modeste.

Une cible mémoire raisonnable pour MySQL/InnoDB

Supposez que vous pouvez allouer 600–700 Mo à mysqld sur un VPS 1 Go dédié à la base. Si vous co‑hébergez l’application, réduisez à 400–500 Mo. Affectez ensuite :

  • innodb_buffer_pool_size : typiquement 256–512 Mo
  • innodb_log_file_size : modeste (par ex. 64–128 Mo) pour garder un temps de récupération raisonnable
  • tampons par connexion : gardez‑les petits ; plafonnez les connexions

Extrait de configuration de base (MySQL 8‑ish)

Ceci est volontairement conservateur. Vous pouvez assouplir plus tard ; vous ne pouvez pas rendre un serveur OOM vivant.

cr0x@server:~$ sudo bash -lc 'cat >/etc/mysql/mysql.conf.d/99-vps-1gb.cnf <<"EOF"
[mysqld]
# Core
max_connections = 60
skip_name_resolve = 1

# InnoDB memory
innodb_buffer_pool_size = 384M
innodb_buffer_pool_instances = 1

# InnoDB durability / IO
innodb_flush_method = O_DIRECT
innodb_flush_log_at_trx_commit = 1
sync_binlog = 1
innodb_io_capacity = 200
innodb_io_capacity_max = 400

# Redo log (MySQL 8 uses innodb_redo_log_capacity; keep modest)
innodb_redo_log_capacity = 256M

# Temp / sort behavior
tmp_table_size = 32M
max_heap_table_size = 32M
sort_buffer_size = 1M
join_buffer_size = 256K
read_buffer_size = 256K
read_rnd_buffer_size = 512K

# Avoid surprise thread memory
thread_cache_size = 16

# Slow query visibility
slow_query_log = 1
long_query_time = 0.5
log_slow_admin_statements = 1
EOF
systemctl restart mysql'
...output...

Remarques importantes :

  • max_connections n’est pas négociable sur 1 Go. Si votre appli a besoin de plus, elle a besoin de pooling.
  • O_DIRECT aide à éviter le double cache (InnoDB buffer pool + cache OS). Sur certaines combinaisons VPS/stockage c’est gagnant ; sur d’autres, c’est « correct ». Testez, mais n’en faites pas une obsession.
  • Les petits tampons par connexion sont les héros discrets. Les gros tampons par thread sont la façon dont vous vous réveillez avec un OOM lors d’un pic de trafic.

Journaux binaires sur 1 Go : choisissez une position

Si vous n’avez pas besoin de réplication ou de récupération point‑dans‑le‑temps, désactivez les binlogs pour réduire la pression d’écriture. Si vous en avez besoin, gardez‑les mais faites une rotation agressive et surveillez le disque.

Réglages de base : PostgreSQL sur 1 Go qui reste debout

Quand PostgreSQL a du sens sur 1 Go

PostgreSQL est excellent si vous valorisez la justesse, les contraintes et des sémantiques prévisibles, et si vous êtes prêts à gérer les connexions. Ce n’est pas « lourd » ; c’est honnête sur le coût du travail.

Une cible mémoire raisonnable pour PostgreSQL

Sur un VPS 1 Go dédié à la base, prévoyez grosso modo 600–750 Mo pour les processus Postgres plus la mémoire partagée, et laissez le reste pour le cache OS et la santé du système. Puis :

  • shared_buffers : 128–256 Mo (rarement plus sur 1 Go)
  • work_mem : 2–8 Mo (oui, aussi bas ; la concurrence multiplie)
  • maintenance_work_mem : 64–128 Mo (vacuum/index builds en ont besoin, mais ne mettez pas 512 Mo sur 1 Go sauf si vous aimez le paging)
  • max_connections : maintenez‑le bas, et utilisez PgBouncer

Extrait de configuration de base (PostgreSQL 14–17 style)

cr0x@server:~$ sudo bash -lc 'PGVER=$(psql -V | awk "{print \$3}" | cut -d. -f1); \
CONF="/etc/postgresql/$PGVER/main/postgresql.conf"; \
cat >>"$CONF" <<"EOF"

# 1GB VPS baseline (conservative)
max_connections = 40

shared_buffers = 192MB
effective_cache_size = 512MB

work_mem = 4MB
maintenance_work_mem = 96MB

wal_buffers = 8MB
checkpoint_timeout = 10min
max_wal_size = 1GB
min_wal_size = 256MB
checkpoint_completion_target = 0.9

# Autovacuum: keep it on, but don’t let it stampede
autovacuum_max_workers = 2
autovacuum_naptime = 30s
autovacuum_vacuum_cost_limit = 1000
autovacuum_vacuum_cost_delay = 10ms

# Observability
log_min_duration_statement = 500
log_checkpoints = on
log_autovacuum_min_duration = 1000
EOF
systemctl restart postgresql'
...output...

Pourquoi ces choix :

  • effective_cache_size est un indice pour le planner, pas une allocation. Sur 1 Go, ne mentez pas exagérément.
  • Un petit work_mem réduit le risque d’OOM. Si une requête spécifique a besoin de plus, donnez‑lui par session pour ce job.
  • Autovacuum n’est pas optionnel. Si vous le désactivez pour « économiser des ressources », vous ne faites que contracter des problèmes à taux d’intérêt prédateur.

Blague courte #2 : Désactiver autovacuum sur Postgres, c’est comme couper le détecteur de fumée parce qu’il fait du bruit.

Les connexions vont vous tuer : pool et plafonds stricts

Pourquoi PostgreSQL paraît « pire » face à des tempêtes naïves de connexions

Chaque connexion PostgreSQL est un processus. Ce n’est pas un défaut moral ; c’est un choix de conception qui rend l’isolation et l’observabilité propres. Sur 1 Go, cela signifie aussi que 150 connexions équivalent à une attaque par déni de service que vous vous êtes infligée.

PgBouncer n’est pas « un extra », c’est la ceinture de sécurité

Placez PgBouncer en mode pooling par transaction pour le trafic web typique. Il vous permet de garder max_connections bas tout en servant une plus grande concurrence client en toute sécurité.

cr0x@server:~$ sudo bash -lc 'apt-get update -y && apt-get install -y pgbouncer'
...output...
cr0x@server:~$ sudo bash -lc 'cat >/etc/pgbouncer/pgbouncer.ini <<"EOF"
[databases]
appdb = host=127.0.0.1 port=5432 dbname=appdb

[pgbouncer]
listen_addr = 127.0.0.1
listen_port = 6432

auth_type = scram-sha-256
auth_file = /etc/pgbouncer/userlist.txt

pool_mode = transaction
max_client_conn = 200
default_pool_size = 20
reserve_pool_size = 5

server_idle_timeout = 60
query_timeout = 30
log_connections = 1
log_disconnections = 1
EOF
systemctl restart pgbouncer'
...output...

Pour MySQL, le pooling reste pertinent. Beaucoup d’apps « gèrent le pooling » en ouvrant et fermant sans arrêt des connexions, ce qui n’est pas du pooling ; c’est du cardio. Utilisez un vrai pooler côté runtime applicatif, et maintenez les connexions serveur basses.

Stockage & réalités E/S : le disque du VPS est le boss caché

Sur les petits plans VPS, votre disque est souvent le composant le plus lent et le moins prévisible. Vous pouvez être sur un SSD partagé avec crédits de burst, ou sur un « SSD » qui se comporte comme un HDD poli. Les bases de données rendent le comportement du disque visible parce qu’elles écrivent régulièrement et font des fsync.

Les réglages de durabilité sont des réglages de performance

Lorsque vous utilisez des valeurs durables par défaut (innodb_flush_log_at_trx_commit=1, commit synchrone Postgres, WAL fsync), vous choisissez explicitement d’attendre que le stockage confirme les écritures. C’est la bonne décision. Si vous la relâchez, vous échangez la cohérence contre la vitesse.

Sur un VPS 1 Go pour des charges hobby, vous pouvez accepter une durabilité moins stricte. En production, soyez prudent : vous ne découvrez votre vraie tolérance au risque qu’après avoir perdu des données. Ce n’est pas le moment pour en débattre.

Le cache du système de fichiers compte même quand vous avez un cache DB

Le page cache Linux aide toujours. Il met en cache les métadonnées, les tablespaces et les pages fréquemment accédées que la base n’a pas épinglées. Affamer l’OS de mémoire est la façon dont vous obtenez des pics de latence aléatoires même quand le cache DB est « grand ». C’est pourquoi « donner 80 % à InnoDB » n’est pas une vérité universelle — surtout pas sur 1 Go.

Swap : le vilain avec une utilité

Sur 1 Go, je préfère généralement avoir un peu de swap (même un petit fichier swap) pour absorber les pics brefs plutôt que de déclencher immédiatement l’OOM killer. Mais si vous swappez constamment, vous êtes déjà mal. Le swap est un coussin de sécurité, pas un moteur.

Mode d’emploi diagnostic rapide : trouvez le goulot en quelques minutes

C’est l’ordre qui vous empêche de partir dans une spirale de réglages d’une semaine.

Première étape : pression mémoire ou E/S ?

  1. Vérifiez swap et mémoire : si le swap monte vite ou si des OOM kill se produisent, arrêtez de tuner le SQL et plafonnez les connexions / la mémoire d’abord.
  2. Vérifiez l’attente I/O : un fort %iowait signifie que vous attendez le disque ; votre plan de requête peut être correct, mais votre disque est triste.

Deuxième étape : la base est‑elle surchargée ou l’appli ?

  1. Nombre de connexions : êtes‑vous à/près du maximum de connexions ? Si oui, vous faites de la mise en file et du thrashing.
  2. Requêtes lentes : quelques requêtes dominent‑elles ? Si oui, corrigez‑les avant de « tuner le serveur ».

Troisième étape : checkpoints/flushs ou contention de verrous ?

  1. PostgreSQL : vérifiez la fréquence des checkpoints et le comportement de l’autovacuum ; recherchez aussi les verrous bloquants.
  2. MySQL : vérifiez la pression sur le redo log, les misses du buffer pool et les waits de lock.

Quatrième étape : vérifiez le stockage et les contraintes du noyau

  1. Espace disque : un disque plein cause le chaos.
  2. CPU steal : sur des hôtes partagés, votre « CPU » peut être surtout théorique.

Tâches pratiques (avec commandes) : observer, décider, agir

Voici les vérifications que je lance sur les incidents de petits VPS. Chacune inclut ce que signifie la sortie et la décision à prendre.

Tâche 1 : Confirmer RAM, swap et pression

cr0x@server:~$ free -m
              total        used        free      shared  buff/cache   available
Mem:            987         612          54          12         320         248
Swap:          1023         310         713

Signification : Un available faible et un usage non négligeable du swap indiquent une pression mémoire. Si le swap augmente pendant les pics, vous vous dirigez vers des pics de latence.

Décision : Réduire max_connections, diminuer légèrement les caches DB, ajouter du pooling, ou augmenter la RAM. Ne commencez pas par ajouter des index.

Tâche 2 : Voir si vous êtes lié par l’I/O (iowait élevé)

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
 1  0 317440  55296  12000 310000    0    8   120   980  300  450 12  6 72 10  0
 2  1 317680  41000  11800 309500    0   64   400  2200  500  900 18  8 58 16  0

Signification : Un wa (I/O wait) dans les dizaines indique que le disque est un goulot, surtout si cela se corrèle avec la latence.

Décision : Réduire l’amplification d’écriture (réglage des checkpoints/flush), éviter de gros sorts sur disque, et envisager un stockage plus rapide ou déplacer la DB.

Tâche 3 : Identifier les plus gros consommateurs de mémoire

cr0x@server:~$ ps -eo pid,comm,rss,pmem --sort=-rss | head
 1823 postgres   98200  9.9
 1744 postgres   94120  9.5
 1602 postgres   90500  9.2
 1320 mysqld     82000  8.3
  911 node       61000  6.1

Signification : Plusieurs backends Postgres consommant ~90 Mo chacun suggèrent mémoire par connexion + travail de requête. MySQL apparaît comme un gros processus unique.

Décision : Pour Postgres : réduire les connexions et work_mem ; ajouter PgBouncer. Pour MySQL : vérifier la taille du buffer pool et les tampons par thread.

Tâche 4 : Vérifier si le noyau tue des processus (OOM)

cr0x@server:~$ dmesg -T | tail -n 20
[Mon Dec 30 10:12:14 2025] Out of memory: Killed process 1823 (postgres) total-vm:1620400kB, anon-rss:210000kB, file-rss:1200kB, shmem-rss:0kB

Signification : Vous avez touché le mur. Postgres a été tué ; vous avez probablement perdu du travail en vol, et l’appli peut retenter agressivement.

Décision : Plafonnez immédiatement les connexions, baissez les réglages mémoire, et stabilisez. Ensuite, trouvez la cause du pic (requête, déploiement, tâche planifiée).

Tâche 5 : Confirmer l’espace disque et l’état des inodes

cr0x@server:~$ df -h
Filesystem      Size  Used Avail Use% Mounted on
/dev/vda1        25G   22G  2.1G  92% /

Signification : 92 % n’est pas « plein », mais c’est suffisamment proche pour que autovacuum/fichiers temporaires/WAL vous posent problème.

Décision : Nettoyez les logs, faites des rotations, archivez les binlogs/WAL anciens, et gardez au moins quelques Go libres. Sur des disques minuscules, la marge, c’est la vie.

Tâche 6 : Mesurer rapidement la latence du stockage

cr0x@server:~$ iostat -x 1 3
Device            r/s     w/s   r_await   w_await  aqu-sz  %util
vda              8.00   35.00     6.10    28.40    1.20   92.00

Signification : Un w_await élevé et un %util élevé signifient que le disque est saturé en écritures.

Décision : Réduire les rafales d’écriture (checkpoints Postgres, flush MySQL), déplacer les jobs batch lourds hors‑pic. Si c’est constamment saturé, améliorez le stockage/plan.

Tâche 7 : PostgreSQL—vérifier le nombre et l’état des connexions

cr0x@server:~$ sudo -u postgres psql -c "select state, count(*) from pg_stat_activity group by 1 order by 2 desc;"
   state   | count
-----------+-------
 idle      |    22
 active    |     9
 idle in transaction | 6
(3 rows)

Signification : « idle in transaction » est un classique : des sessions tiennent des verrous et risquent de causer du bloat.

Décision : Corrigez la gestion des transactions dans l’appli ; configurez idle_in_transaction_session_timeout. Le pooling aide mais ne corrige pas le mauvais code.

Tâche 8 : PostgreSQL—trouver les requêtes lentes totales

cr0x@server:~$ sudo -u postgres psql -c "select query, calls, total_exec_time::int as total_ms, mean_exec_time::int as mean_ms from pg_stat_statements order by total_exec_time desc limit 5;"
                               query                                | calls | total_ms | mean_ms
--------------------------------------------------------------------+-------+----------+---------
 select * from events where user_id = $1 order by created_at desc    |  1200 |    98000 |      81
 update accounts set last_seen = now() where id = $1                 |  4500 |    42000 |       9
(2 rows)

Signification : La première requête est candidate pour un index comme (user_id, created_at desc) et peut‑être pour ne sélectionner que moins de colonnes.

Décision : Corrigez les pires coupables avant de toucher aux réglages globaux. Un tuning 1 Go ne sauvera pas un scan de table complet à chaque requête.

Tâche 9 : PostgreSQL—vérifier la pression des checkpoints

cr0x@server:~$ sudo -u postgres psql -c "select checkpoints_timed, checkpoints_req, buffers_checkpoint, buffers_backend from pg_stat_bgwriter;"
 checkpoints_timed | checkpoints_req | buffers_checkpoint | buffers_backend
-------------------+-----------------+--------------------+----------------
                18 |              42 |            8123456 |         456789
(1 row)

Signification : Beaucoup de checkpoints demandés suggèrent que le WAL se remplit vite (rafales d’écriture), ce qui peut causer des pics d’I/O.

Décision : Augmentez max_wal_size modestement (si le disque le permet), ajustez les checkpoints, et réduisez les écritures en rafales. Ne le mettez pas énorme sur un petit disque.

Tâche 10 : PostgreSQL—vérifier si autovacuum suit

cr0x@server:~$ sudo -u postgres psql -c "select relname, n_dead_tup, last_autovacuum from pg_stat_user_tables order by n_dead_tup desc limit 5;"
 relname  | n_dead_tup |        last_autovacuum
----------+------------+-------------------------------
 events   |     182345 | 2025-12-30 09:50:12.12345+00
 logs     |      93440 | 
(2 rows)

Signification : Beaucoup de dead tuples et l’absence de last_autovacuum sur une table chaude suggèrent que le vacuum est à la traîne ou que les seuils sont mal réglés.

Décision : Ajustez les facteurs d’autovacuum par table, ajoutez des index avec précaution, et assurez‑vous que le vacuum n’est pas bloqué par des transactions longues.

Tâche 11 : MySQL—vérifier les variables mémoire pertinentes

cr0x@server:~$ sudo mysql -e "show variables where Variable_name in ('max_connections','innodb_buffer_pool_size','tmp_table_size','max_heap_table_size','sort_buffer_size','join_buffer_size');"
+-------------------------+-----------+
| Variable_name           | Value     |
+-------------------------+-----------+
| innodb_buffer_pool_size | 402653184 |
| join_buffer_size        | 262144    |
| max_connections         | 60        |
| max_heap_table_size     | 33554432  |
| sort_buffer_size        | 1048576   |
| tmp_table_size          | 33554432  |
+-------------------------+-----------+

Signification : Confirme que les plafonds sont appliqués ; les tailles semblent conservatrices.

Décision : Si vous voyez des tampons par thread énormes ou un max_connections dans les centaines, corrigez cela avant de chasser des plans de requête.

Tâche 12 : MySQL—vérifier le hit rate et les lectures du buffer pool

cr0x@server:~$ sudo mysql -e "show global status like 'Innodb_buffer_pool_read%';"
+---------------------------------------+-----------+
| Variable_name                         | Value     |
+---------------------------------------+-----------+
| Innodb_buffer_pool_read_requests      | 184003211 |
| Innodb_buffer_pool_reads              | 1200345   |
+---------------------------------------+-----------+

Signification : Certaines lectures disque sont normales. Si Innodb_buffer_pool_reads augmente rapidement par rapport aux requêtes, le working set ne tient pas et vous êtes lié par l’I/O.

Décision : Si le disque est assez rapide, peut être acceptable. Sinon, augmentez légèrement le buffer pool (sans affamer l’OS), ou réduisez dataset/requêtes.

Tâche 13 : MySQL—vérifier les tables temporaires écrites sur disque

cr0x@server:~$ sudo mysql -e "show global status like 'Created_tmp%tables';"
+-------------------------+--------+
| Variable_name           | Value  |
+-------------------------+--------+
| Created_tmp_disk_tables | 18420  |
| Created_tmp_tables      | 40210  |
+-------------------------+--------+

Signification : Une grande fraction de tmp tables sur disque indique des spills de sorts/joins, ce qui est pénalisant sur un stockage lent.

Décision : Ajoutez des index, réécrivez les requêtes, ou augmentez légèrement les limites des tables temporaires si la mémoire le permet. Sur 1 Go, corriger la requête vaut mieux que forcer la mémoire.

Tâche 14 : Confirmer l’utilisation des connexions (MySQL)

cr0x@server:~$ sudo mysql -e "show global status like 'Threads_connected'; show global status like 'Max_used_connections';"
+-------------------+-------+
| Variable_name     | Value |
+-------------------+-------+
| Threads_connected | 48    |
+-------------------+-------+
+----------------------+-------+
| Variable_name        | Value |
+----------------------+-------+
| Max_used_connections | 59    |
+----------------------+-------+

Signification : Vous êtes proche du plafond. C’est soit une mise en file saine (si la latence est correcte), soit un goulot (si les requêtes expirent).

Décision : Si vous avez des timeouts, ajoutez du pooling et réduisez la concurrence côté appli. Augmenter max_connections sur 1 Go augmente généralement simplement l’audience du désastre.

Tâche 15 : Vérifier le temps de steal CPU (réalité VPS partagée)

cr0x@server:~$ mpstat 1 3
Linux 6.1.0 (server) 	12/30/2025 	_x86_64_	(1 CPU)

12:10:01 AM  %usr %nice %sys %iowait %irq %soft %steal %guest %gnice %idle
12:10:02 AM  18.0  0.0  8.0    10.0  0.0   1.0   12.0   0.0    0.0  51.0

Signification : %steal à 12 % signifie que l’hyperviseur vous enlève du CPU. Votre tuning ne peut pas lutter contre votre hébergeur.

Décision : Envisagez un meilleur plan/fournisseur, réduisez la charge, ou déplacez la DB ailleurs. Ne perdez pas des jours à micro‑tuner des requêtes pendant que l’hôte est survendu.

Tâche 16 : Confirmer la latence depuis la base elle‑même

cr0x@server:~$ sudo -u postgres psql -c "select now(), pg_sleep(0.1), now();"
              now              | pg_sleep |              now
-------------------------------+----------+-------------------------------
 2025-12-30 00:10:10.0101+00   |          | 2025-12-30 00:10:10.1109+00
(1 row)

Signification : Si cet appel simple est lent ou se bloque, le problème est au niveau système (I/O, CPU steal, thrash mémoire), pas au niveau requête.

Décision : Cessez d’accuser l’ORM et regardez l’hôte.

Trois micro‑histoires d’entreprise tirées du terrain

1) Incident causé par une hypothèse erronée : « C’est seulement 1 Go, mais c’est juste une machine de dev »

Ils avaient un petit service interne sur un VPS 1 Go. « Interne » signifiait « seulement utilisé par des ingénieurs », ce qui veut dire « personne ne l’a mesuré et tout le monde a supposé que ça allait ». Ils ont choisi PostgreSQL, mis max_connections à 200 parce que l’appli avait parfois des rafales, et mis work_mem à 64 Mo parce qu’un article disait que trier en mémoire est plus rapide.

Ça a fonctionné pendant des semaines. Puis un nouveau tableau de bord a été déployé qui exécutait quelques requêtes d’agrégation par chargement de page. Chaque requête utilisait plusieurs sorts et hashes. Les ingénieurs ont ouvert le dashboard en parallèle pendant un incident. La base ne s’est pas dégradée en douceur ; elle est tombée d’un coup. Le noyau a commencé à tuer des backends. Le dashboard retentait. Les requêtes retentées ont créé plus de backends. Vous imaginez la suite.

L’hypothèse était : « 64 Mo, c’est petit ; le serveur a 1 Go. » La réalité : work_mem est par opération, par connexion, et les requêtes peuvent en utiliser plusieurs à la fois. Multipliez par la concurrence et vous avez un générateur d’OOM.

La correction n’a pas été héroïque. Ils ont réduit max_connections à 40, baissé work_mem à 4 Mo, mis PgBouncer en place, et déplacé le dashboard pour exécuter les requêtes lourdes de façon asynchrone avec des résultats mis en cache. Le service « interne » est redevenu ennuyeux, ce qui est le plus grand compliment pour une base de données.

2) Optimisation qui a mal tourné : « Rendons les checkpoints inexistants »

Une autre équipe exécutait Postgres sur un petit VPS et se plaignait de pics de latence périodiques. Ils ont vu des logs de checkpoint et ont conclu que les checkpoints étaient l’ennemi. Leur solution a été d’augmenter max_wal_size de façon agressive et d’étirer checkpoint_timeout pour que les checkpoints arrivent moins souvent.

Pendant un temps, cela a paru mieux. Moins de pics. Tout le monde a déclaré victoire et a tourné la page. Puis ils ont eu un reboot non planifié (maintenance du fournisseur). La récupération a pris beaucoup plus de temps que prévu, et le service a manqué sa fenêtre SLO. Pas de perte de données, mais le post‑mortem était gênant parce que « l’optimisation » était le seul changement notable récent.

Ils ont appris la leçon : moins de checkpoints peut signifier plus de WAL à rejouer, et sur un stockage lent cela peut être pénible. Sur les petits disques VPS, un WAL plus grand occupe aussi l’espace disque, augmentant la chance d’atteindre le plein disque au pire moment.

La correction pragmatique : remettre des valeurs de checkpoints conservatrices, puis résoudre la cause racine — rafales d’écritures provenant de jobs batch. Ils ont limité ces jobs, ajusté les paramètres de coût autovacuum pour étaler les I/O, et accepté que certains checkpoints soient normaux. Une base qui ne checkpoint jamais n’est pas « optimisée » ; elle procrastine bruyamment.

3) Pratique ennuyeuse mais correcte qui a sauvé la mise : plafonds de connexions + logs de requêtes lentes

Un petit SaaS exploitait MySQL sur des instances 1 Go pour certains shards clients. Rien de spécial, juste de l’OLTP. Ils avaient une règle stricte : chaque shard avait un max_connections dur, des tampons par thread conservateurs, et le slow query log activé avec un seuil bas. Ils faisaient aussi une revue hebdomadaire des extraits de requêtes lentes pour détecter les régressions.

Un vendredi, une nouvelle fonctionnalité a introduit une requête qui passait en staging mais était pathologique pour un shard client avec un dataset biaisé. La requête faisait un join sans index approprié et écrivait des tables temporaires sur disque. Sur un VPS 1 Go avec I/O moyen, c’est comme transformer une seule requête en déni de service.

La différence : le shard n’a pas dégénéré. Les plafonds de connexions ont évité un effet de masse explosif en mémoire. Les logs de requêtes lentes ont rendu le coupable évident en quelques minutes. Ils ont rollbacké la fonctionnalité pour ce shard, ajouté l’index correct, et redéployé après vérification.

Pas de tuning héroïque. Pas de nuit blanche « peut‑être que Linux est cassé ». Juste des garde‑fous et une trace. L’incident était ennuyeux, et ennuyeux, c’est exactement ce qu’on veut.

Erreurs courantes : symptôme → cause racine → correctif

1) Symptom : timeouts soudains, pics de load, swap qui grimpe

Cause racine : Trop de connexions + mémoire par connexion (backends Postgres, tampons thread MySQL) provoquant pression mémoire et thrash du swap.

Fix : Plafonner les connexions serveur ; ajouter du pooling ; baisser work_mem (Postgres) et les tampons par-thread (MySQL). Envisager un petit fichier swap si vous n’en avez pas, mais considérez‑le comme un filet de sécurité, pas une capacité.

2) Symptom : pics de latence périodiques toutes les quelques minutes

Cause racine : Rafales de checkpoints/flushs et saturation d’écriture du stockage.

Fix : Pour Postgres, caler le pacing des checkpoints (checkpoint_completion_target) et permettre une taille WAL modérée ; pour MySQL, revoir le flush et les réglages redo. Réduire les jobs écrivant en rafale et éviter de grosses transactions.

3) Symptom : utilisation disque PostgreSQL qui grossit alors que les données « ne changent pas beaucoup »

Cause racine : Bloat dû au MVCC + retard de vacuum, souvent aggravé par des transactions longues ou un autovacuum désactivé.

Fix : Laisser autovacuum activé ; traquer les transactions longues ; régler les seuils autovacuum par table ; envisager des VACUUM (ANALYZE) ou REINDEX en fenêtre de maintenance.

4) Symptom : MySQL paraît rapide, puis bloque soudainement sur les écritures

Cause racine : Pression sur le redo log et latence de fsync ; peut‑être une petite capacité redo combinée à des rafales d’écriture.

Fix : Garder une capacité redo modérée, éviter de très grosses transactions, et valider votre stockage. Si le disque VPS est erratique, aucun paramètre ne le rendra honnête.

5) Symptom : connexions « idle in transaction » s’accumulent (Postgres)

Cause racine : L’appli ouvre une transaction et oublie de commit/rollback, tenant des verrous et empêchant le nettoyage du vacuum.

Fix : Corriger la portée des transactions dans l’appli ; configurer idle_in_transaction_session_timeout ; surveiller pg_stat_activity et tuer les contrevenants si nécessaire.

6) Symptom : CPU élevé mais débit faible

Cause racine : Mauvais plans de requête, index manquants, ou sorts/hashes coûteux qui débordent sur disque ; parfois du CPU steal.

Fix : Utiliser EXPLAIN (ANALYZE, BUFFERS) (Postgres) ou EXPLAIN (MySQL), corriger les index, et vérifier le %steal. Si le steal est élevé, vos « optimisations CPU » sont surtout du théâtre.

7) Symptom : tout ralentit après « augmentation des caches »

Cause racine : Affamer l’OS et le cache de fichiers ; swap ; tâches de fond en concurrence pour la mémoire.

Fix : Réduire légèrement les tailles de cache DB, garder de la marge mémoire, et mesurer à nouveau. Sur 1 Go, laisser 200 Mo libres peut surpasser le fait d’extraire chaque Mo pour la DB.

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

Étape par étape : choisir MySQL vs PostgreSQL pour 1 Go

  1. Comptez votre concurrence : si vous avez besoin de nombreux clients concurrents, prévoyez du pooling quel que soit le SGBD.
  2. Classifiez les requêtes : majoritairement OLTP indexées simples vs mix OLTP + reporting. Les charges mixtes favorisent Postgres pour ses sémantiques et outils, mais seulement si vous contrôlez les connexions.
  3. Décidez la posture de durabilité : si vous ne pouvez pas accepter de perte de données, ne « désactivez » pas les fsync.
  4. Vérifiez la qualité du stockage : si le disque est lent, tunez pour moins d’écritures et des rafales plus faibles ; envisagez de déplacer la DB hors du VPS.
  5. Choisissez les valeurs prudentes de ce guide et ne changez qu’une variable à la fois avec des mesures.

Étape par étape : durcir un hôte base de données 1 Go

  1. Installer du monitoring (au minimum : disque, mémoire, swap, load, iowait).
  2. Créer un fichier swap si aucun n’existe (petit, par ex. 1 Go) et régler vm.swappiness raisonnablement selon la distro.
  3. Plafonner les connexions de la base (max_connections), puis appliquer du pooling dans l’appli.
  4. Activer les logs de requêtes lentes (log_min_duration_statement ou slow log MySQL) avec un seuil qui attrape tôt la douleur (par ex. 500 ms).
  5. Vérifier qu’autovacuum est activé (Postgres) et non bloqué ; vérifier qu’InnoDB est utilisé (MySQL) et que le buffer pool n’est pas absurde.
  6. Garder une marge d’espace disque et activer la rotation des logs. Un disque plein transforme un « problème de perf » en incident.
  7. Faire un test de charge qui correspond à la réalité, pas à l’espoir.

Étape par étape : tuner sans cargo‑cult

  1. Obtenir une baseline : latence, débit, CPU, iowait, mémoire.
  2. Corriger la pire requête d’abord (top total time), pas la requête « la plus discutée ».
  3. Ensuite seulement envisager le dimensionnement des caches : buffer pool / shared buffers.
  4. Recontrôler après chaque changement. Si vous ne pouvez pas mesurer d’amélioration, revenez en arrière et passez à autre chose.

FAQ

1) Puis‑je exécuter PostgreSQL sur 1 Go sans PgBouncer ?

Vous le pouvez, mais vous misez la stabilité sur le fait que votre appli n’ouvrira jamais trop de connexions. Si c’est un service unique et petit avec pooling strict côté runtime, OK. Sinon, utilisez PgBouncer et dormez tranquille.

2) Quel est un shared_buffers sûr sur 1 Go ?

Généralement 128–256 Mo. Si vous mettez 512 Mo sur une machine 1 Go et autorisez beaucoup de connexions, vous construisez une compression mémoire. Laissez de la marge pour l’OS et pour les processus backend.

3) Quel est un innodb_buffer_pool_size sûr sur 1 Go ?

Souvent 256–512 Mo. Si le VPS héberge aussi l’appli, penchez vers 256–384 Mo. Le reste de la mémoire n’est pas « gaspillé » ; il évite le swapping et aide le cache OS.

4) Dois‑je désactiver fsync ou le commit synchrone pour aller plus vite ?

Seulement si vous acceptez complètement de perdre des transactions récentes en cas de perte de courant ou crash d’hôte. Sur un VPS, vous ne contrôlez pas les modes de défaillance. En production, gardez la durabilité et optimisez ailleurs.

5) Le swap est‑il bon ou mauvais pour les bases sur petites machines ?

Un peu de swap est utile comme amortisseur. Le swap constant est fatal. Si vous voyez une croissance stable du swap en charge normale, vous avez un problème de budget mémoire, pas de configuration swap.

6) Quelle base est « plus légère » en mémoire ?

Aucune par défaut. MySQL tend à être dominé par un gros processus (buffer pool) plus des tampons par connexion ; Postgres tend à augmenter la mémoire avec les connexions. Avec des plafonds sensés et du pooling, les deux tiennent. Sans plafonds, les deux peuvent vous ruiner la journée.

7) Quel est le réglage le plus important sur 1 Go ?

max_connections. Il contrôle la multiplication mémoire dans le pire cas et votre profil de contention. Si vous vous trompez là, vos autres réglages deviennent décoratifs.

8) Comment savoir si j’ai besoin de plus de RAM ou d’un meilleur disque ?

Si vous swappez ou subissez des OOM kills, il vous faut plus de RAM ou réduire la concurrence. Si %iowait est élevé et que w_await disque est important, vous avez besoin d’un meilleur disque ou de réduire les écritures. Souvent il faut les deux, mais l’un crie généralement plus fort.

9) Puis‑je « simplement ajouter des index » pour corriger la perf sur 1 Go ?

Les index aident les lectures et peuvent pénaliser les écritures. Sur 1 Go avec un stockage lent, trop d’index augmente l’amplification d’écriture et le travail de vacuum. Ajoutez les bons index pour les requêtes principales, pas chaque index que votre ORM suggère.

10) Dois‑je exécuter l’appli et la DB sur le même VPS 1 Go ?

Seulement pour des déploiements très petits. Co‑héberger augmente la contention et rend les incidents plus difficiles à diagnostiquer. Si vous devez le faire, réduisez les caches DB et appliquez agressivement le pooling des connexions.

Prochaines étapes que vous pouvez faire aujourd’hui

  1. Choisissez un plafond de connexions (Postgres 40, MySQL 60 est un bon point de départ) et imposez du pooling.
  2. Appliquez la configuration conservatrice pour la base choisie, redémarrez, et vérifiez que les paramètres ont bien pris effet.
  3. Activez la visibilité des requêtes lentes (seuil 500 ms) et collectez des données pendant une journée.
  4. Exécutez le mode d’emploi diagnostic rapide la prochaine fois qu’un pic de latence survient : mémoire → iowait → connexions → requêtes principales → checkpoints/flushs.
  5. Décidez d’un déclencheur d’upgrade : si vous êtes régulièrement proche des plafonds, en swap ou en saturation disque, augmenter à 2 Go ou améliorer le stockage apportera plus de stabilité que la finesse.

Si vous voulez une règle finale pour les bases sur VPS 1 Go : borne votre pire cas. C’est à cela que servent les réglages. Le reste, c’est juste se quereller avec la physique.

← Précédent
Volumes Docker : Bind mounts vs volumes nommés — qu’est-ce qui survit mieux aux migrations
Suivant →
Cases à cocher et boutons radio personnalisés en pur CSS : modèles accessibles qui ne trompent pas

Laisser un commentaire