Bien démarrer avec SQLite en prod

SQLite en production ? Si tu es comme moi, ta première réaction a été de lever un sourcil. On m'a tellement répété que c'était une base pour prototypes ou pour du local que j'ai mis des années à oser l'utiliser en prod. Puis j'ai découvert que des services comme Cloudflare ou des applications mobiles l'utilisent sérieusement. Et là, j'ai compris : SQLite n'est pas un jouet, c'est un vrai moteur de base de données embarquée qui peut encaisser des charges impressionnantes si tu le configures correctement.

Dans ce tutoriel, je vais te montrer comment passer de « SQLite pour mes tests » à « SQLite en production » sans te prendre la tête. On va voir ensemble les fondamentaux : pourquoi c'est un choix pertinent pour certains cas, comment le configurer pour qu'il tienne la route, le mode WAL pour gérer la concurrence en lecture/écriture, les transactions ACID qui garantissent l'intégrité de tes données, et enfin les sauvegardes fiables. Je te donnerai des exemples concrets, des commandes, et des retours d'expérience.

Un exemple qui m'a bluffé : j'ai une application de suivi de logs qui écrit environ 200 requêtes par seconde en pic. Avec SQLite en mode WAL, elle tourne sans broncher sur un simple VPS. Le secret ? Une configuration adaptée et une bonne compréhension des limites. Parce que oui, SQLite a des limites, mais elles sont souvent bien plus hautes qu'on ne le croit.

Alors, si tu es prêt à arrêter de te cacher derrière MySQL ou PostgreSQL pour des projets qui n'en ont pas besoin, suis-moi. On va faire le tour complet, et je te garantis que tu verras SQLite autrement.

Qu'est-ce que SQLite et pourquoi l'utiliser en production ?

SQLite, c'est une bibliothèque en C qui embarque un moteur de base de données relationnelle complet dans un seul fichier. Pas de serveur, pas de port à ouvrir, pas de processus à gérer. Tu l'inclus dans ton application, et elle lit et écrit directement dans un fichier .db. C'est la base de données la plus déployée au monde : elle est dans ton téléphone, dans ton navigateur, dans les systèmes embarqués. Et pourtant, on continue à la regarder de haut. Moi-même, j'ai mis des années à la prendre au sérieux. Puis j'ai vu un projet qui servait 500 requêtes par seconde sur un Raspberry Pi, et j'ai compris que le problème n'était pas SQLite, mais la façon dont on l'utilise.

Pourquoi l'utiliser en production ? D'abord, la simplicité. Tu copies un fichier, tu as ta base. Pas de serveur à configurer, pas de droits à gérer, pas de réseau à sécuriser. Ensuite, la performance. Pour des lectures, SQLite est souvent plus rapide que PostgreSQL ou MySQL, surtout quand la base tient en mémoire. Et puis, il y a la fiabilité. Les transactions ACID sont garanties par le moteur, et avec le mode WAL, tu peux avoir des lectures concurrentes pendant une écriture. J'ai une application de suivi de logs qui écrit 200 requêtes par seconde en pic, et elle tourne sur un VPS avec 512 Mo de RAM. Avec MySQL, j'aurais dû mettre en place un serveur dédié et des caches. Avec SQLite, je n'ai qu'un fichier et une bonne configuration.

Bien sûr, il y a des limites. SQLite ne gère pas les connexions multiples comme PostgreSQL, et l'écriture est sérialisée. Mais pour une application web à trafic modéré, un outil interne, une API de niche, c'est largement suffisant. Cloudflare l'utilise pour gérer des données de configuration, et des applications mobiles comme WhatsApp l'utilisent pour stocker les messages. Alors, si tu penses que SQLite n'est pas pour la production, pose-toi la question : est-ce que ton application a vraiment besoin d'un serveur de base de données ? Si la réponse est non, tu te compliques la vie pour rien.

