A-t-on vraiment besoin d’une base de données ?
(dbpro.app)- Toutes les bases de données ne sont au fond qu’un ensemble structuré de fichiers sur un système de fichiers ; pour une application en phase initiale, gérer directement des fichiers peut offrir des performances suffisantes
- En implémentant le même serveur en Go, Bun et Rust pour comparer trois approches — scan de fichiers, map en mémoire et recherche binaire sur disque —, on peut obtenir un débit élevé même avec un simple accès fichier
- L’approche avec map en mémoire offre les meilleures performances (jusqu’à 169k req/s), tandis que SQLite reste stable à 25k req/s mais avec un certain surcoût
- La plupart des services peuvent atteindre jusqu’à 90 millions de DAU avec un simple fichier SQLite, ce qui rend une base de données séparée inutile au début d’un produit
- Il devient nécessaire d’introduire une base de données lorsque le jeu de données dépasse la RAM ou quand on a besoin de jointures, recherches multicritères, écritures concurrentes et transactions
A-t-on vraiment besoin d’une base de données ?
- Une base de données n’est au fond qu’un ensemble de fichiers : SQLite est un fichier unique, PostgreSQL repose sur un répertoire et des processus
- Toutes les bases de données lisent et écrivent sur le système de fichiers, et fonctionnent de la même manière qu’un appel à
open()dans le code - L’enjeu n’est donc pas de savoir s’il faut « écrire dans des fichiers », mais utiliser les fichiers de la base de données ou les gérer soi-même
- Pour beaucoup d’applications en phase initiale, une gestion directe peut suffire en termes de performances
- Toutes les bases de données lisent et écrivent sur le système de fichiers, et fonctionnent de la même manière qu’un appel à
Configuration de l’expérience
- Le même serveur HTTP a été implémenté en Go, Bun (TypeScript) et Rust, avec comparaison de deux stratégies de stockage
- Utilisation de trois fichiers JSONL :
users.jsonl,products.jsonl,orders.jsonl - Création via
POST /users, consultation viaGET /users/:id - Seul le chemin de lecture (GET) a été retenu pour le benchmark
- Utilisation de trois fichiers JSONL :
-
Approche 1 : lire le fichier à chaque requête
- À chaque requête, le fichier est ouvert, toutes les lignes sont parcourues, le JSON est parsé puis l’ID est comparé
- En moyenne, il faut lire la moitié du fichier, soit une complexité en O(n)
- Plus les données grossissent, plus la vitesse de traitement chute fortement
-
Approche 2 : tout charger en mémoire
- Au démarrage, le fichier complet est lu et stocké dans une table de hachage indexée par ID
- Les écritures sont répercutées à la fois dans la map et dans le fichier ; les lectures se font par un simple accès à la map en O(1)
- Le fichier joue le rôle de stockage persistant, la map celui d’index
- En Go,
sync.RWMutex, et en Rust,RwLock, permettent les lectures parallèles
-
Approche 3 : recherche binaire sur disque
- Une solution intermédiaire pour obtenir des lectures rapides sans tout charger en RAM
- Création d’un fichier de données trié par ID et d’un fichier d’index à largeur fixe (58 octets/enregistrement)
- L’index est parcouru en O(log n) avec
ReadAt, puis un seul enregistrement est lu à l’offset correspondant - L’ajout de nouveaux enregistrements casse l’ordre de tri, ce qui impose une reconstruction périodique de l’index ou une fusion
- Ce schéma de fusion ressemble au fonctionnement d’un LSM-tree
Environnement de benchmark
- Taille des jeux de données : 10k, 100k, 1M enregistrements
- Outil de charge : wrk, avec requêtes GET aléatoires pendant 10 secondes, sur 4 threads et 50 connexions concurrentes
- Tests réalisés sur la même machine (Apple M1 Mac mini, macOS 15) avec Go 1.26, Bun 1.3 et Rust 1.94
- En Go, comparaison supplémentaire avec la recherche binaire (sur disque) et SQLite (modernc.org/sqlite)
Principaux résultats
- Dégradation des performances en scan linéaire : à 1M d’enregistrements, Go tombe à 23 req/s et Bun à 19 req/s
- Recherche binaire (sur disque) : de 10k à 1M d’enregistrements, le débit ne baisse que de 15 %, de 45k à 38k req/s
- Grâce au cache de pages de l’OS, les parties hautes de l’index restent toujours en mémoire
- SQLite : 25k req/s avec une latence moyenne de 2 ms, pour des performances constantes
- La recherche binaire est environ 1,7× plus rapide que SQLite, qui garde un surcoût sur une simple lecture par clé primaire
- L’approche avec map en mémoire est la plus performante : 97k à 169k req/s, avec une latence inférieure à 0,5 ms
- Bun est plus rapide que Go : Bun à 106k req/s contre 97k req/s pour Go
- Bun s’appuie sur JavaScriptCore + Zig(uWebSockets) et contourne libuv
- Rust domine largement en scan linéaire : 3 à 6 fois plus rapide que Go, probablement grâce à l’efficacité du parsing JSON et des E/S
-
Meilleur choix selon le cas d’usage
- Débit absolu maximal : map en mémoire Rust (169k req/s)
- Meilleur sans chargement en RAM : recherche binaire Go (~40k req/s)
- Si SQL est nécessaire : SQLite (25k req/s)
- Implémentation la plus simple : scan linéaire Go (~20 lignes de code)
Ce que signifient 25 000 req/s
- Le trafic web classique repose sur une hypothèse de rapport pic:moyenne = 2:1
- 12 500 req/s en moyenne → 25 000 req/s au pic
- En supposant qu’un utilisateur actif effectue 10 consultations par heure et qu’au pic 10 % des utilisateurs sont connectés simultanément
- Formule des requêtes de pointe : DAU × 0.000278
- Résultat du calcul du DAU à saturation pour chaque approche
- Scan linéaire Go : 2.8M
- Recherche binaire Go : 144M
- SQLite : 90M
- Map en mémoire Go : 349M
- Map en mémoire Bun : 381M
- Map en mémoire Rust : 608M
- La plupart des produits n’atteignent jamais ces chiffres
- Exemples : 10 000 clients SaaS → 3 req/s, application à 100 000 DAU → 30 req/s
- En conclusion, la plupart des produits en phase initiale n’ont pas besoin de base de données
- Et si besoin, un simple fichier SQLite peut tenir jusqu’à 90 millions de DAU
Quand une base de données devient nécessaire
-
Quand le jeu de données ne tient plus en RAM
- Au-delà de plusieurs dizaines de millions d’enregistrements, rien que les index peuvent nécessiter plusieurs Go
- Il faut alors paginer les données, ce qu’une base de données gère automatiquement
-
Quand il faut interroger sur d’autres champs que l’ID
- Les recherches multicritères imposent soit un scan de fichiers, soit des maps supplémentaires
- Maintenir plusieurs maps revient en pratique à réimplémenter soi-même un moteur de requêtes
-
Quand des jointures sont nécessaires
- Il faut lire et combiner plusieurs fichiers, et SQL devient plus efficace
-
En cas d’écritures concurrentes depuis plusieurs processus
- Les maps en mémoire de chaque instance sont séparées, ce qui fait perdre la cohérence
- Il faut une source externe unique de vérité → le rôle d’une base de données
-
Lorsqu’il faut des écritures atomiques entre entités
- Il faut garantir que la création d’une commande et la décrémentation du stock réussissent ou échouent ensemble
- Cela nécessite d’implémenter un journal de transactions séparé, alors qu’une base de données le gère via ACID
- Les outils internes, side projects et produits en phase initiale qui n’ont pas ces contraintes
- peuvent fonctionner correctement dans la RAM d’un seul serveur
- Les fichiers JSONL peuvent ensuite être facilement migrés vers une base de données
Annexe et code fourni
- Code serveur Go, Bun et Rust inclus
- Fourniture séparée des données de seed et du script d’exécution du benchmark (
run_bench.sh) - Le fichier ZIP contient
go-server/,bun-server/,rust-server/,seed.ts - Le script initialise les données aux trois tailles, lance les tests de charge avec wrk, puis s’arrête
Informations sur DB Pro
-
DB Pro** est un client de base de données pour Mac, Windows et Linux**
- Il intègre requêtes, exploration et administration
- Avec plateforme web collaborative et IA intégrée
- La dernière version prend en charge la connexion aux bases SQLite de Val Town
- La v1.3.0 ajoute la création de bases, un éditeur multi-requêtes et la connexion à PlanetScale Vitess
26 commentaires
C'est quoi encore ce délire ?
On croit que les DB sont utilisées pour les performances ou quoi ?
Oui, voilà. Je me suis dit qu’il y aurait peut-être un nouvel angle intéressant, alors j’ai même regardé l’article original, mais je me suis demandé ce que c’était que ça...
Au lieu d’ouvrir avec des points basiques comme le fait qu’on utilise le disque parce que la mémoire coûte cher, la stabilité d’exploitation en production ou encore l’atomicité, ils se mettent d’emblée à faire des comparaisons de vitesse, et ça me fait doucement rire.
« On vend des DB, mais il n’y a pas toujours besoin d’une DB ! » — ils publient un article pour dire ça sans la moindre gêne ; on se demande s’ils veulent juste faire du marketing -_-... Même en essayant de voir ça positivement, ça me rend parfois un peu cynique.
Bon, disons au moins qu’on en aura tiré un benchmark.
Je pense que c’est un très bon article. En particulier, ce genre de contenu avec des « chiffres » est précieux. À une époque où il est difficile de trouver des développeurs qui ont ne serait-ce qu’une intuition approximative des surcoûts du code que nous écrivons et de la stack technique que nous utilisons, je l’ai lu avec plaisir.
Je suis également d’accord. Je pense que c’est une ressource qui apporte une intuition importante sur la mechanical sympathy et sur la gestion du bon niveau d’intensité dans le développement. Comme « Latency Numbers Every Programmer Should Know », par exemple.
Et je n’ai pas non plus lu cet article comme affirmant qu’une direction particulière serait inconditionnellement meilleure. Au contraire, les chiffres montrés par toutes les approches mentionnées dans l’article m’ont semblé correspondre à des « performances largement suffisantes pour la plupart des entreprises », et je l’ai donc plutôt lu comme une invitation à choisir l’approche adaptée à la situation.
Les pépites dans les réponses sont en prime.
S’il y a une raison de faire ainsi, ça peut valoir le coup d’y réfléchir, non ? Par exemple si les contraintes de performance sont extrêmement fortes.
Mais dans la plupart des cas, est-ce qu’il y a vraiment une raison de choisir ça ? Les avantages qu’apporte une base de données ne sont quand même pas inexistants...
On dirait juste une invitation à changer de perspective, mais vous êtes tous bien susceptibles.
Oui, en effet. Au début d’une activité, quand il n’y a pas encore beaucoup d’utilisateurs, on peut voir cela comme une simple proposition : ne pas acheter de base de données ni trop compliquer les choses, et aller jusqu’à la stabilisation du service avec de simples E/S de fichiers.
Je suis d’accord aussi. Dans les services, on accorde parfois à la DB une importance plus grande que nécessaire, et il arrive même qu’on investisse excessivement dans la conception, comme si casser la normalisation allait forcément être catastrophique.
Il ne s’agit pas de dire qu’il ne faut pas utiliser de DB, mais plutôt de rafraîchir sa réflexion sur la raison pour laquelle on l’utilise et sur ce qui constitue vraiment le fondement du service.
Au final, l’équilibre reste toujours essentiel.
À partir du moment où on choisit SQLite pour un serveur de production, il faut sans cesse se demander quand il faudra migrer.
Autrefois, cela valait la peine d’y réfléchir parce que le coût même de la base de données (achat des serveurs, IDC, licences, etc.) était élevé,
mais aujourd’hui, alors qu’on peut déployer tout ça en un soi-disant simple clic, est-ce vraiment nécessaire de se poser la question ?
Même maintenant, les bases de données coûtent cher.
C’est du coding de façade typique.
Bien sûr, s’il s’agit d’un « projet à un stade très précoce ou d’une application de petite taille », on peut ne pas avoir besoin d’une base de données. Et pas seulement d’une base de données : pour les autres éléments aussi, on peut faire un peu n’importe quoi à ce stade. Le vrai problème, c’est quand l’échelle augmente. Au final, c’est juste un article amusant qui regarde des chiffres.
https://hackers.pub/@gnh1201/2025/…
Parfois, il n’est pas nécessaire d’installer une base de données distincte. Mais c’est limité à Windows...
Le titre m'a bien fait éclater de rire.
Je me demande parfois s'il est vraiment nécessaire que les principales entités garantissent leur persistance via un SGBDR. Il existe déjà pas mal de technologies alternatives pour fournir une SSOT, après tout.
Si SQLite se corrompt, il n’y a plus de solution...
Existe-t-il des cas où
sqlitepeut se corrompre ? Je suis curieux. En dehors des déplacements ou suppressions de fichiers anormaux, bien sûr.Avis Hacker News
J’aime beaucoup cet article. Il montre très bien à quel point les ordinateurs sont rapides
En revanche, je ne suis pas d’accord avec la conclusion de la fin. L’auteur dit que cela ne s’applique pas aux applications où « plusieurs processus doivent écrire en même temps », mais en pratique, même dans un produit au stade initial, il arrive souvent que des workers séparés comme cron ou une message queue doivent écrire simultanément
On peut faire en sorte que seul le serveur principal écrive, mais cela augmente la complexité architecturale
Donc, du point de vue strict de la montée en charge, je suis d’accord avec l’auteur, mais avec une vision plus large, je pense qu’il vaut mieux utiliser une base de données. En particulier, SQLite est un choix raisonnable
Si l’échelle devient un enjeu, il suffit de mettre en cache en mémoire les données fréquemment consultées. La combinaison que j’utilise est SQLite + cache en mémoire
S3 peut parfois faire l’affaire, mais il y a encore trop de contraintes pour l’utiliser comme remplacement complet
C’est bien plus simple et moins coûteux, car il n’y a pas de serveur de DB séparé à gérer ni de sauvegardes à maintenir
J’adore vraiment SQLite, mais j’ai compris que ce n’est pas la réponse à tous les problèmes
En créant une appli de dictionnaire côté client, j’ai essayé le port wasm de SQLite, mais le fichier de DB était plus gros que prévu, se compressait mal et le chargement était lent
J’ai finalement changé d’approche en construisant directement un index à partir du fichier TSV source, en le compressant avec zstd, puis en le décompressant à chaque fois dans wasm. C’était bien plus rapide que SQLite
La taille du module est passée de 800KB à 52KB, et même avec plusieurs instances lancées en parallèle, la charge restait faible
Pour la recherche de chaînes, j’ai utilisé stringzilla, et c’est absurdement rapide
SQLite est excellent, mais ce n’est pas la bonne réponse dans tous les cas
Le benchmark SQLite est insuffisamment optimisé
Rien qu’en ajoutant
les performances sont passées sur ma machine de 278700 r/s à 8987 r/s
J’ai essayé les prepared statements et le remplacement des timestamps par des int, mais cela n’a pas fait de grande différence
L’article était correct, mais la partie qui dit que « toutes les DB accèdent au système de fichiers via open() » n’est pas exacte
Des applications comme SQLite utilisent mmap pour mapper directement le fichier dans l’espace mémoire. Cette méthode permet d’éviter les syscalls et d’accéder beaucoup plus vite aux données
Plus loin dans l’article, l’auteur explique le processus qui consiste à lire tout le fichier en mémoire ; avec mmap, cela aurait sans doute été mieux
Cela dit, on ne peut pas forcément dire que mmap soit toujours meilleur. Certains préfèrent gérer cela directement dans la logique applicative plutôt que de dépendre des API de l’OS
Voir l’article associé : l’étude de CMU sur mmap
La formulation « ça fonctionne comme open() » est un peu simplifiée, mais techniquement, ce n’est pas faux
Il y a longtemps, j’avais créé une petite application web de vente en Perl, et comme il était impossible d’installer quoi que ce soit sur le serveur de l’ISP, j’avais utilisé un hash basé sur des fichiers
Le client l’a utilisé tel quel pendant plus de 20 ans avant de décéder, puis sa famille a repris l’activité et est passée à Wordpress
La dernière fois que j’ai vérifié, il y avait plusieurs centaines de milliers de commandes, et les performances restaient correctes
Grâce aux progrès du matériel, cette architecture bricolée a tenu bien plus longtemps que prévu. Aujourd’hui, SQLite aurait probablement suffi
Implémenter soi-même le stockage permet de comprendre comment fonctionne une DB
Il faut gérer efficacement les index et les structures de données, et on finit par conclure que « si ce n’est pas un jouet, il aurait fallu utiliser une DB dès le départ »
Relational Databases Aren’t Dinosaurs, They’re Sharks
Comparé au faible gain qu’on tire d’une petite appli, la perte de temps à réinventer la roue est bien plus importante
À l’époque du Crétacé, les requins avaient déjà à peu près leur forme actuelle, et ils ont survécu ensuite sans grands changements
À l’inverse, les dinosaures, les ptérosaures et les mosasaures ont disparu, alors que les requins, les crocodiles et les grands serpents existent encore aujourd’hui presque inchangés grâce à une conception optimisée
Je pense que les DB relationnelles sont de cette nature
J’aime lire ce genre d’articles
Malgré tout, dans 99 % des cas, j’utilise quand même une DB avec SQL et des transactions
Cela dit, sur un projet personnel récent, j’ai essayé de gérer les données avec un simple système de fichiers basé sur des fichiers YAML, et à mon échelle, il n’y a eu aucun problème de performances
Le fait que ce soit lisible par un humain et diffable était plus important que les performances
Malgré cela, dans la plupart des cas, je choisirai quand même une DB avec un langage de requête et des garanties de cohérence
Au final, on finit toujours par avoir besoin des fonctionnalités d’une DB et des garanties ACID
Chaque fois que je dois travailler avec un ancien stockage en flat files, je galère à lui greffer de force cohérence, transactions et langage de requête. Au fond, cela revient à réinventer la roue
Dès qu’on a besoin d’atomicité, une DB devient indispensable
Implémenter des écritures atomiques au-dessus d’un système de fichiers est extrêmement fragile
C’est pour cela que beaucoup de DB ont des problèmes de corruption de données en cas de crash. C’était le cas de RocksDB sur Windows à une époque
L’implémenter soi-même me paraît relever de la folie. Ce serait bien d’apprendre à écrire de manière sûre avec les API de l’OS, mais aujourd’hui c’est une compétence trop niche
En plus, la personne qui reprendra le code risque de ne pas pouvoir en assurer la maintenance. On finira de toute façon par passer à une DB
Il faudrait au minimum écrire dans un fichier temporaire sur le même système de fichiers, faire un fsync, puis remplacer via rename
Si on écrit toute la DB dans un fichier temporaire, puis qu’on flush avant de remplacer par move, c’est atomique sur Unix
En revanche, ça ne passe absolument pas à l’échelle. Il faut réécrire tout le fichier même pour une petite mise à jour, et il faut aussi gérer les verrous. Cela ne résout qu’une partie d’ACID
À noter qu’en tant que DB OLAP, DuckDB fonctionne aussi remarquablement bien sur des workloads out-of-core
Lien vers la documentation officielle
On peut vivre sans réfrigérateur, mais ce serait moins pratique.
S’il est possible d’en utiliser un, il n’y a aucune raison de s’en priver.
Êtes-vous un partisan d'Ilbe, à ce point ?
Si je dis non, ça fait automatiquement de moi un type d'Ilbe ? Moi, je viens pourtant du Gyeongsang-do, hein ?
J’aimerais le signaler, mais je ne sais pas comment faire. Argh.
On dirait que ce commentaire montre bien l’étroitesse de vue de certains développeurs coréens et le niveau de GeekNews.
Dis-moi de quel niveau il s’agit exactement, pourquoi tu as évalué ce niveau ainsi, et explique-le en utilisant au moins deux éléments parmi la logique, les faits, la science et les statistiques.
Haha, rien qu’en voyant les mots, on voit que c’est un type de DC Inside, Ilbe ou FM Korea ; ne faites pas attention.