Vous êtes sur un VPS. Vous voulez « une base de données ». Pas un projet du week-end, pas une ferme à yaks. Quelque chose qui ne vous réveillera pas à 03:00 parce qu’un seul fichier est coincé, ou parce que votre appli reçoit soudainement du vrai trafic et que votre choix « simple » se transforme en une migration douloureuse.
La façon la plus rapide de choisir entre PostgreSQL et SQLite est d’arrêter de débattre des fonctionnalités et de poser une seule question brutale : où se situe votre frontière de concurrence et de défaillance ? Si elle est à l’intérieur d’un seul processus, SQLite est un scalpel. Si elle s’étend sur de nombreux processus, utilisateurs, jobs et connexions, PostgreSQL est votre clé robuste et éprouvée.
La décision en une minute
Si vous ne lisez que cette section, vous ferez quand même un choix respectable.
Choisissez SQLite si tout cela est vrai
- Votre appli est principalement à un seul rédacteur et trafic modeste (pensez : un processus web ou un worker de file qui écrit, pas une nuée).
- Vous pouvez vivre avec la sémantique de verrouillage basée sur le fichier et l’occasionnel « base de données verrouillée » si vous en faites un mauvais usage.
- Vous voulez zéro overhead d’exploitation : pas de démon, pas de vacuum en arrière-plan à tuner, pas de drame de connection pooling.
- Votre domaine de défaillance est « ce VPS et ce disque » et cela vous va.
- Vous voulez une parité dev locale facile : livrer un seul fichier DB est un atout.
Choisissez PostgreSQL si l’un de ces points est vrai
- Vous avez plusieurs rédacteurs, plusieurs instances d’appli, cron jobs, workers, requêtes analytiques, outils d’administration… tout ce qui se comporte comme une petite foule.
- Vous avez besoin d’une forte concurrence sans transformer votre appli en coordinateur de verrous.
- Vous tenez à l’isolation, la durabilité et la récupérabilité face à des modes de défaillance réels et désordonnés.
- Vous voulez des migrations en ligne, des index plus riches et des plans de requête qui vont au-delà du « mignon ».
- Vous prévoyez une croissance et préférez scaler en ajoutant CPU/RAM maintenant et des réplicas plus tard, plutôt que faire une migration risquée plus tard.
Règle empirique : si votre base de données doit médier l’impatience humaine (trafic web) et l’impatience machine (jobs), PostgreSQL est l’adulte dans la pièce.
Blague #1 : SQLite, c’est comme un vélo : rapide, élégant et parfait jusqu’à ce que vous essayiez de déplacer un canapé avec.
Un modèle mental qui évite les regrets
La plupart des débats « Postgres vs SQLite » meurent parce que les gens comparent la syntaxe SQL ou des checklists de fonctionnalités. Le vrai choix porte sur la forme opérationnelle : qui parle à la base, à quelle fréquence, et ce qui se passe quand ça casse.
SQLite : une bibliothèque avec un fichier, pas un serveur
SQLite s’exécute en processus. Il n’y a pas de démon serveur acceptant des connexions. Votre appli lie une bibliothèque ; la « base de données » est un fichier (plus des fichiers journaux/WAL optionnels). Cela signifie :
- La latence peut être excellente parce qu’il n’y a pas de saut réseau. Les appels sont des appels de fonction.
- La concurrence est limitée par le verrouillage de fichier. Les lectures vont bien. Les écritures nécessitent de la coordination ; WAL améliore cela mais ne transforme pas tout en foire d’empoigne.
- La durabilité dépend des sémantiques du système de fichiers, des options de montage et de votre usage des paramètres synchronous. Ce n’est pas « dangereux », c’est « vous gérez les zones tranchantes ».
- Les sauvegardes sont des sauvegardes de fichier, ce qui peut être merveilleusement simple—jusqu’à ce que vous en fassiez une au mauvais moment sans utiliser les API de sauvegarde de SQLite.
PostgreSQL : un serveur avec des processus, de la mémoire et des convictions
PostgreSQL fonctionne comme un serveur de base de données avec ses propres processus, caches, write-ahead log (WAL), vacuum en arrière-plan et des sémantiques transactionnelles bien définies. Cela signifie :
- Haute concurrence grâce à MVCC (contrôle de concurrence multi-version) : les lecteurs ne bloquent pas les rédacteurs comme le feraient des verrous de fichiers.
- La durabilité et la récupération après crash sont au cœur du système. Il faut toujours configurer et tester, mais le système est conçu pour les mauvais jours.
- Il y a un overhead opérationnel : mises à jour, sauvegardes, monitoring, vacuum et gestion des connexions.
- Les voies d’évolution sont plus claires : réplication, réplicas de lecture, partitionnement, poolers de connexion et un écosystème mature.
La question de la frontière
Demandez : « La base de données est-elle une frontière de service partagée ? » Si oui, PostgreSQL. Si non, SQLite peut être une base légitime en production. Ne sous-estimez pas la fréquence à laquelle un « non » devient un « oui » quand vous ajoutez un worker, puis une seconde instance d’appli, puis un tableau d’administration qui exécute des requêtes lourdes.
Faits intéressants et un peu d’histoire
Un peu de contexte aide, car les choix de conception n’étaient pas arbitraires. Ce sont des cicatrices d’usage réel.
- SQLite est né en 2000 comme base embarquée pour éviter l’overhead des bases client/serveur pour un projet logiciel précis ; il est devenu le moteur « petit SQL » par défaut dans le monde.
- PostgreSQL remonte aux années 1980 (projet POSTGRES à UC Berkeley), et son ADN se voit : extensibilité, exactitude et obsession académique pour le comportement transactionnel.
- SQLite est probablement le moteur de base de données le plus déployé car il est embarqué dans les téléphones, navigateurs, systèmes d’exploitation et d’innombrables applications comme bibliothèque.
- PostgreSQL a popularisé l’extensibilité riche via types personnalisés, opérateurs et extensions ; c’est pourquoi il est la plateforme « SQL plus » par défaut dans beaucoup de stacks modernes.
- Le mode WAL de SQLite a été ajouté plus tard pour réduire le blocage des rédacteurs et améliorer la concurrence ; cela a changé ce pour quoi SQLite est adapté en production.
- L’MVCC de PostgreSQL signifie que d’anciennes versions de lignes traînent jusqu’à ce que vacuum les nettoie ; c’est une fonctionnalité de performance et une corvée opérationnelle.
- SQLite est célèbre pour la portabilité stricte de son fichier de base entre architectures et versions, mais il dépend toujours du comportement du système de fichiers pour la durabilité.
- Le WAL de PostgreSQL s’appelle aussi WAL (même acronyme, implémentation différente), et il est la base pour la réplication et la récupération PITR.
- « database is locked » dans SQLite n’est pas un bug ; c’est le résultat explicite du modèle de verrouillage. Le bug est votre hypothèse qu’il se comporte comme une base serveur.
Réalités du VPS : disques, mémoire et voisins
Un VPS n’est pas un laptop et pas une base gérée. C’est une petite tranche d’une machine plus grande avec IO partagé et parfois des voisins imprévisibles. Votre choix de base doit en tenir compte.
Le benchmark IO est le premier mensonge qu’on vous raconte
Sur un VPS, votre « SSD » peut être rapide, ou il peut être « rapide quand les voisins dorment ». SQLite et PostgreSQL tiennent compte du comportement de fsync, mais ils en font l’expérience différemment :
- SQLite écrit dans un seul fichier de base (plus journaux/WAL). Les écritures aléatoires peuvent être pénalisantes si votre charge génère beaucoup d’écritures.
- PostgreSQL écrit dans plusieurs fichiers : fichiers de données et segments WAL. Les écritures WAL sont plus séquentielles et peuvent être plus indulgentes pour des disques réels, mais vous avez alors des processus en arrière-plan et des checkpoints.
La mémoire n’est pas que « cache » ; c’est une politique
SQLite s’appuie fortement sur le cache de pages de l’OS. C’est acceptable—Linux est bon pour le caching. PostgreSQL a ses propres shared buffers en plus du cache OS. Si vous le dimensionnez mal sur un petit VPS, vous pouvez vous retrouver avec un double-cache et priver le reste du système.
Le modèle de processus compte quand la RAM est faible
SQLite vit dans votre processus applicatif. PostgreSQL utilise plusieurs processus et de la mémoire par connexion. Sur un VPS de 1 Go, une pile de connexions inactives peut être un bug de performance, pas un détail mineur. Si vous exécutez Postgres sur du petit fer, vous apprendrez à aimer le pooling de connexions.
Rayon d’explosion opérationnel
Le rayon d’explosion de SQLite est souvent « ce fichier ». Celui de PostgreSQL est « ce cluster », mais avec de meilleurs outils pour isoler et récupérer. SQLite peut être récupéré en copiant un fichier—sauf si vous le copiez au mauvais moment. PostgreSQL peut être récupéré en rejouant le WAL—sauf si vous n’avez jamais testé vos sauvegardes. Choisissez votre poison ; puis atténuez-le.
Tâches pratiques : commandes, sorties, décisions (12+)
Voici des tâches que vous pouvez lancer sur un VPS aujourd’hui. Chacune donne un signal, pas une impression. Le but est de décider sur la base de preuves : capacité IO, besoins de concurrence et risques de défaillance.
Task 1: Check CPU and memory pressure (are you even allowed to run Postgres?)
cr0x@server:~$ lscpu | egrep 'Model name|CPU\(s\)'
CPU(s): 2
Model name: Intel(R) Xeon(R) CPU E5-2690 v4 @ 2.60GHz
cr0x@server:~$ free -h
total used free shared buff/cache available
Mem: 1.0Gi 220Mi 180Mi 12Mi 620Mi 690Mi
Swap: 1.0Gi 0B 1.0Gi
Ce que cela signifie : Sur 1 Go de RAM, Postgres est possible mais il faut de la discipline (pooler de connexions, tuning mémoire). SQLite paraîtra sans effort.
Décision : Si vous ne pouvez pas allouer quelques centaines de Mo pour Postgres plus de la marge pour votre appli, préférez SQLite ou augmentez le VPS.
Task 2: Identify your storage type and mount options (durability lives here)
cr0x@server:~$ findmnt -no SOURCE,FSTYPE,OPTIONS /
/dev/vda1 ext4 rw,relatime,errors=remount-ro
Ce que cela signifie : ext4 avec relatime est normal. Si vous voyez des options étranges comme data=writeback ou un FS réseau exotique, vous devez traiter les affirmations de durabilité de SQLite avec suspicion et tuner PostgreSQL aussi.
Décision : Si vous êtes sur un stockage réseau ou bizarre, Postgres avec WAL+fsync testé est généralement plus sûr que « copier le fichier de base ».
Task 3: Quick disk latency check (your future “db is slow” ticket)
cr0x@server:~$ iostat -xz 1 3
Linux 6.2.0 (server) 12/30/2025 _x86_64_ (2 CPU)
avg-cpu: %user %nice %system %iowait %steal %idle
3.10 0.00 1.20 0.40 0.10 95.20
Device r/s w/s rkB/s wkB/s await svctm %util
vda 5.00 8.00 80.0 210.0 2.10 0.40 0.52
Ce que cela signifie : await en chiffres faibles est correct. Si vous voyez des pics de 20–100ms, SQLite et Postgres souffriront, mais SQLite le manifestera comme des blocages à l’intérieur des threads appli.
Décision : Un IO wait élevé plaide pour Postgres avec tuning de checkpoint et possiblement migrer vers un stockage meilleur ; cela plaide aussi pour réduire l’amplification d’écritures de toute façon.
Task 4: Measure filesystem sync cost (SQLite and Postgres both pay this bill)
cr0x@server:~$ sudo dd if=/dev/zero of=/var/tmp/fsync.test bs=4k count=25000 conv=fdatasync status=progress
102400000 bytes (102 MB, 98 MiB) copied, 1.52 s, 67.4 MB/s
25000+0 records in
25000+0 records out
102400000 bytes (102 MB, 98 MiB) copied, 1.52 s, 67.3 MB/s
Ce que cela signifie : C’est grossier, mais ça approximative « à quel point forcer la durabilité est coûteux ». Si c’est glacial, vos réglages « sûrs » feront mal.
Décision : Si le fsync forcé est cher, SQLite a besoin de WAL + paramètres synchronous sensés ; Postgres a besoin de checkpoints ajustés et de ne pas abuser de synchronous_commit pour des écritures non critiques.
Task 5: Verify open file limits (Postgres will care more)
cr0x@server:~$ ulimit -n
1024
Ce que cela signifie : 1024 est serré pour Postgres sous charge avec beaucoup de connexions et de fichiers. SQLite s’en soucie moins, mais votre appli peut en souffrir.
Décision : Si vous choisissez Postgres, augmentez les limites via systemd ou limits.conf ; si vous ne pouvez pas, gardez les connexions basses et utilisez un pooler.
Task 6: Inspect live connection count (if it’s already a crowd, SQLite will get spicy)
cr0x@server:~$ sudo ss -tanp | awk '$4 ~ /:5432$/ {c++} END {print c+0}'
0
Ce que cela signifie : Pas de Postgres pour l’instant, mais le motif compte : combien de clients DB concurrents existeront ?
Décision : Si vous attendez des dizaines/centaines de connexions concurrentes, Postgres plus un pooler gagne. SQLite n’a pas de « connexions » au même sens ; il y a des « threads et processus se battant pour un fichier ».
Task 7: Create a SQLite database with WAL and inspect pragmas (make it less fragile)
cr0x@server:~$ sqlite3 /var/lib/myapp/app.db 'PRAGMA journal_mode=WAL; PRAGMA synchronous=NORMAL; PRAGMA wal_autocheckpoint=1000;'
wal
Ce que cela signifie : Mode WAL activé ; synchronous NORMAL est un compromis courant (suffisamment durable pour beaucoup d’applis, moins d’IO que FULL).
Décision : Si vous choisissez SQLite, soyez explicite sur les pragmas. Les réglages par défaut ne sont pas une « politique de production », ce sont des « valeurs génériques de bibliothèque ».
Task 8: Simulate concurrent SQLite writes (spot the lock wall early)
cr0x@server:~$ for i in $(seq 1 20); do (sqlite3 /var/lib/myapp/app.db "BEGIN IMMEDIATE; CREATE TABLE IF NOT EXISTS t(x); INSERT INTO t VALUES($i); COMMIT;" >/dev/null 2>&1 &); done; wait; echo done
done
Ce que cela signifie : C’est un test approximatif. Si vous relancez avec plus de contention et commencez à voir « database is locked » sur stderr, c’est votre sirène d’avertissement.
Décision : Si votre charge réelle ressemble à ça (beaucoup de rédacteurs), arrêtez de romanticiser SQLite et utilisez Postgres.
Task 9: Install Postgres and confirm the service health
cr0x@server:~$ sudo apt-get update -qq
...output...
cr0x@server:~$ sudo apt-get install -y postgresql
...output...
cr0x@server:~$ sudo systemctl status postgresql --no-pager
● postgresql.service - PostgreSQL RDBMS
Loaded: loaded (/lib/systemd/system/postgresql.service; enabled)
Active: active (exited)
Ce que cela signifie : Sur Debian/Ubuntu, le service wrapper peut afficher « active (exited) » tandis que les unités de cluster tournent. Ne paniquez pas ; vérifiez le cluster.
Décision : Si vous ne parvenez pas à garder un service sain sur votre VPS (permissions, disque plein, pression mémoire), SQLite peut être le choix plus sain jusqu’à stabilisation de l’hôte.
Task 10: Check Postgres cluster readiness
cr0x@server:~$ pg_lsclusters
Ver Cluster Port Status Owner Data directory Log file
16 main 5432 online postgres /var/lib/postgresql/16/main /var/log/postgresql/postgresql-16-main.log
Ce que cela signifie : C’est en ligne. Vous avez un répertoire de données et un chemin de log—deux choses que vous apprendrez à respecter.
Décision : Si Postgres démarre proprement et reste en ligne sous votre appli, c’est un signal fort que vous pouvez assumer l’ops.
Task 11: Inspect Postgres durability and checkpoint settings (don’t fly blind)
cr0x@server:~$ sudo -u postgres psql -c "SHOW synchronous_commit; SHOW fsync; SHOW full_page_writes; SHOW checkpoint_timeout; SHOW max_wal_size;"
synchronous_commit
-------------------
on
(1 row)
fsync
-------
on
(1 row)
full_page_writes
------------------
on
(1 row)
checkpoint_timeout
--------------------
5min
(1 row)
max_wal_size
--------------
1GB
(1 row)
Ce que cela signifie : Les valeurs par défaut sont conservatrices. Elles visent la sécurité sur du matériel générique, pas nécessairement votre VPS spécifique.
Décision : Si vous avez besoin d’un débit d’écriture élevé, vous pouvez tuner les checkpoints et la taille WAL. Si vous voulez une sécurité maximale, laissez ces valeurs conservatrices et investissez dans les sauvegardes et tests.
Task 12: Spot vacuum pressure (Postgres’s “housekeeping tax”)
cr0x@server:~$ sudo -u postgres psql -c "SELECT relname, n_dead_tup FROM pg_stat_user_tables ORDER BY n_dead_tup DESC LIMIT 5;"
relname | n_dead_tup
---------+------------
(0 rows)
Ce que cela signifie : Pas encore de tables utilisateurs. Plus tard, cela montre si des tuples morts s’accumulent. Des tas signifient bloat, requêtes plus lentes et finalement des problèmes de pagination.
Décision : Si vous choisissez Postgres, surveillez vacuum/bloat. Si vous ne pouvez pas vous engager là-dedans, la simplicité de SQLite devient attractive—à condition que la concurrence convienne.
Task 13: Identify slow queries in Postgres (decide if you need indexes or a different DB)
cr0x@server:~$ sudo -u postgres psql -c "SHOW shared_preload_libraries;"
shared_preload_libraries
--------------------------
(1 row)
Ce que cela signifie : Si pg_stat_statements n’est pas activé, il vous manque une lentille clé sur le comportement des requêtes.
Décision : Si vous exécutez quelque chose au-delà d’un trafic jouet, activez les stats de requêtes et traitez-les comme de la télémétrie de production. SQLite a des options, mais Postgres rend ce type d’analyse routinier.
Task 14: Estimate DB size and growth (SQLite file vs Postgres cluster)
cr0x@server:~$ du -sh /var/lib/myapp/app.db
48M /var/lib/myapp/app.db
cr0x@server:~$ sudo -u postgres psql -c "SELECT pg_size_pretty(pg_database_size(current_database()));"
pg_size_pretty
----------------
7289 kB
(1 row)
Ce que cela signifie : SQLite est un seul fichier ; Postgres est un arbre de répertoires plus WAL. Les schémas de croissance diffèrent : le fichier SQLite grossit et peut ne pas rétrécir ; Postgres peut gonfler sans vacuum.
Décision : Si vous avez besoin d’une gestion prévisible de la taille et de la rétention, Postgres avec vacuum et partitionnement appropriés (si nécessaire) a tendance à être plus facile à contrôler qu’un fichier monolithique.
Task 15: Test backup/restore workflow (this decides whether you sleep)
cr0x@server:~$ sqlite3 /var/lib/myapp/app.db ".backup '/var/backups/app.db.bak'"
cr0x@server:~$ ls -lh /var/backups/app.db.bak
-rw-r--r-- 1 root root 48M Dec 30 03:12 /var/backups/app.db.bak
cr0x@server:~$ sudo -u postgres pg_dump -Fc -f /var/backups/pg.dump postgres
cr0x@server:~$ ls -lh /var/backups/pg.dump
-rw-r--r-- 1 postgres postgres 36K Dec 30 03:13 /var/backups/pg.dump
Ce que cela signifie : Les deux peuvent être sauvegardés. L’essentiel est la cohérence et les tests de restauration. SQLite a besoin de la bonne méthode de sauvegarde ; Postgres a besoin que vous pratiquiez la restauration et les permissions.
Décision : Si vous ne pouvez pas et ne ferez pas de tests de restauration, ne choisissez ni l’un ni l’autre—parce que vous ne choisissez pas une base, vous choisissez un incident futur.
Feuille de jeu pour diagnostic rapide
Ceci est la séquence de triage « quelque chose est lent ». L’objectif est d’isoler le goulot d’étranglement en minutes, pas de débattre de l’architecture sur Slack pendant des heures.
Premier : est-ce CPU, mémoire ou disque ?
cr0x@server:~$ uptime
03:20:11 up 12 days, 2:41, 1 user, load average: 0.22, 0.40, 0.35
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 0 184320 28000 635000 0 0 10 25 120 180 3 1 95 1 0
0 0 0 183900 28000 635200 0 0 0 0 110 170 2 1 97 0 0
Interprétation : Un wa élevé signifie attente IO ; un si/so élevé signifie swap ; un r élevé avec peu d’idle signifie pression CPU.
Action : Si l’hôte swap, corrigez la mémoire d’abord (réduisez les connexions, tunez Postgres, ajoutez de la RAM). Si l’attente IO est élevée, regardez les checkpoints, le coût de fsync et les patterns d’écriture.
Second : la base est-elle verrouillée ou bloquée ?
SQLite : cherchez des erreurs de verrou dans les logs d’appli ; vérifiez si vous faites des transactions longues.
Postgres : vérifiez les verrous bloquants.
cr0x@server:~$ sudo -u postgres psql -c "SELECT pid, wait_event_type, wait_event, state, query FROM pg_stat_activity WHERE state <> 'idle' ORDER BY pid;"
pid | wait_event_type | wait_event | state | query
------+-----------------+------------+--------+-------
(0 rows)
Interprétation : Si vous voyez des sessions en attente de verrous, vous n’êtes pas « lent », vous êtes sérialisé. Remède différent : raccourcir les transactions, ajouter des index pour diminuer la durée des verrous, éviter les DDL longue durée pendant les heures de pointe.
Troisième : problème de requête ou de capacité ?
Pour Postgres, identifiez les requêtes lentes et faites des EXPLAIN. Pour SQLite, examinez vos patterns d’accès et d’index, et envisagez de sortir les requêtes lourdes du chemin chaud.
cr0x@server:~$ sudo -u postgres psql -c "EXPLAIN (ANALYZE, BUFFERS) SELECT 1;"
QUERY PLAN
--------------------------------------------------------------------------------------
Result (cost=0.00..0.01 rows=1 width=4) (actual time=0.003..0.004 rows=1 loops=1)
Planning Time: 0.020 ms
Execution Time: 0.010 ms
(3 rows)
Interprétation : En usage réel, cherchez des scans séquentiels sur de grandes tables, des hits massifs de buffers, ou du temps passé en attente IO.
Action : Si les requêtes sont lentes à cause d’index manquants, corrigez le schéma. Si c’est le disque lent, améliorez le stockage ou réduisez le churn d’écritures. Si c’est la concurrence, corrigez le pooling ou choisissez la bonne base.
Erreurs courantes (symptômes → cause → correctif)
Ce ne sont pas des échecs moraux. Ce sont des conséquences prévisibles de traiter une base comme une boîte noire.
1) “database is locked” apparaît sporadiquement (SQLite)
Symptômes : Erreurs applicatives sous charge, pics lors de jobs en arrière-plan, requêtes échouant puis réussissant au retry.
Cause : Plusieurs rédacteurs ou transactions longues tenant des verrous d’écriture. WAL aide, mais un seul rédacteur a toujours besoin de temps.
Correctif : Activez WAL ; gardez les transactions courtes ; sérialisez les écritures via une file de jobs ; ajoutez busy_timeout ; ou migrez vers Postgres si vous avez besoin d’écritures concurrentes.
2) SQLite est rapide jusqu’à ce que vous déployiez plusieurs instances
Symptômes : Fonctionne en dev, instable en prod ; performance qui s’effondre seulement en scalant horizontalement.
Cause : Le verrouillage de fichier entre processus devient contention. Aussi : les systèmes de fichiers partagés sont un piège.
Correctif : Ne partagez pas SQLite sur NFS. Si vous avez besoin de plus d’un processus rédacteur, utilisez Postgres.
3) Postgres est « lent » mais le CPU est inactif
Symptômes : Latence élevée, CPU bas, pauses périodiques.
Cause : Attente IO pendant les checkpoints ou charge d’écriture fsync-heavy ; max_wal_size trop petit ; stockage médiocre.
Correctif : Augmentez max_wal_size ; tunez les checkpoints ; déplacez le WAL sur un disque plus rapide si possible ; réduisez les écritures synchrones pour les chemins non critiques (avec précaution).
4) Postgres s’écroule avec beaucoup de connexions sur un petit VPS
Symptômes : Pics mémoire, OOM kills, « too many clients », timeouts aléatoires.
Cause : Un modèle « une connexion par requête » ; coût mémoire par connexion ; pas de pooling.
Correctif : Utilisez PgBouncer ; réduisez max_connections ; utilisez une taille de pool sensée ; faites en sorte que l’appli réutilise les connexions.
5) Des sauvegardes existent mais les restaurations échouent
Symptômes : Test de restauration échoue ; permissions brisées ; rôles manquants ; fichier de sauvegarde SQLite corrompu ou incohérent.
Cause : Sauvegardes prises incorrectement (copie de fichier SQLite en cours d’écriture) ou non testées (pg_dump manquant des globals/rôles).
Correctif : Pour SQLite, utilisez .backup ou l’API de sauvegarde ; pour Postgres, faites des exercices de restauration incluant rôles et schéma ; automatisez la vérification.
6) Les tables Postgres gonflent et les requêtes se dégradent en semaines
Symptômes : Utilisation disque qui augmente plus vite que les données ; index qui grossissent ; requêtes lentes ; vacuum constant.
Cause : Accumulation de tuples morts MVCC ; autovacuum qui n’est pas à la hauteur ; patterns UPDATE/DELETE agressifs.
Correctif : Tuner autovacuum par table ; éviter les hot updates quand possible ; envisager partitionnement ou maintenance périodique.
7) Le fichier SQLite gonfle et ne rétrécit jamais
Symptômes : Utilisation disque qui augmente même après des deletes ; VPS manque d’espace disque.
Cause : SQLite réutilise les pages mais ne rend pas toujours l’espace au système de fichiers ; fragmentation ; gros deletes.
Correctif : VACUUM périodique (coûteux) ; concevoir une stratégie de rétention ; envisager de scinder de grandes tables ou migrer vers Postgres si le churn est élevé.
8) « On a utilisé Postgres parce que c’est entreprise » et maintenant l’ops coule
Symptômes : Personne ne gère les upgrades, vacuum, sauvegardes ; la DB est un animal de compagnie, pas du bétail.
Cause : Avoir choisi Postgres sans allouer la maturité opérationnelle.
Correctif : Soit investissez dans les bases ops (monitoring, drills de restauration, cadence de mise à jour) soit restez simple avec SQLite jusqu’à ce que vous ayez vraiment besoin d’une DB serveur.
Trois mini-récits d’entreprise
Mini-récit 1 : L’incident causé par une mauvaise hypothèse (fichier SQLite sur « stockage partagé »)
L’entreprise était de taille moyenne, le produit se portait bien, et quelqu’un a eu l’idée lumineuse : exécuter deux instances d’appli derrière un load balancer « pour la résilience ». La base était SQLite, posée sur ce que le fournisseur VPS annonçait comme « stockage partagé », monté dans les deux instances. Ça semblait élégant. Un fichier. Deux instances. Que pourrait-il mal se passer ?
Ça a marché quelques jours. Puis le premier pic de trafic—rien de dramatique, juste un email marketing. Les requêtes ont commencé à s’accumuler. La latence a monté. Certains utilisateurs ont eu des erreurs ; d’autres des lectures obsolètes ; quelques-uns ont vu des mises à jour partielles bizarres qui disparaissaient au rafraîchissement.
L’on-call a fouillé les logs et trouvé des « database is locked » intermittents, mais pas constamment. Pire, il y avait des messages sporadiques de type « disk I/O error » qui ressemblaient à du matériel. Ils ne l’étaient pas. C’était le système de fichiers et le gestionnaire de verrous qui avaient un désaccord sur qui détenait la vérité à travers deux nœuds.
L’hypothèse erronée était subtile : « Si le stockage est partagé, le verrou de fichier est partagé. » Sur beaucoup de systèmes de fichiers partagés, les verrous avisés ne se comportent pas comme des verrous ext4 locaux, surtout sous panne ou latence. SQLite n’était pas « cassé » ; l’environnement violait les hypothèses qu’il fait pour fournir les sémantiques ACID.
La correction a été ennuyeuse : passer à Postgres sur un nœud d’abord, puis ajouter un réplica plus tard. Ils ont aussi retiré le montage partagé et traité les frontières de stockage comme des frontières de défaillance. Le rapport d’incident n’a pas blâmé SQLite ; il a blâmé l’architecture qui prétendait qu’un fichier pouvait être un système distribué.
Mini-récit 2 : L’optimisation qui s’est retournée contre eux (Postgres réglé pour la vitesse, payé en anxiété de perte de données)
Une autre organisation avait Postgres sur un petit VPS. Les écritures étaient lourdes : événements, logs, compteurs. L’équipe voulait une latence plus faible et a vu un article qui disait de désactiver des knobs de durabilité. Ils ont changé des paramètres pour réduire la pression fsync et ont rendu les commits plus rapides. Tout le monde a applaudi. Les graphs ont baissé.
Deux semaines plus tard, l’hôte VPS a eu un reboot non planifié. Rien de dramatique—juste une de ces maintenances de nœud dont on apprend après coup. Postgres a redémarré, mais une tranche des écritures les plus récentes avait disparu. Pas catastrophique, mais suffisant pour déclencher des questions clients et des alarmes internes.
Le vrai impôt est arrivé : l’incertitude. Ils ne pouvaient pas dire avec confiance ce qui avait été perdu, et les équipes produit ont commencé à traiter la base comme « peut-être cohérente ». C’est corrosif. Ça transforme chaque bug en débat sur la véracité des données.
L’optimisation s’est retournée parce qu’elle optimisait la mauvaise chose : latence en état stable au prix d’une durabilité prévisible. Il y a des raisons valables de relâcher la durabilité pour de l’analytics éphémère ou des caches. Mais ils l’utilisaient pour de l’état client.
La correction a été de restaurer des réglages sûrs pour les tables cœur, isoler les données à fort débit et faible valeur dans des chemins séparés, et exécuter des sauvegardes avec tests de restauration. Ils ont aussi introduit du batching pour réduire la fréquence des commits plutôt que de parier sur le comportement en cas de crash.
Mini-récit 3 : La pratique ennuyeuse mais correcte qui a sauvé la mise (drills de sauvegarde et automatisation de restauration)
Celui-ci est moins dramatique, et c’est le but. Une équipe de SaaS sur un seul VPS utilisait Postgres. Ils n’étaient pas sophistiqués. Ils n’avaient pas d’équipe plateforme. Mais ils faisaient une chose sans relâche : des drills de restauration hebdomadaires vers une VM de test, avec une checklist.
Ils avaient un script qui récupérait la dernière sauvegarde, la restaurait, exécutait une petite suite de requêtes de sanity, et confirmait que l’appli pouvait démarrer dessus. Ils gardaient aussi un « runbook » minimal décrivant comment promouvoir la DB restaurée si le primaire mourait. Personne n’aimait le faire. C’était comme se brosser les dents.
Puis un développeur a lancé par erreur une migration destructive en prod. Pas malveillant. Juste une variable d’environnement mal réglée et un outil de migration qui a obéi. L’on-call a étouffé les alertes, juré en silence, et a démarré le drill de restauration qu’ils avaient pratiqué.
Ils ont quand même eu une mauvaise heure, mais pas une mauvaise semaine. Ils ont restauré, relancé les migrations correctement, et rejoué une courte fenêtre d’événements business à partir des logs. Le PDG n’a jamais eu à apprendre ce que signifie « WAL », ce qui est le compliment le plus flatteur que les ops puissent recevoir.
Citation (idée paraphrasée) : « On ne se hisse pas à la hauteur d’une occasion ; on retombe sur sa préparation. » — idée paraphrasée souvent entendue dans les cercles fiabilité/ops
Listes de contrôle / plan pas-à-pas
Checklist A : Si vous penchez pour SQLite (donnez-lui une forme production)
- Confirmez la réalité single-writer : listez tous les chemins de code qui écrivent (requêtes web, workers, cron, scripts admin). S’il y a plus d’un acteur à la fois, planifiez de sérialiser ou de migrer.
- Utilisez le mode WAL : définir
PRAGMA journal_mode=WAL. - Réglez synchronous de façon sensée : généralement
NORMALest un bon compromis sur VPS ; utilisezFULLsi vous ne pouvez pas tolérer la perte d’écritures récentes en cas de crash. - Définissez busy_timeout : faites en sorte que l’appli attende brièvement plutôt que d’échouer immédiatement en cas de contention de verrou.
- Sauvegardez correctement : utilisez le mécanisme de backup de SQLite, pas un « cp » du fichier en heures de pointe.
- Prévoyez la croissance du fichier : surveillez la taille du fichier DB et l’espace libre ; planifiez des VACUUM périodiques seulement si nécessaire.
- Ne mettez pas SQLite sur NFS/montages partagés : disque local uniquement, sauf si vous aimez déboguer les verrous de fichier à travers la latence.
Checklist B : Si vous penchez pour PostgreSQL (rendez-le ennuyeux, stable et peu coûteux)
- Taille des connexions adaptée : gardez
max_connectionsraisonnable ; utilisez un pooler pour les applis web. - Réglez la mémoire délibérément : tunez
shared_buffersde façon conservatrice sur petite RAM ; laissez de la marge pour le cache OS et votre appli. - Activez la visibilité des requêtes : activez les stats de requêtes pour voir ce qui est lent avant que les utilisateurs ne se plaignent.
- Surveillez le vacuum : regardez les tuples morts et l’activité autovacuum ; le bloat est une fuite lente.
- Sauvegardes et tests de restauration : automatisez les deux. Une sauvegarde sans test de restauration n’est qu’un vœu.
- Planification des upgrades : décidez comment vous gèrerez les mises à jour mineures et majeures avant d’y être contraint.
- Gestion du disque : surveillez l’utilisation disque pour les données et le WAL ; évitez d’être à 90% plein sur un VPS.
Pas-à-pas : le chemin décisionnel sans regret (15 minutes)
- Exécutez les Tasks 1–4 pour comprendre la réalité RAM et IO.
- Listez vos rédacteurs. S’il y a plus d’un rédacteur concurrent maintenant ou bientôt, choisissez Postgres.
- Si SQLite est encore plausible, lancez les Tasks 7–8. Si la contention de verrous apparaît lors d’un test de concurrence, choisissez Postgres.
- Si vous choisissez Postgres, exécutez les Tasks 9–12 et confirmez que vous pouvez le garder sain sur ce VPS.
- Exécutez la Task 15 et faites au moins un drill de restauration. Choisissez le système dont le chemin de restauration vous pouvez vraiment exécuter sous stress.
Blague #2 : La base la plus rapide est celle que vous n’avez pas perdue à 03:00, ce qui est aussi pourquoi les sauvegardes ont le meilleur ROI de toute fonctionnalité que vous ne présenterez jamais en démo.
FAQ
1) SQLite peut-il gérer du trafic de production ?
Oui, si « trafic de production » signifie principalement des lectures, un petit nombre d’écritures et un modèle de concurrence contrôlé. Il est utilisé dans beaucoup de systèmes réels. Il ne veut juste pas être votre coordinateur d’écritures multi-tenant.
2) Le mode WAL rend-il SQLite « aussi bon que Postgres » ?
Non. WAL réduit le blocage lecteur/rédacteur et améliore la concurrence, mais vous avez toujours un fichier de base unique avec une sémantique de verrouillage et moins d’outils de concurrence. Postgres est conçu comme un service partagé.
3) Postgres est-il overkill pour un petit VPS ?
Parfois. Si votre VPS est minuscule et que votre charge est simple, Postgres peut ajouter des pièces en mouvement supplémentaires. Mais si vous avez plusieurs rédacteurs ou une trajectoire de croissance, « overkill » devient vite « merci de ne pas m’avoir forcé à migrer en urgence ».
4) Quel est le coût caché le plus important de Postgres sur un VPS ?
La gestion des connexions et de la mémoire. Sans pooling et limites sensées, Postgres peut brûler de la RAM sur des sessions inactives et mourir d’une instabilité qui semble aléatoire. Ce n’est pas aléatoire ; c’est des mathématiques.
5) Quel est le coût caché le plus important de SQLite sur un VPS ?
La contention des verrous et les hypothèses opérationnelles. Au moment où vous avez plusieurs rédacteurs, des transactions longues, ou que vous placez le fichier sur un stockage douteux, vous héritez de modes de défaillance qui semblent mystérieux jusqu’à ce que vous acceptiez le modèle de verrouillage.
6) Si je commence avec SQLite, la migration vers Postgres est-elle douloureuse ?
Ça va de « un week-end » à « un trimestre », selon la complexité du schéma, le volume de données et combien votre appli s’appuyait sur des particularités SQLite. Si vous anticipez la croissance, concevez votre appli avec une abstraction DB et des outils de migration dès le premier jour.
7) Dois-je utiliser SQLite pour le cache et Postgres pour la source de vérité ?
Ça peut marcher, mais ne construisez pas un système distribué par accident. Si vous avez besoin d’un cache, envisagez des caches en mémoire ou des stratégies natives Postgres. Si vous utilisez SQLite comme cache local, traitez-le comme jetable et reconstruisable.
8) Qu’en est-il de la durabilité : SQLite est-il dangereux ?
SQLite peut être durable s’il est configuré correctement et utilisé sur un système de fichiers qui respecte ses attentes. Le risque n’est pas « SQLite est dangereux », c’est « SQLite vous permet facilement d’être dangereux sans le remarquer ». Postgres centralise ces comportements de durabilité dans un serveur conçu pour les crashes.
9) Ai-je besoin de réplication sur un VPS ?
Pas toujours. Pour beaucoup de setups VPS, le premier gain est des sauvegardes fiables et des drills de restauration. La réplication devient utile quand vos exigences d’uptime dépassent « restaurer en X minutes » et que vous pouvez assumer la complexité.
10) Comment décider si mon appli a « plusieurs rédacteurs » ?
Si des écritures peuvent se produire simultanément depuis plus d’un processus OS ou conteneur (workers web, job workers, tâches planifiées, scripts admin), vous avez plusieurs rédacteurs. Si vous déployez plusieurs instances d’appli, vous en avez définitivement.
Prochaines étapes à faire aujourd’hui
Choisissez une voie et rendez-la opérationnellement réelle. Les bases ne tombent pas en panne parce que vous avez choisi la mauvaise marque ; elles tombent en panne parce que vous n’avez pas adapté le système à la charge et que vous n’avez pas pratiqué la reprise.
Si vous choisissez SQLite
- Activez WAL et définissez explicitement synchronous.
- Ajoutez un busy timeout et gardez les transactions courtes.
- Implémentez des sauvegardes via le mécanisme de backup de SQLite et faites un test de restauration.
- Écrivez une règle stricte : « pas de filesystem partagé, pas de chaos multi-rédacteur. »
Si vous choisissez PostgreSQL
- Mettez en place un pooling de connexions et des limites sensées immédiatement.
- Activez la visibilité des requêtes et surveillez les requêtes lentes et les verrous.
- Automatisez les sauvegardes et effectuez des drills de restauration selon un calendrier.
- Surveillez l’utilisation disque et la santé du vacuum avant d’en avoir besoin.
La version sans regrets ne consiste pas à choisir la « meilleure » base. Il s’agit de choisir la base dont les modes de défaillance vous sont prévisibles, observables et récupérables sur un VPS pendant des heures adaptées aux humains.