Dans la suite de ce tutoriel, je vais te montrer comment configurer SQLite pour qu'il tienne la route en production : le mode WAL, les transactions ACID, les sauvegardes. Mais d'abord, retiens ceci : SQLite n'est pas un jouet, c'est un outil puissant qui a sa place en production, à condition de connaître ses limites et de le configurer correctement.

Configuration optimale pour SQLite en production

Les paramètres par défaut de SQLite sont pensés pour la compatibilité, pas pour la performance. Si tu veux tenir en production, il faut passer par des PRAGMA. Voici ma config de base, celle que j'applique partout :

Le mode WAL, on en parle plus tard, mais c'est lui qui te permet de lire et écrire en même temps. `synchronous=NORMAL` est le bon compromis : en WAL, avec NORMAL, les transactions sont durables sans le coût d'un fsync à chaque commit. Ça accélère énormément les écritures.

Ensuite, `cache_size=-64000` passe le cache à 64 Mo. Si ta base tient en RAM, ça change la vie. Les lectures deviennent quasi instantanées. Et `busy_timeout=5000` évite les erreurs `database is locked` dès que deux processus se marchent dessus. Crois-moi, ça arrive vite.

Je vois trop de gens copier des configs sans comprendre. `PRAGMA synchronous=OFF` pour "aller plus vite", c'est le meilleur moyen de perdre des données. Ne fais pas ça.

Dernier point, et pas des moindres : `PRAGMA foreign_keys=ON`. SQLite ne vérifie pas les clés étrangères par défaut. Tu oublies ça, et un `JOIN` te retourne des résultats incohérents. Le genre de bug qui te pourrit une release.

Attention, ces PRAGMA sont par connexion. Si tu utilises un pool de connexions, il faut les exécuter à chaque ouverture. Personnellement, je les mets dans un hook d'initialisation de ma lib. J'ai un service qui écrit 200 requêtes par seconde, et avec cette config, la latence reste sous les 2 ms. Teste, mesure, et adapte. Mais ces cinq lignes sont le socle.

Le mode WAL : améliorer la concurrence

SQLite est une base de données embarquée, mais par défaut elle se bloque toute seule dès qu'une écriture est en cours. Les lectures attendent, et tu te retrouves avec des 'database is locked' en pleine production, à 2 heures du matin. J'ai vécu ça avec une appli qui écrivait seulement 30 requêtes par seconde : c'était déjà trop.

Le mode WAL change la mécanique. Au lieu d'écrire directement dans le fichier .db, SQLite enregistre les modifications dans un journal séparé, puis les intègre à la base par lots. Résultat : les lecteurs continuent de lire la dernière version stable pendant qu'un écrivain fait son travail, et l'écrivain ne bloque plus les lecteurs.

Attention, SQLite reste mono-écrivain : deux écritures simultanées, l'une attend. Mais pour de la concurrence en lecture/écriture classique, c'est le jour et la nuit.

Pour activer le mode WAL, c'est une seule instruction :

Pas plus. Le mode est persistant pour la base, tu peux le vérifier avec :

Tu vas voir apparaître deux fichiers -wal et -shm à côté de ta base. C'est normal, ne les supprime jamais.

Je m'arrête rarement à cette seule ligne. Je combine toujours WAL avec ces deux pragmas :

synchronous=NORMAL évite les lenteurs inutiles tout en gardant une base cohérente en cas de crash. wal_autocheckpoint, c'est le nombre de pages du WAL avant qu'il se réécrive dans la base. Avec ces réglages, mon outil de logs encaisse 200 requêtes par seconde en pic sans un seul 'database is locked'.

Transactions ACID et sauvegardes robustes

Tu penses que les transactions ACID sont un truc de gros SGBD ? Détrompe-toi. SQLite les gère nativement, et c'est même un de ses points forts. Mais attention : si tu ne les utilises pas correctement, tu vas droit dans le mur. Moi, j'ai appris à la dure quand une mise à jour en masse a corrompu une base de 2 Go parce que j'avais oublié de wrapper mes opérations dans une transaction. Depuis, je ne jure que par BEGIN IMMEDIATE.

