42 points par GN⁺ 2026-04-16 | 26 commentaires | Partager sur WhatsApp
  • 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

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 via GET /users/:id
    • Seul le chemin de lecture (GET) a été retenu pour le benchmark
  • 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

 
happing94 2026-04-16

C'est quoi encore ce délire ?
On croit que les DB sont utilisées pour les performances ou quoi ?

 
unknowncyder 2026-04-16

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.

 
botplaysdice 2026-04-17

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.

 
foriequal0 2026-04-17

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.

 
botplaysdice 2026-04-17

Les pépites dans les réponses sont en prime.

 
white9s 2026-04-17

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...

 
m00nlygreat 2026-04-16

On dirait juste une invitation à changer de perspective, mais vous êtes tous bien susceptibles.

 
tazuya 2026-04-17

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.

 
smash8106 2026-04-17

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.

 
cafedead 2026-04-16

À 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 ?

 
roxie 2026-04-22

Même maintenant, les bases de données coûtent cher.

 
myc0058 2026-04-24

C’est du coding de façade typique.

 
csjune 2026-04-17

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.

 
carnoxen 2026-04-17

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...

 
roxie 2026-04-22

Le titre m'a bien fait éclater de rire.

 
kuthia 2026-04-17

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.

 
neptune 2026-04-17

Si SQLite se corrompt, il n’y a plus de solution...

 
okxrr 2026-04-17

Existe-t-il des cas où sqlite peut se corrompre ? Je suis curieux. En dehors des déplacements ou suppressions de fichiers anormaux, bien sûr.

 
GN⁺ 2026-04-16
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

    • Je vis souvent une situation similaire. Même si un seul serveur suffit, dès qu’il faut de la redondance serveur, on a besoin de stockage réseau, et on finit par pencher vers une DB accessible sur le réseau
      S3 peut parfois faire l’affaire, mais il y a encore trop de contraintes pour l’utiliser comme remplacement complet
    • Aujourd’hui, quand je démarre un nouveau projet, j’utilise SQLite par défaut. Les performances sont excellentes, et si le projet grossit plus tard, il est facile de migrer vers Postgres
      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
    • Après avoir vu le benchmark Rust 1M, j’ai de nouveau réalisé à quel point les ordinateurs sont rapides
  • 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

    db.SetMaxOpenConns(runtime.NumCPU())
    db.SetMaxIdleConns(runtime.NumCPU())
    

    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

    • Il est vrai que l’article simplifie la façon dont les DB font leurs E/S
      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
    • Le backend store utilisé par mmap reste au final un fichier sur le système de fichiers
      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

    • Je serais curieux de savoir quel type de produit ce site vendait
  • 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

    • La métaphore requins vs dinosaures est vraiment pertinente
      À 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

    • Si j’avais besoin de modifications atomiques sur un fichier, j’utiliserais simplement SQLite
      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
    • Le code de l’article produira un fichier vide le jour où une panne de courant surviendra
      Il faudrait au minimum écrire dans un fichier temporaire sur le même système de fichiers, faire un fsync, puis remplacer via rename
    • Dans les cas simples, ce n’est pas si fragile
      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
    • Vu comme ça, on est déjà en train de traiter le A d’ACID
      À noter qu’en tant que DB OLAP, DuckDB fonctionne aussi remarquablement bien sur des workloads out-of-core
    • En 2025, Linux + ext4 prennent en charge les écritures atomiques mono-bloc et multi-blocs
      Lien vers la documentation officielle
 
mstorm 2026-04-16

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.

 
foobarman 2026-04-17

Êtes-vous un partisan d'Ilbe, à ce point ?

 
alfenmage 2026-04-24

Si je dis non, ça fait automatiquement de moi un type d'Ilbe ? Moi, je viens pourtant du Gyeongsang-do, hein ?

 
foobarman 2026-04-24

J’aimerais le signaler, mais je ne sais pas comment faire. Argh.

 
okxrr 2026-04-17

On dirait que ce commentaire montre bien l’étroitesse de vue de certains développeurs coréens et le niveau de GeekNews.

 
alfenmage 2026-04-24

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.

 
foobarman 2026-04-24

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.