Vous avez exécuté un benchmark rapide sur votre portable et SQLite ressemblait à une fusée. Puis vous avez déployé,
placé la base derrière un serveur web, ajouté quelques workers, et les performances sont devenues un rapport d’incident au ralenti.
Dire que « SQLite est rapide » n’était pas faux — juste incomplet.
La production n’est pas votre portable. En production il y a de la concurrence, des voisins bruyants, de vrais disques, de la latence réelle,
des sauvegardes, des basculements, et l’habitude charmante de punir toute hypothèse non écrite.
C’est pourquoi « rapide en local » devient « lent en prod », et pourquoi MariaDB se comporte souvent mieux sous pression
même lorsqu’il semble plus lourd au premier jour.
La vraie différence : architecture, pas la syntaxe SQL
SQLite est une bibliothèque que vous liez à votre application. MariaDB est un serveur auquel vous vous connectez.
Cette simple phrase explique la plupart des surprises de performance.
Avec SQLite, votre « base de données » est un fichier et le moteur de BD s’exécute dans votre processus. Les lectures peuvent être incroyablement rapides
parce que vous évitez les sauts réseau, les négociations d’authentification, les changements de contexte client/serveur,
et vous touchez souvent le cache de pages de l’OS. Sur une machine de développeur calme avec un seul utilisateur, c’est presque injuste.
MariaDB est un processus séparé (souvent sur un hôte différent). Il paie un overhead mesurable : TCP, gestion des connexions,
parsing des requêtes, ordonnancement de threads. Mais il achète des propriétés que vous mesurez aussi : contrôle de concurrence robuste, mise en mémoire tampon mature et vidage en arrière-plan,
schémas d’E/S ajustés, instrumentation, et des réglages opérationnels conçus pour la réalité multi-locataire.
Si votre charge est « une petite appli, un seul écrivain, beaucoup de lectures, peu d’ops », SQLite peut être le bon choix.
Si votre charge est « trafic web, rafales, multiples workers, tâches en arrière-plan, migrations, et quelqu’un qui demande de la HA » ,
MariaDB cesse d’être optionnel et devient de l’hygiène de base.
Deux métriques de performance que l’on confond (puis on en souffre)
- Latence : combien de temps prend une requête. « Ma requête semble lente. »
- Débit : combien de requêtes vous complétez par seconde sous concurrence. « Le système s’effondre à 20 RPS. »
SQLite peut paraître excellent sur des tests de latence puis s’effondrer sur le débit dès qu’on ajoute des écrivains concurrents.
MariaDB peut sembler plus lent dans un test mono-thread puis garder sa dignité quand le graphe de trafic explose.
Une citation, parce qu’elle reste vraie
Tout échoue, tout le temps.
— Werner Vogels
Si vous concevez en imaginant que le fichier sera toujours disponible, que le verrou ne sera jamais contesté, et que le disque sera toujours rapide,
vous avez conçu pour une démo.
Faits et histoire intéressants (ce qui explique aujourd’hui)
- Le design « serverless » de SQLite était un choix délibéré : c’est un moteur de base de données embarqué, pas un démon, ce qui le rend idéal pour les applications et appliances.
- SQLite stocke toute la base dans un seul fichier (plus des fichiers annexes comme WAL ou journal), ce qui est pratique opérationnellement et sensible en termes de performances.
- SQLite utilise un verrou d’écriture au niveau de la base (avec des nuances en WAL), ce qui simplifie la justesse mais limite les écritures concurrentes.
- Le mode WAL (Write-Ahead Logging) de SQLite améliore radicalement la concurrence lecture/écriture en séparant les lecteurs de l’écrivain—jusqu’à un certain point.
- MariaDB est un fork de MySQL, créé après l’acquisition de Sun par Oracle ; il a maintenu l’écosystème MySQL vivant avec un modèle de gouvernance différent.
- InnoDB est devenu le moteur MySQL par défaut parce qu’il fournissait des transactions ACID et une récupération après crash d’une façon qui montait mieux en charge que les anciens moteurs.
- Le buffer pool d’InnoDB est l’une des raisons majeures pour lesquelles MariaDB peut dépasser SQLite sur des charges réelles : c’est un cache conçu avec des comportements plus intelligents que « ce que le noyau a caché ».
- SQLite est omniprésent — mobiles, navigateurs, systèmes embarqués — parce que l’histoire de déploiement (une bibliothèque, un fichier) est imbattable quand la charge convient.
Pourquoi SQLite est souvent ultra-rapide en local
Les tests locaux sont un scénario optimal pour SQLite. Votre appli et la base partagent la mémoire et le CPU, et le fichier vit sur un SSD local rapide. Le cache de pages de l’OS fait la plupart du travail après warm-up. Et parce que vous exécutez probablement un seul processus,
vous évitez la partie la plus coûteuse du design de SQLite : la coordination.
1) Pas de réseau
MariaDB a besoin d’une socket (TCP ou Unix), d’un protocole, et d’un thread serveur pour répondre. Même quand c’est efficace, ce n’est pas gratuit.
SQLite, ce sont des appels de fonction. C’est pour cela que ça parait vif.
2) « C’est en cache » (vous ne le saviez juste pas)
Quand vous exécutez un benchmark deux fois et que la deuxième passe est plus rapide, vous mesurez probablement le cache de pages de l’OS, pas la base.
SQLite lit le fichier. Le noyau met en cache les pages. Votre deuxième passage est à la vitesse de la mémoire. Félicitations : vous avez benchmarké la RAM.
3) Un seul écrivain, pas de contention
De nombreuses configurations locales exécutent un seul processus. SQLite brille là-dessus. Un seul écrivain peut faire beaucoup de travail par seconde, surtout
quand vous regroupez les transactions et n’effectuez pas de fsync à chaque petite écriture.
4) Overhead de requête plus simple
Le planificateur de requêtes et le moteur d’exécution de SQLite sont légers. Il est optimisé pour un overhead faible. Cela peut battre une base serveur dans
des micro-benchmarks, surtout quand le dataset tient en cache et qu’il n’y a pas de stress de concurrence.
Blague #1 : SQLite en dev, c’est comme un chariot de supermarché avec des roues parfaites — silencieux, fluide, et qui ne transporte que les courses d’une seule personne.
Ce qui change en production : concurrence, stockage et modes de défaillance
Les charges en production ne sont pas polies. Elles sont multi-thread, rafales, et pleines de points de synchronisation accidentels.
Elles tournent aussi sur une infrastructure avec des bizarreries : systèmes de fichiers réseau, couches d’overlay de conteneurs, IOPS bridés, voisins bruyants,
et des jobs de sauvegarde qui arrivent comme par magie au pire moment.
La concurrence transforme « rapide » en « bloqué »
La concurrence SQLite est là où la plupart des histoires « ça marche bien en local » meurent. Le mode d’échec courant n’est pas « SQLite est lent ».
C’est « SQLite attend ».
- Plusieurs écrivains se disputent le même verrou au niveau de la base.
- Les longues transactions de lecture peuvent empêcher les checkpoints WAL de s’exécuter, faisant croître le WAL et provoquant des blocages.
- Les timeouts busy sont mal configurés, conduisant à des erreurs immédiates
database is lockedou à de longues attentes.
Le stockage n’est pas que « vitesse du disque »
La durabilité de SQLite dépend des sémantiques du système de fichiers. Si le système est local, un peu POSIX, et se comporte, vous êtes bon.
Si le fichier vit sur NFS, SMB, un système distribué avec un verrouillage « assez bon », ou un driver de stockage de conteneur avec des comportements fsync surprenants, vous pouvez obtenir n’importe quoi, d’une falaise de performance au risque de corruption.
MariaDB dépend aussi du système de fichiers, mais ses schémas d’E/S et ses réglages sont conçus pour gérer des réalités laides :
group commit, vidage en arrière-plan, doublewrite, vidage adaptatif, et des valeurs par défaut sensées pour la récupération après crash.
Paramètres de durabilité : vous payez toujours quelque part
Le benchmark « rapide en local » désactive souvent la durabilité en silence. SQLite peut fonctionner avec synchronous=OFF ou
NORMAL et paraître incroyable. Mais si la production exige de ne pas perdre de données lors d’une coupure de courant ou d’un crash de nœud,
vous remettrez la durabilité et découvrirez où le temps est passé : fsync.
MariaDB a la même vérité, mais il l’amortit différemment via les redo logs d’InnoDB et le group commit. SQLite paie souvent plus directement par transaction à moins que vous ne regroupiez.
L’observabilité est une fonctionnalité de performance
Quand quelque chose est lent à 2h du matin, « comment voir ce qui se passe ? » devient la seule question. MariaDB dispose d’une instrumentation mature : slow query log, performance schema (dans MySQL ; MariaDB a des outils similaires), état du moteur, statistiques du buffer pool, et info sur connexions/threads. SQLite peut être instrumenté, mais c’est surtout à vous de le faire : métriques au niveau application, tracing, et journalisation soignée autour des transactions.
Pourquoi MariaDB (InnoDB) gagne généralement en production
L’avantage de MariaDB n’est pas d’être magiquement plus rapide en SQL. C’est d’être conçu pour l’accès concurrent, la durabilité contrôlée, et un comportement prévisible sous charge. SQLite est conçu pour être embarqué et correct avec une faible empreinte.
Objectifs différents, résultats différents.
InnoDB gère la concurrence comme s’il attendait que les humains fassent de mauvais choix
InnoDB utilise le verrouillage au niveau des lignes (et MVCC) pour de nombreuses charges, permettant aux écrivains concurrents d’avancer sans se bloquer
aussi souvent. Le verrou d’écriture de SQLite est plus grossier. WAL aide les lectures à coexister avec les écritures, mais pas plusieurs écrivains comme le ferait un moteur OLTP serveur.
Buffer pool vs cache de l’OS : même idée, contrôle différent
SQLite s’appuie fortement sur le cache de pages de l’OS. MariaDB a le buffer pool InnoDB, un cache géré avec des heuristiques internes
et de la visibilité. Vous pouvez le dimensionner, surveiller les taux de hit, et raisonner à son sujet.
Le cache de l’OS reste bon, mais en production vous voulez que la base se comporte de manière cohérente même lorsque le noyau décide
de préférer mettre en cache autre chose (comme vos fichiers de log pendant une rafale d’erreurs).
Durabilité en mode journal et group commit
InnoDB transforme les écritures aléatoires en écritures plus séquentielles sur le journal, vide stratégiquement, et groupe les commits entre sessions.
Cela compte sur de vrais disques et des volumes cloud où les IOPS et la latence sont des réalités facturées, pas des nombres théoriques.
Fonctionnalités opérationnelles qui affectent indirectement les performances
- Gestion des connexions : pooling, cache de threads, max connections. SQLite n’a pas de connexions de la même manière, mais le modèle de processus de votre appli devient le goulot.
- Réplication : pas gratuite, mais permet d’augmenter les lectures et des fenêtres de maintenance plus sûres.
- Changements de schéma en ligne : toujours délicats, mais possibles avec les bons outils et patterns. Les migrations SQLite peuvent verrouiller le monde si vous n’êtes pas prudent.
- Sauvegardes : MariaDB supporte snapshots cohérents et stratégies de streaming ; les backups SQLite sont possibles mais exigent une gestion soigneuse du WAL/journal et des copies de fichiers.
Blague #2 : Benchmarker SQLite sur votre portable et déclarer victoire, c’est comme tester un pont avec un vélo et le qualifier de « prêt pour les camions ».
Tâches pratiques : commandes, sorties et décisions (12+)
Voici les vérifications que je lance quand quelqu’un dit « SQLite était rapide en dev » ou « MariaDB est lent » et attend que je devine.
Chaque tâche inclut : une commande, ce que la sortie signifie, et la décision que vous en tirez.
Task 1: Identify your filesystem and mount options (SQLite cares a lot)
cr0x@server:~$ findmnt -no SOURCE,FSTYPE,OPTIONS /var/lib/app
/dev/nvme0n1p2 ext4 rw,relatime,errors=remount-ro
Signification : ext4 local avec options typiques. Bon point de départ pour SQLite. Si vous voyez nfs, cifs ou quelque chose d’exotique, considérez cela comme un drapeau rouge.
Décision : Si le fichier BD est sur un stockage réseau, déplacez-le sur du disque local ou passez à MariaDB/Postgres. SQLite sur NFS, c’est la voie sûre vers un incident.
Task 2: Check disk latency under real load (your “slow DB” might be slow storage)
cr0x@server:~$ iostat -xz 1 3
Linux 6.5.0 (server) 12/30/25 _x86_64_ (8 CPU)
avg-cpu: %user %nice %system %iowait %steal %idle
12.31 0.00 4.22 8.55 0.00 74.92
Device r/s rkB/s rrqm/s %rrqm r_await rareq-sz w/s wkB/s wrqm/s %wrqm w_await wareq-sz aqu-sz %util
nvme0n1 55.0 4200.0 1.0 1.79 1.20 76.36 90.0 8600.0 3.0 3.23 18.50 95.56 1.80 92.00
Signification : Un w_await élevé (18.5ms) avec un %util important suggère que le device est saturé en écriture. SQLite et MariaDB en souffrent, mais SQLite peut se bloquer davantage quand la fréquence des fsync est élevée.
Décision : Réduisez la fréquence des syncs via le batching, ajustez les paramètres de durabilité (avec précaution), ou fournissez un stockage plus rapide / plus d’IOPS. Si vous ne pouvez pas contrôler les écritures, migrez vers MariaDB et laissez-le amortir les commits.
Task 3: Confirm SQLite journal mode and sync settings (the usual “fast local” trick)
cr0x@server:~$ sqlite3 /var/lib/app/app.db "PRAGMA journal_mode; PRAGMA synchronous;"
wal
2
Signification : wal est bon pour la concurrence lecture/écriture. synchronous=2 signifie FULL. Durable, plus lent.
Décision : Si vous observez des blocages sur les écritures, essayez de regrouper les transactions avant de toucher au paramètre synchronous. Si votre activité peut tolérer une perte de quelques secondes en cas de crash, envisagez synchronous=NORMAL avec WAL — mais documentez le risque.
Task 4: Check whether WAL is growing (often caused by long readers)
cr0x@server:~$ ls -lh /var/lib/app/app.db*
-rw-r----- 1 app app 1.2G Dec 30 01:58 /var/lib/app/app.db
-rw-r----- 1 app app 3.8G Dec 30 02:10 /var/lib/app/app.db-wal
-rw-r----- 1 app app 32K Dec 30 01:59 /var/lib/app/app.db-shm
Signification : Le WAL est plus grand que le fichier DB. Ce n’est pas automatiquement incorrect, mais cela hurle « checkpoint qui n’a pas lieu » ou « transactions de lecture de longue durée ».
Décision : Identifiez les lectures longues ; ajoutez une stratégie de checkpoint ; assurez-vous que les connexions ferment rapidement leurs transactions. Si votre appli garde des transactions de lecture ouvertes pendant des minutes, corrigez cela en priorité.
Task 5: Find long-lived SQLite transactions (application-level, but you can infer)
cr0x@server:~$ lsof /var/lib/app/app.db | head
COMMAND PID USER FD TYPE DEVICE SIZE/OFF NODE NAME
api 1221 app 9u REG 259,2 1288490188 77 /var/lib/app/app.db
worker 1304 app 11u REG 259,2 1288490188 77 /var/lib/app/app.db
Signification : Processus tenant le fichier DB ouvert. Ce n’est pas la preuve de transactions ouvertes, mais ça vous dit qui est dans la pièce.
Décision : Instrumentez l’appli : journalisez les durées begin/commit, ajoutez du tracing autour du scope des transactions, et vérifiez qu’aucun handler de requête ne laisse une transaction ouverte pendant des appels réseau.
Task 6: Check for lock contention symptoms in logs
cr0x@server:~$ journalctl -u app-api -n 30 --no-pager | sed -n '1,8p'
Dec 30 02:12:01 server app-api[1221]: ERROR db: sqlite busy: database is locked
Dec 30 02:12:01 server app-api[1221]: WARN db: retrying transaction attempt=1
Dec 30 02:12:02 server app-api[1221]: ERROR db: sqlite busy: database is locked
Dec 30 02:12:02 server app-api[1221]: WARN db: retrying transaction attempt=2
Signification : Erreurs busy/locked. C’est un effondrement de débit : les workers passent leur temps à réessayer au lieu de faire du travail.
Décision : Réduisez les écrivains concurrents, introduisez une file d’écriture, ou déplacez la charge d’écriture vers MariaDB. SQLite vous dit qu’il n’est pas un moteur de concurrence d’écriture.
Task 7: Validate SQLite busy timeout (avoid instant failure, but don’t hide the problem)
cr0x@server:~$ sqlite3 /var/lib/app/app.db "PRAGMA busy_timeout;"
0
Signification : Pas de busy timeout ; les verrous échouent immédiatement.
Décision : Définissez un busy timeout raisonnable dans l’application (par ex. 2000–5000ms) et ajoutez des réessais avec jitter. Traitez ça comme un pansement ; corrigez toujours la contention.
Task 8: Check MariaDB is actually using InnoDB and not doing something weird
cr0x@server:~$ mariadb -e "SHOW VARIABLES LIKE 'default_storage_engine';"
+------------------------+--------+
| Variable_name | Value |
+------------------------+--------+
| default_storage_engine | InnoDB |
+------------------------+--------+
Signification : InnoDB est le moteur par défaut, comme il se doit pour de l’OLTP en production.
Décision : Si vous voyez autre chose, arrêtez et corrigez ça d’abord. Tuner un mauvais moteur, c’est du théâtre de performance.
Task 9: Check MariaDB buffer pool sizing (common production misconfig)
cr0x@server:~$ mariadb -e "SHOW VARIABLES LIKE 'innodb_buffer_pool_size';"
+-------------------------+-----------+
| Variable_name | Value |
+-------------------------+-----------+
| innodb_buffer_pool_size | 134217728 |
+-------------------------+-----------+
Signification : Buffer pool de 128MB. Correct pour des BDs minuscules, dramatique pour tout ce qui est réel.
Décision : Augmentez-le (souvent 60–75% de la RAM sur un hôte DB dédié). Puis vérifiez le taux de hit et la pression mémoire. Ne laissez pas l’OS ni d’autres services à sec.
Task 10: Detect MariaDB disk flushing pressure (fsync tax)
cr0x@server:~$ mariadb -e "SHOW GLOBAL STATUS LIKE 'Innodb_data_fsyncs'; SHOW GLOBAL STATUS LIKE 'Innodb_os_log_fsyncs';"
+-------------------+-------+
| Variable_name | Value |
+-------------------+-------+
| Innodb_data_fsyncs| 98321 |
+-------------------+-------+
+---------------------+-------+
| Variable_name | Value |
+---------------------+-------+
| Innodb_os_log_fsyncs| 22110 |
+---------------------+-------+
Signification : Les comptes d’fsync peuvent indiquer la charge sur le stockage. Pris seuls ce n’est pas « mauvais », mais des pics corrélés avec la latence sont suspects.
Décision : Si la latence correspond à des pics d’fsync, vérifiez innodb_flush_log_at_trx_commit et la latence du stockage. Ne changez pas la durabilité à la légère ; préférez corriger l’I/O et regrouper les écritures.
Task 11: Find slow queries on MariaDB (don’t guess)
cr0x@server:~$ mariadb -e "SHOW VARIABLES LIKE 'slow_query_log'; SHOW VARIABLES LIKE 'long_query_time';"
+----------------+-------+
| Variable_name | Value |
+----------------+-------+
| slow_query_log | ON |
+----------------+-------+
+-----------------+-------+
| Variable_name | Value |
+-----------------+-------+
| long_query_time | 1.000 |
+-----------------+-------+
Signification : Slow query log activé ; seuil à 1s.
Décision : Utilisez le slow log pour cibler les vrais coupables. Si vous n’avez pas ça activé en production, vous déboguez à l’aveugle.
Task 12: Inspect MariaDB current waits and locks (spot contention)
cr0x@server:~$ mariadb -e "SHOW FULL PROCESSLIST;" | head
Id User Host db Command Time State Info
48 app 10.0.2.41:51244 prod Query 12 Waiting for table metadata lock ALTER TABLE events ADD COLUMN x INT
51 app 10.0.2.40:49810 prod Query 11 Sending data SELECT * FROM events WHERE created_at > NOW() - INTERVAL 1 DAY
Signification : Un verrou de métadonnées d’un ALTER bloque des requêtes. C’est un ralentissement classique en production qui n’a rien à voir avec « la BD est lente » et tout à voir avec la stratégie DDL en ligne.
Décision : Arrêtez de faire des changements de schéma bloquants en pleine charge. Utilisez des méthodes de changement de schéma en ligne ou planifiez des fenêtres de maintenance.
Task 13: Validate you aren’t accidentally running SQLite on an overlay filesystem
cr0x@server:~$ stat -f -c "%T" /var/lib/app/app.db
ext2/ext3
Signification : C’est sur une famille ext (ext4 affiche de la même façon). Si vous voyez overlayfs, vous êtes peut-être dans la couche writable d’un conteneur avec des sémantiques fsync désagréables.
Décision : Placez le fichier BD sur un vrai volume persistant monté, pas sur la couche d’écriture du conteneur.
Task 14: Check open file descriptor limits (SQLite + many workers = surprise)
cr0x@server:~$ ulimit -n
1024
Signification : Limite FD basse. Sous charge, vous pouvez épuiser les FDs et mal diagnostiquer comme « BD lente » parce que tout réessaye.
Décision : Augmentez les limites pour le service et vérifiez via les réglages systemd. Puis retestez sous charge.
Task 15: Quick reality check on CPU throttling (cloud can do that)
cr0x@server:~$ mpstat 1 3
Linux 6.5.0 (server) 12/30/25 _x86_64_ (8 CPU)
01:20:11 PM all %usr %nice %sys %iowait %irq %soft %steal %idle
01:20:12 PM all 18.0 0.0 6.0 2.0 0.0 1.0 0.0 73.0
01:20:13 PM all 19.0 0.0 6.0 3.0 0.0 1.0 0.0 71.0
Signification : Pas de steal évident ici. Si %steal est élevé, votre « problème BD » peut être dû au hyperviseur qui vous vole du CPU.
Décision : Si le steal est élevé, changez de type d’instance ou d’hôte, ou isolez la BD. Tuner des requêtes ne réparera pas un CPU que vous ne possédez pas.
Task 16: Confirm MariaDB durability settings (don’t accidentally run “benchmark mode”)
cr0x@server:~$ mariadb -e "SHOW VARIABLES LIKE 'innodb_flush_log_at_trx_commit'; SHOW VARIABLES LIKE 'sync_binlog';"
+--------------------------------+-------+
| Variable_name | Value |
+--------------------------------+-------+
| innodb_flush_log_at_trx_commit | 1 |
+--------------------------------+-------+
+---------------+-------+
| Variable_name | Value |
+---------------+-------+
| sync_binlog | 1 |
+---------------+-------+
Signification : Paramètres pleinement durables. Cela coûte en latence d’écriture, mais c’est ce que de nombreux environnements de production exigent réellement.
Décision : Si vous changez ces valeurs, faites-le avec une acceptation explicite du risque. Sinon, achetez des IOPS ou repensez les patterns d’écriture.
Playbook de diagnostic rapide
Quand les performances chutent, vous n’avez pas le temps pour la philosophie. Vous avez besoin d’un triage de 10 minutes qui réduit le champ.
Voici le playbook que j’utilise quand la question est « SQLite vs MariaDB — qu’est-ce qui est lent et pourquoi ? »
Premier : est-ce que ça attend des verrous ou de l’I/O ?
- SQLite : cherchez dans les logs
database is locked; vérifiez la taille du WAL ; vérifiez le busy timeout ; identifiez combien d’écrivains existent. - MariaDB : vérifiez
SHOW FULL PROCESSLISTpour les attentes de verrou ; vérifiez l’état d’InnoDB si nécessaire ; consultez le slow query log pour les outliers. - Hôte : lancez
iostat -xzet regardez unawaitélevé et un%utilélevé.
Second : le système sature-t-il une ressource ?
- CPU : %usr/%sys élevé, iowait bas. Le plan des requêtes, le parsing JSON ou le chiffrement peuvent dominer.
- Disque : iowait élevé, await/util disques élevés. Vous êtes limité en IOPS ou en latence.
- Réseau : MariaDB sur un hôte séparé : vérifiez le RTT et les pertes de paquets. Une « requête rapide » sur un réseau lent reste lente.
- Threads/workers : trop de workers applicatifs peuvent détruire SQLite via la contention ; trop de connexions MariaDB peuvent ajouter des changements de contexte et de la pression mémoire.
Troisième : validez la forme de la charge (la question qui décide de la base)
- Combien d’écrivains concurrents au pic ?
- Les écritures sont-elles petites et fréquentes (pire cas), ou regroupées (meilleur cas) ?
- Exécutez-vous de longues transactions de lecture (rapports, exports, analytics) contre la même BD que l’OLTP ?
- Avez-vous besoin de HA/réplication/sauvegardes avec un downtime minimal ?
Si vous avez plusieurs écrivains et que vous ne pouvez pas les sérialiser facilement, arrêtez de discuter avec SQLite. Utilisez MariaDB (ou Postgres).
Si vous faites surtout des lectures et quelques écritures occasionnelles que vous pouvez regrouper, SQLite peut encore gagner.
Erreurs courantes : symptôme → cause racine → correction
1) Symptom: “Random spikes of latency, then everything times out”
Cause racine : Contention du verrou écrivain de SQLite ; les réessais de l’appli amplifient la charge (thundering herd).
Correction : Réduisez la concurrence des écrivains ; implémentez une file unique d’écriture ; regroupez les écritures ; activez WAL ; définissez un busy timeout avec des réessais jitter. Si la concurrence d’écriture est inhérente, migrez vers MariaDB.
2) Symptom: “SQLite WAL file grows without bound”
Cause racine : Des transactions de lecture de longue durée empêchent le checkpoint ; ou les checkpoints sont désactivés/mal configurés.
Correction : Assurez-vous que les lecteurs s’engagent rapidement ; évitez d’entourer de longues tâches de reporting d’une seule transaction ; lancez des checkpoints périodiques ; séparez l’analytics de l’OLTP.
3) Symptom: “It was fast until we put the DB on shared storage”
Cause racine : Verrouillage sur système de fichiers réseau et sémantiques fsync ; la latence et la cohérence des verrous tuent SQLite.
Correction : Mettez SQLite sur disque local uniquement ; sinon passez à MariaDB avec un stockage approprié. Ne « tunez » pas autour du verrouillage NFS en espérant le meilleur.
4) Symptom: “MariaDB is slow, but CPU is low and queries look simple”
Cause racine : Buffer pool InnoDB trop petit ; tout est en lecture disque.
Correction : Augmentez innodb_buffer_pool_size ; vérifiez le working set ; ajoutez des index. Mesurez à nouveau.
5) Symptom: “MariaDB slows down during schema changes”
Cause racine : Verrous de métadonnées ou ALTER bloquant ; les longues transactions empêchent la complétion du DDL.
Correction : Utilisez une stratégie de changement de schéma en ligne ; raccourcissez les transactions ; exécutez le DDL hors-pointe ; assurez-vous que vos outils de migration sont sûrs pour la production.
6) Symptom: “SQLite ‘works’ but we lose data after crashes”
Cause racine : Paramètres de durabilité relaxés (synchronous=OFF), ou stockage sous-jacent qui ment sur le flush.
Correction : Restaurez les paramètres durables ; vérifiez le comportement du système de fichiers et du cache device ; si vous avez besoin de garanties fortes, migrez vers MariaDB avec une config durable et du matériel approprié.
7) Symptom: “High p99 latency only in production”
Cause racine : La production a de la concurrence en rafale, des caches froids, des jobs en arrière-plan, des sauvegardes, des voisins bruyants, et de la contention I/O réelle.
Correction : Faites des tests de charge avec concurrence ; ajoutez des workloads background réalistes ; mesurez le p99, pas les moyennes ; ajoutez de l’observabilité et des alertes avant d’évoluer.
Trois micro-récits d’entreprise tirés du terrain
Micro-récit 1 : L’incident causé par une mauvaise hypothèse
Une entreprise de taille moyenne a construit un scheduler interne : tables de queue, pool de workers, retries, le classique.
Ça a commencé comme un binaire unique avec un fichier SQLite embarqué. C’était rapide, peu coûteux, et déployable partout.
L’équipe adorait parce que cela ne nécessitait aucune coordination avec l’équipe base de données.
Puis le produit est devenu populaire en interne, et l’équipe a mis les workers à l’horizontale. Même volume partagé, même fichier SQLite,
désormais monté dans plusieurs conteneurs. L’hypothèse était « c’est juste un fichier ; plusieurs pods peuvent le lire et l’écrire ».
Pendant une semaine c’était même correct — jusqu’au lundi matin.
Le lundi a apporté une rafale de trafic plus un backlog de jobs planifiés. La contention des verrous s’est transformée en cascade :
les workers réessaient instantanément, ce qui augmente la pression de verrou, ce qui augmente le volume de retries. Le système n’était pas « lent » ; il attendait majoritairement, et quand il travaillait il martelait la couche de stockage avec des petites transactions fsync-intensives.
La correction n’a pas été un pragma astucieux. Ils ont déplacé la queue de jobs vers MariaDB, conservé SQLite pour le cache local là où il appartenait,
et ajouté une règle simple : les bases embarquées sont des composants mono-noeud sauf preuve du contraire.
Le rapport d’incident s’est terminé par une phrase que tout SRE reconnaît : « Nous avons supposé que le système de fichiers se comportait comme un disque local. »
Micro-récit 2 : L’optimisation qui s’est retournée contre eux
Une autre organisation avait un service très écrit qui collectait des événements depuis des devices en périphérie. Ils utilisaient MariaDB et subissaient une latence d’écriture douloureuse au pic. Quelqu’un a regardé les paramètres de durabilité et trouvé des gains faciles : relaxer le fsync, augmenter le débit, déployer.
Les graphes de benchmark étaient superbes.
Deux mois plus tard, un hôte a crashé. Pas tout le cluster — juste une machine. Le service a basculé correctement, mais ils ont découvert
un trou d’événements manquants. Ce n’était pas énorme, mais assez pour casser la réconciliation en aval et déclencher des alertes clients. Soudain tout le monde se souciait de « quelques secondes de données ».
Le postmortem a été gênant parce que l’optimisation avait fonctionné exactement comme conçu. La défaillance n’était pas mystérieuse.
C’était le classique compromis : moins d’fsync, meilleure performance, garanties plus faibles. La vraie erreur a été de traiter ce compromis comme une décision purement technique plutôt qu’une décision produit avec acceptation explicite du risque.
Le plan de rétablissement était ennuyeux : restaurer les paramètres durables, regrouper correctement les inserts, utiliser des inserts multi-lignes, et provisionner
un stockage capable de gérer le débit d’écriture sans compromis. Les performances se sont améliorées de nouveau — cette fois sans jouer.
Micro-récit 3 : La pratique ennuyeuse mais correcte qui a sauvé la mise
Un petit service adjacent aux paiements exécutait MariaDB avec une routine opérationnelle stricte : slow query log activé, revue hebdomadaire des principaux coupables, et habitude d’exécuter les migrations dans un staging avec un volume de données proche de la production.
Ce n’était pas glamour. Ça ne faisait pas de belles slides.
Un développeur a ajouté un nouvel endpoint qui joignait deux tables sur une colonne non indexée. En dev c’était instantané parce que le dataset était minuscule et tout était en cache. En staging avec un snapshot réel, c’était manifestement mauvais : le plan de requête était un scan complet et les estimations de lignes étaient déplorables.
Parce que l’équipe avait l’habitude de vérifier les plans et de regarder le slow log, ils l’ont détecté avant la mise en prod.
Ils ont ajouté l’index, réécrit la requête pour être sargable, et l’incident n’a jamais eu lieu.
L’outil de performance le plus efficace en production reste : « on lit les logs comme des adultes ».
Listes de contrôle / plan étape par étape
Checklist de décision : ce workload doit-il être SQLite ou MariaDB ?
- Choisir SQLite quand :
- Noeud unique, disque local, pas de système de fichiers partagé.
- Principalement des lectures, faible taux d’écritures, ou les écritures peuvent être regroupées.
- Vous voulez zéro overhead opérationnel et pouvez tolérer une concurrence limitée.
- Vous pouvez accepter de « scale up » plutôt que de « scale out ».
- Choisir MariaDB quand :
- Plusieurs écrivains concurrents sont la norme, pas l’exception.
- Vous avez besoin de HA/réplication, maintenance en ligne, et comportement prévisible sous charge.
- Vous avez besoin d’outils opérationnels et de visibilité sans tout instrumenter vous-même.
- Vous prévoyez de la croissance, plusieurs services, ou des patterns d’accès partagés.
Étape par étape : faire tenir SQLite (quand c’est l’outil adapté)
- Gardez le fichier BD sur disque local, pas sur NFS/SMB/couches d’overlay.
- Activez le mode WAL pour les charges en lecture intensives avec écritures occasionnelles.
- Regroupez les écritures : enveloppez plusieurs inserts/updates dans une seule transaction.
- Définissez un busy timeout et implémentez des réessais jitter pour éviter l’effet de foule.
- Gardez les transactions courtes ; ne les maintenez pas pendant des appels réseau ou de longues opérations.
- Surveillez la croissance du WAL ; lancez des checkpoints intentionnels et évitez les lecteurs longs.
- Soyez explicite sur la durabilité et documentez ce que vous pouvez perdre en cas de crash.
Étape par étape : rendre MariaDB rapide (sans désactiver la sécurité)
- Dimensionnez le buffer pool pour contenir le working set, dans les contraintes mémoire.
- Activez et utilisez le slow query log ; corrigez les requêtes, pas les impressions.
- Ajoutez des index appropriés ; vérifiez les plans avant et après.
- Regroupez les écritures (inserts multi-lignes, prepared statements).
- Contrôlez le nombre de connexions via du pooling ; évitez des milliers de connexions concurrentes sauf si vous aimez le changement de contexte.
- Provisionnez des IOPS et mesurez la latence disque ; ne supposez pas que des volumes gp2-like sont magiques.
- Planifiez les changements de schéma pour éviter des tempêtes de verrous de métadonnées.
FAQ
1) SQLite est-il « plus lent » que MariaDB ?
Pas universellement. SQLite peut être plus rapide pour des charges mono-processus, locales, avec des datasets petits à moyens et peu de contention d’écriture.
MariaDB est généralement plus rapide (et plus stable) avec des écrivains concurrents et un accès multi-utilisateur.
2) Pourquoi SQLite semble instantané sur mon portable ?
Vous mesurez l’overhead d’appel de fonction plus le cache OS sur un SSD local avec peu de contention. C’est l’environnement meilleur cas.
La production ajoute la concurrence, la pression I/O, et le coût réel de la durabilité.
3) Le mode WAL rend-il SQLite « prêt pour la production » ?
WAL améliore la concurrence lecture/écriture, surtout de nombreux lecteurs avec un écrivain. Il ne transforme pas SQLite en un serveur OLTP multi-écrivains.
Si vous avez besoin de nombreux écrivains concurrents, WAL ne vous sauvera pas.
4) Puis-je exécuter SQLite sur NFS si je suis prudent ?
Vous pouvez, mais vous misez votre uptime sur des sémantiques de verrouillage et de flush que vous ne contrôlez pas. Si vous avez besoin d’un stockage partagé,
utilisez une base serveur. Le verrouillage fichier de SQLite n’est pas un projet amusant de systèmes distribués.
5) Pourquoi MariaDB est-il plus lent dans mon benchmark mono-thread ?
Parce que vous incluez l’overhead client/serveur et que vous ne saturez probablement pas ce que MariaDB est conçu pour gérer : la concurrence.
Un test juste utilise une concurrence réaliste, des données réalistes, et mesure p95/p99 en plus du débit.
6) Quel est le tueur de performance SQLite le plus courant en production ?
Trop d’écrivains concurrents, généralement causés par la mise à l’échelle horizontale des workers sans stratégie de coordination d’écriture.
Le symptôme est des erreurs de verrou ou de longs blocages, pas toujours un CPU élevé.
7) Quel est le tueur de performance MariaDB le plus courant en production ?
Mauvais index et buffer pool sous-dimensionné, suivi de près par la latence disque et des changements de schéma mal planifiés.
MariaDB est indulgent, mais pas devin.
8) Puis-je garder SQLite et quand même scaler l’appli horizontalement ?
Oui, si vous évitez les écritures partagées. Patterns communs : chaque nœud a son propre SQLite pour le cache ; ou vous canalisez les écritures via un service écrivain unique.
Si chaque nœud doit écrire le même fichier BD, c’est un problème pour une base serveur.
9) Dois-je désactiver fsync/durabilité pour gagner en vitesse ?
Seulement en acceptant explicitement le risque de perte de données, et seulement après avoir corrigé le batching et la provision I/O. Désactiver la durabilité par défaut
transforme les problèmes de performance en problèmes d’intégrité.
10) Si je migre de SQLite vers MariaDB, qu’est-ce qui fait généralement mal ?
Les bords de compatibilité des requêtes, les hypothèses d’isolation de transaction, et les tâches opérationnelles (sauvegardes, utilisateurs, changements de schéma).
La contrepartie est une concurrence prévisible et des outils. Prévoyez du temps pour des migrations propres et de l’optimisation des plans.
Prochaines étapes concrètes pour cette semaine
Si vous exécutez SQLite en production et constatez de la lenteur :
- Confirmez que le fichier BD est sur disque local et pas sur un système partagé/réseau/overlay.
- Activez le mode WAL (si approprié), définissez un busy timeout, et regroupez les écritures.
- Mesurez explicitement la contention des verrous (journalisez les retries, suivez les durées des transactions).
- Décidez si vous essayez de faire de l’OLTP multi-écrivain avec une BD fichier unique. Si oui, stoppez et migrez.
Si vous exécutez MariaDB et voyez de la lenteur :
- Activez le slow query logging (ou vérifiez qu’il est activé), puis corrigez les principaux coupables.
- Dimensionnez correctement le buffer pool InnoDB et assurez-vous de ne pas relire tout depuis le disque à chaque requête.
- Vérifiez la latence du stockage et la pression fsync ; achetez des IOPS avant d’acheter des croyances.
- Auditez les pratiques de changement de schéma pour ne pas vous infliger des outages de verrous de métadonnées.
La leçon clé : la vitesse locale prouve que votre SQL fonctionne. La vitesse en production prouve que votre conception système fonctionne. Traitez-les comme des étapes séparées,
et vous dormirez mieux.