Pourquoi BEGIN IMMEDIATE ? Parce qu'il prend un verrou en écriture dès le départ, ce qui évite les deadlocks dans les applications multi-thread. En mode WAL, tu peux continuer à lire pendant l'écriture, mais si tu veux écrire, tu dois être sûr que personne d'autre ne va tenter d'écrire en même temps. Avec BEGIN IMMEDIATE, tu réserves le terrain. Voici un exemple typique :

Si une erreur survient entre les deux UPDATE, tu fais ROLLBACK et tout revient à l'état initial. C'est ça, l'atomicité. Et pour la durabilité, SQLite utilise le journal (ou WAL) pour garantir que les données ne sont pas perdues en cas de crash. Le mode WAL est d'ailleurs indispensable en production : il améliore la concurrence en lecture/écriture et réduit les blocages. Active-le avec PRAGMA journal_mode=WAL; et tu verras la différence.

Maintenant, parlons sauvegardes. Beaucoup de gens copient le fichier .db à la volée. Grosse erreur. Si une écriture est en cours, tu obtiens une copie corrompue. L'API de sauvegarde en ligne de SQLite est là pour ça. Elle te permet de faire une sauvegarde cohérente sans interrompre les opérations. En Python, par exemple, tu peux utiliser sqlite3.Connection.backup() :

Cette méthode gère les verrous et copie même les pages modifiées pendant la sauvegarde. Pour une automatisation, tu peux la lancer dans un cron ou un scheduler. Moi, je fais une sauvegarde toutes les heures sur un volume séparé, et une complète chaque nuit. Ça m'a sauvé la mise plus d'une fois.

Un conseil : ne te contente pas de la sauvegarde. Teste la restauration régulièrement. J'ai vu des équipes sauvegarder pendant des mois, puis découvrir que les fichiers étaient illisibles. Avec SQLite, c'est simple : tu copies le fichier de backup, tu ouvres avec PRAGMA integrity_check; et tu vérifies. Si ça passe, tu es tranquille.

Conclusion

Voilà, tu as maintenant toutes les cartes en main pour faire de SQLite un vrai citoyen de ta production. Le plus dur, c'est de te débarrasser de ton a priori. Je suis passé par là : je croyais que SQLite c'était un jouet, et j'ai mis des années à le déployer pour un service qui écrit 200 requêtes par seconde. Résultat : zéro incident, et un déploiement qui tient dans un seul binaire.

Ce qu'il faut retenir, c'est que le mode WAL change la donne pour la concurrence. Tu lis et tu écris en même temps sans te battre avec des verrous. Pour mes tables de logs, j'ai activé WAL avec un simple PRAGMA journal_mode=WAL; et les pics de latence ont fondu. Les transactions ACID, elles, sont là pour te sauver la mise si ton application plante : avec un bon BEGIN IMMEDIATE, tu évites les interblocages. Et pour les sauvegardes, l'API de backup en ligne de SQLite est bien plus fiable qu'une copie de fichier à l'arraché.

Alors, quand est-ce que tu ne devrais pas utiliser SQLite ? Si tu as besoin de plusieurs écrivains sur des machines différentes, ou si tu vises des millions d'écritures par seconde, passe ton chemin. Mais pour une application embarquée, un site à trafic modéré, ou un outil interne, SQLite est souvent le choix le plus pragmatique. C'est un des rares cas où tu peux dire que la solution est simple sans être simpliste.

Mon dernier conseil : fais le test toi-même. Prends un projet existant, active WAL, règle ton cache, et mesure. Tu vas peut-être découvrir, comme moi, que ta base de données « de dev » n'attendait que ça. Et si un jour tu te retrouves face à un problème de concurrence, tu auras au moins la certitude d'avoir épuisé toutes les options avant de basculer sur PostgreSQL.

SQLite ne demande pas qu'on croie en lui. Il demande qu'on le configure comme une vraie base de production, avec de la discipline et des tests. Fais-le, et tu ne regarderas plus jamais les fichiers .db de la même façon.

Link_