Braucht man wirklich eine Datenbank?
(dbpro.app)- Alle Datenbanken sind letztlich strukturierte Dateisammlungen auf einem Dateisystem, und Anwendungen in einer frühen Phase können oft genügend Leistung erzielen, wenn sie Dateien direkt verwalten
- Beim Vergleich von drei Ansätzen in demselben Server, implementiert in Go, Bun und Rust — Datei-Scan, In-Memory-Map und binäre Suche auf der Festplatte — zeigte sich, dass auch einfacher Dateizugriff einen hohen Durchsatz erreichen kann
- Der In-Memory-Map-Ansatz lieferte die beste Leistung (bis zu 169k req/s), während SQLite mit 25k req/s stabil blieb, aber Overhead mitbringt
- Die meisten Services können mit einer einzelnen SQLite-Datei bis zu rund 90 Millionen DAU bewältigen, sodass in der frühen Produktphase keine separate Datenbank nötig ist
- Eine Datenbank wird erst dann nötig, wenn der Datensatz den RAM übersteigt oder Joins, Suchen mit mehreren Bedingungen, gleichzeitige Schreibzugriffe oder Transaktionen erforderlich werden
Braucht man wirklich eine Datenbank?
- Eine Datenbank ist letztlich nur eine Sammlung von Dateien; SQLite besteht aus einer einzelnen Datei, PostgreSQL aus einem Verzeichnis und Prozessen
- Jede Datenbank liest und schreibt auf das Dateisystem und arbeitet damit im Grunde genauso wie Code, der
open()aufruft - Die Kernfrage ist daher nicht „ob man Dateien schreibt“, sondern „ob man die Dateien einer Datenbank verwendet oder sie selbst verwaltet“
- Viele Anwendungen in der Anfangsphase können auch bei eigener Verwaltung ausreichend Leistung erzielen
- Jede Datenbank liest und schreibt auf das Dateisystem und arbeitet damit im Grunde genauso wie Code, der
Aufbau des Experiments
- Derselbe HTTP-Server wurde in Go, Bun (TypeScript) und Rust implementiert und mit zwei Speicherstrategien verglichen
- Verwendung von drei JSONL-Dateien:
users.jsonl,products.jsonl,orders.jsonl - Erstellen über
POST /users, Abruf überGET /users/:id - Für den Benchmark wurde nur der Leseweg (GET) gemessen
- Verwendung von drei JSONL-Dateien:
-
Ansatz 1: Datei bei jeder Anfrage lesen
- Bei jeder Anfrage wird die Datei geöffnet, jede Zeile gescannt, JSON geparst und auf eine passende ID geprüft
- Im Mittel muss die Hälfte der Datei gelesen werden, also O(n)-Komplexität
- Mit wachsender Datenmenge sinkt die Verarbeitungsgeschwindigkeit pro Anfrage stark
-
Ansatz 2: Alles in den Speicher laden
- Beim Start wird die komplette Datei gelesen und in einer Hashmap auf Basis der ID gespeichert
- Schreibvorgänge werden gleichzeitig in Map und Datei übernommen, Lesevorgänge sind ein einzelner Map-Lookup mit O(1)
- Die Datei dient als persistenter Speicher, die Map als Index
- Paralleles Lesen wird in Go mit
sync.RWMutexund in Rust mitRwLockunterstützt
-
Ansatz 3: Binäre Suche auf der Festplatte
- Ein Mittelweg für schnelle Abfragen, ohne alle Daten in den RAM zu laden
- Es werden eine nach ID sortierte Datendatei und eine Indexdatei mit fester Breite (58 Byte pro Record) erzeugt
- Mit
ReadAtwird der Index in O(log n) durchsucht und anschließend genau der passende Record am Offset gelesen - Werden neue Records angehängt, geht die Sortierung verloren, daher ist eine periodische Neuerstellung oder Zusammenführung des Index nötig
- Dieses Merge-Muster ähnelt der Arbeitsweise eines LSM-tree
Benchmark-Umgebung
- Datensatzgrößen: 10k, 100k, 1M Records
- Lasttest-Tool: wrk, 10 Sekunden lang mit 4 Threads und 50 gleichzeitigen Verbindungen für zufällige GET-Anfragen
- Tests auf derselben Maschine (Apple M1 Mac mini, macOS 15) mit Go 1.26, Bun 1.3 und Rust 1.94
- In Go zusätzlich ein Vergleich mit binärer Suche (Festplatte) und SQLite (
modernc.org/sqlite)
Zentrale Ergebnisse
- Einbruch bei linearer Scan-Leistung: Bei 1M Records fiel Go auf 23 req/s und Bun auf 19 req/s
- Binäre Suche (Festplatte): Im Bereich von 10k bis 1M Records sank die Leistung nur um 15 %, von 45k auf 38k req/s
- Durch den Effekt des OS-Page-Cache bleiben die oberen Bereiche des Index stets im Speicher
- SQLite: Hielt mit 25k req/s und durchschnittlich 2 ms Latenz eine konstante Leistung
- Die binäre Suche war etwa 1,7-mal schneller als SQLite, bei einfachen PK-Abfragen zeigt sich also SQLite-Overhead
- Der Memory-Map-Ansatz erzielte die höchste Leistung: 97k bis 169k req/s bei unter 0,5 ms Latenz
- Bun war schneller als Go: Bun 106k req/s, Go 97k req/s
- Bun basiert auf JavaScriptCore + Zig (uWebSockets) und umgeht libuv
- Rust dominierte beim linearen Scan: 3- bis 6-mal schneller als Go, vermutlich wegen effizienterem JSON-Parsing und I/O
-
Beste Wahl je nach Einsatzzweck
- Absolut höchster Durchsatz: Rust In-Memory-Map (169k req/s)
- Beste Wahl ohne Laden in den RAM: Go binäre Suche (~40k req/s)
- Wenn SQL benötigt wird: SQLite (25k req/s)
- Einfachste Implementierung: Go linearer Scan (~20 Zeilen Code)
Was bedeuten 25.000 req/s?
- Für typischen Web-Traffic wird ein Verhältnis Peak:Durchschnitt = 2:1 angenommen
- Durchschnittlich 12.500 req/s → Peak bei etwa 25.000 req/s
- Angenommen, aktive Nutzer führen 10 Abfragen pro Stunde aus und die gleichzeitige Nutzungsrate im Peak liegt bei 10 %
- Formel für Peak-Anfragen: DAU × 0.000278
- Daraus ergibt sich die Sättigungsgrenze in DAU je Ansatz:
- Go linearer Scan: 2,8M
- Go binäre Suche: 144M
- SQLite: 90M
- Go In-Memory-Map: 349M
- Bun In-Memory-Map: 381M
- Rust In-Memory-Map: 608M
- Die meisten Produkte erreichen diese Größenordnung nicht
- Beispiel: 10.000 SaaS-Kunden → 3 req/s, App mit 100.000 DAU → 30 req/s
- Daraus folgt: Die meisten Produkte in einer frühen Phase brauchen keine Datenbank
- Falls doch, reicht eine einzelne SQLite-Datei bis zu 90 Millionen DAU
Wann eine Datenbank nötig wird
-
Wenn der Datensatz nicht mehr in den RAM passt
- Ab mehreren zehn Millionen Records benötigt schon der Index mehrere GB
- Dann wird Paging nötig, und eine Datenbank übernimmt das automatisch
-
Wenn Abfragen über andere Felder als die ID nötig sind
- Bei Suchen mit mehreren Bedingungen braucht man Datei-Scans oder zusätzliche Maps
- Wenn man mehrere Maps pflegt, implementiert man faktisch selbst eine Query Engine
-
Wenn Joins benötigt werden
- Dann müssen mehrere Dateien gelesen und kombiniert werden, wofür SQL effizienter ist
-
Bei gleichzeitigen Schreibzugriffen aus mehreren Prozessen
- Die In-Memory-Maps der einzelnen Instanzen sind voneinander getrennt, Konsistenz geht verloren
- Es wird eine externe Single Source of Truth benötigt → die Rolle der Datenbank
-
Wenn atomare Schreibvorgänge zwischen Entitäten nötig sind
- Etwa wenn die Erstellung einer Bestellung und die Reduktion des Lagerbestands gemeinsam erfolgreich oder gemeinsam fehlgeschlagen sein müssen
- Dann wäre ein separates Transaktionslog nötig, während die DB das mit ACID löst
- Interne Tools, Side Projects und frühe Produkte ohne solche Anforderungen
- können innerhalb des RAM eines einzelnen Servers ausreichend gut funktionieren
- JSONL-Dateien lassen sich später problemlos in eine Datenbank migrieren
Anhang und bereitgestellter Code
- Enthalten sind Server-Codes für Go, Bun und Rust
- Zusätzlich werden Datenseeds und ein Skript zum Ausführen der Benchmarks (
run_bench.sh) bereitgestellt - Die ZIP-Datei enthält
go-server/,bun-server/,rust-server/undseed.ts - Das Skript erzeugt Datensätze in drei Größen, führt Lasttests mit wrk aus und beendet sich danach
Hinweise zu DB Pro
-
DB Pro** ist ein Datenbank-Client für Mac, Windows und Linux**
- Bietet integriert Funktionen für Abfragen, Exploration und Verwaltung
- Unterstützt eine kollaborative Web-Plattform sowie integrierte KI
- In der neuesten Version wird die Anbindung an die SQLite-Datenbank von Val Town unterstützt
- In v1.3.0 kamen Datenbankerstellung, ein Multi-Query-Editor und Unterstützung für PlanetScale-Vitess-Verbindungen hinzu
26 Kommentare
Was ist das denn für ein Blödsinn?
Glaubt ihr etwa, man benutzt eine DB wegen der Performance?
Stimmt schon, ich habe mir das Original angesehen, in der Hoffnung, dass da vielleicht noch ein neuer Insight drinsteckt, aber was soll das eigentlich ...
Wenn es um so grundlegende Dinge geht wie dass Speicher teuer ist und man deshalb Festplatten nutzt, oder um Stabilität im Produktionsbetrieb, oder um Atomarität,
dann sollte man den Einstieg nicht damit eröffnen, dass man plötzlich nur Geschwindigkeiten vergleicht — da kann ich mir ein spöttisches Lachen nicht verkneifen.
„Wir verkaufen zwar Datenbanken, aber eine DB ist nicht immer notwendig!“ — ob man mit so einem Artikel unbedingt auch noch ganz ungeniert solche Aussagen bringen muss, weil man Marketing machen will grummel... Selbst wenn ich es positiv sehen will, werde ich dabei manchmal doch zynisch.
Na gut, dann nehme ich es eben wenigstens als Benchmark mit.
Typischer Sesselprogrammierer-Unsinn.
Ich halte das für einen sehr guten Artikel. Gerade solche Materialien mit solchen 'Zahlen' sind wertvoll. In einer Zeit, in der man nur schwer Entwickler findet, die auch nur ein ungefähres Gefühl dafür haben, welchen Overhead der von uns geschriebene Code und der von uns verwendete Tech-Stack mit sich bringen, habe ich den Text mit Freude gelesen.
Dem stimme ich ebenfalls zu. Ich denke, es ist Material, das wichtige Intuition für mechanical sympathy und für die richtige Steuerung des Entwicklungstempos vermittelt. So wie „Latency Numbers Every Programmer Should Know“.
Außerdem habe ich diesen Artikel nicht so gelesen, als behaupte er, eine bestimmte Richtung sei bedingungslos besser. Vielmehr wirkten die Zahlen, die alle im Text erwähnten Ansätze gezeigt haben, auf mich wie „mehr als ausreichende Performance für die meisten Unternehmen“, sodass ich ihn eher so verstanden habe, dass man den zum Problem passenden Ansatz wählen sollte.
Die Juwelen in den Antworten sind ein Bonus.
Wenn es einen Grund gibt, es so zu machen, dann sollte man das natürlich in Betracht ziehen, oder? Etwa wenn die Performance-Einschränkungen extrem streng sind.
Aber gibt es in den meisten Fällen wirklich einen Grund, sich extra dafür zu entscheiden? Es ist ja nicht so, dass eine DB keine Vorteile hätte..
Das wirkt doch einfach nur wie ein Denkanstoß, aber ihr reagiert ja alle ziemlich empfindlich.
Genau. In der frühen Phase eines Geschäfts, wenn es noch nicht viele Nutzer gibt, kann man das wohl eher als den Vorschlag verstehen, keine Datenbank zu kaufen oder alles unnötig zu verkomplizieren, sondern mit einfachem Datei-I/O auszukommen, bis sich das Geschäft etabliert hat.
Ich stimme ebenfalls zu. In Services wird die DB oft wichtiger genommen als nötig, und manchmal wird sogar übermäßig viel in das Design investiert, als wäre es ein riesiges Problem, wenn die Normalisierung einmal aufgebrochen wird.
Es geht nicht darum, keine DB zu verwenden, sondern eher darum, sich noch einmal ins Gedächtnis zu rufen, warum man sie überhaupt nutzt und was eigentlich der Kern des Services ist. Schon allein aus dieser Perspektive darauf zu schauen, dürfte sehr hilfreich sein.
Am Ende ist Balance immer wichtig.
Ab dem Moment, in dem man sich für SQLite auf einem Produktionsserver entscheidet, muss man sich ständig Gedanken darüber machen, wann man davon wegmigriert.
Früher lohnte sich diese Überlegung, weil die Kosten der DB selbst (Serveranschaffung, IDC, Lizenzkosten usw.) hoch waren,
aber heute lässt sich das Ganze sprichwörtlich per Klick aufsetzen – muss man dann wirklich noch darüber nachdenken?
Auch jetzt noch sind Datenbanken teuer.
Natürlich kann es sein, dass man für ein „Projekt in der Anfangsphase oder eine kleine Anwendung“ keine Datenbank braucht. Nicht nur bei der Datenbank, auch andere Elemente kann man dann ziemlich beliebig umsetzen. Das Problem entsteht erst, wenn der Maßstab wächst. Im Grunde ist das einfach ein Beitrag, der aus Spaß an Zahlen geschrieben wurde.
https://hackers.pub/@gnh1201/2025/…
Manchmal ist die Installation einer separaten Datenbank gar nicht nötig. Allerdings nur unter Windows...
Beim Lesen des Titels musste ich laut lachen.
Ich frage mich manchmal, ob die zentralen Entitäten ihre Persistenz wirklich über ein RDBMS sicherstellen müssen. Schließlich gibt es inzwischen einige alternative Technologien, die als SSOT dienen können.
Wenn SQLite kaputtgeht, ist man aufgeschmissen..
Gibt es Fälle, in denen
sqlitekaputtgeht? Das würde mich interessieren. Abgesehen von ungewöhnlichen Dateioperationen wie Verschieben oder Löschen.Hacker-News-Kommentare
Mir gefällt dieser Artikel wirklich. Er zeigt sehr gut, wie schnell Computer sind
Mit dem Fazit im letzten Teil stimme ich allerdings nicht überein. Der Autor sagte, das gelte nicht für viele Apps mit der Einschränkung, dass „mehrere Prozesse gleichzeitig schreiben müssen“, aber in der Praxis müssen selbst Produkte in einer frühen Phase oft gleichzeitig schreiben, etwa durch separate Worker wie cron oder eine Message Queue
Man kann es so bauen, dass nur der Main-Server schreibt, aber das erhöht die Architekturkomplexität
Deshalb stimme ich dem Autor aus reiner Skalierungssicht zu, aber etwas breiter betrachtet finde ich, dass es besser ist, eine Datenbank zu verwenden. Besonders SQLite ist eine vernünftige Wahl
Wenn Skalierung nötig ist, kann man häufig abgerufene Daten im Speicher cachen. Die Kombination, die ich verwende, ist SQLite + In-Memory-Cache
S3 funktioniert manchmal, hat aber weiterhin viele Einschränkungen, wenn man es als vollständigen Ersatz nutzen will
Es ist viel einfacher und günstiger, weil man keinen separaten DB-Server verwalten oder Backups pflegen muss
Ich mag SQLite wirklich sehr, habe aber erkannt, dass es nicht die Antwort auf alle Probleme ist
Beim Bau einer clientseitigen Wörterbuch-App habe ich den SQLite-wasm-Port ausprobiert, aber die DB-Datei war größer als erwartet, ließ sich nicht gut komprimieren und lud langsam
Am Ende habe ich direkt aus der ursprünglichen TSV-Datei einen Index gebaut, ihn mit zstd komprimiert und in wasm bei jedem Zugriff wieder dekomprimiert. Das war deutlich schneller als SQLite
Die Modulgröße sank außerdem von 800 KB auf 52 KB, und selbst mehrere gleichzeitige Instanzen waren kein Problem
Für die String-Suche habe ich stringzilla verwendet, und das ist absurd schnell
SQLite ist großartig, aber nicht in jeder Situation die richtige Antwort
Der SQLite-Benchmark ist nicht besonders gut optimiert
Schon allein das Hinzufügen von
hat auf meiner Maschine die Leistung von 27.700 r/s auf 89.687 r/s springen lassen
Ich habe prepared statements und Timestamps als int ausprobiert, aber das hat keinen großen Unterschied gemacht
Der Artikel war okay, aber der Teil „alle DBs greifen mit open() auf das Dateisystem zu“ ist nicht ganz korrekt
Apps wie SQLite verwenden mmap, um Dateien direkt in den Speicherraum zu mappen. Damit kann man Syscalls umgehen und viel schneller zugreifen
Später im Artikel wird erklärt, wie die gesamte Datei in den Speicher gelesen wird, aber mit mmap wäre das wohl besser gewesen
Trotzdem ist mmap nicht immer besser. Manche ziehen es vor, Dinge in der Anwendungslogik direkt zu behandeln, statt von OS-APIs abhängig zu sein
Siehe dazu die mmap-Studie von CMU
Die Formulierung „funktioniert wie open()“ ist etwas vereinfacht, aber technisch nicht falsch
Vor langer Zeit habe ich mit Perl eine kleine Verkaufs-Web-App gebaut, und weil ich auf dem Server des ISP nichts installieren konnte, habe ich ein dateibasiertes Hashing verwendet
Der Kunde hat das über 20 Jahre lang unverändert genutzt, bis er verstarb, und die Familie hat es dann durch Wordpress ersetzt
Als ich zuletzt nachgesehen habe, gab es Hunderttausende Bestellungen, und die Performance war immer noch okay
Dank der Hardware-Entwicklung hat diese Hack-Struktur viel länger durchgehalten als erwartet. Heute würde vermutlich auch SQLite völlig ausreichen
Wenn man Storage selbst implementiert, versteht man, wie DBs funktionieren
Man muss effizient mit Indizes und Datenstrukturen umgehen, und am Ende kommt man zu dem Schluss: „Wenn es kein Spielzeug ist, hätte man von Anfang an eine DB verwenden sollen“
Relational Databases Aren’t Dinosaurs, They’re Sharks
Im Vergleich zu den geringen Vorteilen bei kleinen Apps ist die Zeitverschwendung, das Rad neu zu erfinden, viel größer
In der Kreidezeit hatten Haie bereits fast dieselbe Form wie heute und haben danach ohne große Veränderungen überlebt
Dinosaurier, Flugsaurier und Mosasaurier dagegen sind verschwunden, während Haie, Krokodile und große Schlangen dank ihres optimierten Designs bis heute fast unverändert existieren
Ich finde, relationale DBs sind genauso
Solche Artikel zu lesen macht Spaß
Trotzdem verwende ich in 99 % der Fälle weiterhin eine DB mit SQL und Transaktionen
In einem privaten Projekt habe ich Daten allerdings kürzlich mit einem einfachen dateisystembasierten Ansatz auf Basis von YAML-Dateien verwaltet, und in meiner Größenordnung gibt es überhaupt keine Performance-Probleme
Dass Menschen die Daten lesen können und dass sie diffbar sind, war für mich wichtiger als Performance
In den meisten Fällen würde ich trotzdem eine DB mit Abfragesprache und garantierter Konsistenz wählen
Am Ende braucht man doch immer die Funktionen einer DB und ACID-Garantien
Immer wenn ich gelegentlich mit einem Legacy-Flatfile-Store arbeiten muss, quäle ich mich damit, Konsistenz, Transaktionen und eine Abfragesprache nachträglich hineinzubauen. Am Ende erfindet man nur wieder das Rad neu
In dem Moment, in dem Atomarität nötig ist, ist eine DB unverzichtbar
Atomare Schreibvorgänge auf einem Dateisystem zu implementieren, ist äußerst fragil
Aus diesem Grund haben viele DBs bei Abstürzen Probleme mit Datenkorruption. Früher war das bei RocksDB unter Windows der Fall
Das selbst zu implementieren fühlt sich nach Wahnsinn an. Es wäre zwar gut, zu lernen, wie man mit OS-APIs sicher schreibt, aber heutzutage ist das eine viel zu nischige Fähigkeit
Dazu kommt, dass die nächste Person es wahrscheinlich nicht warten kann. Am Ende würde man ohnehin auf eine DB umsteigen
Man sollte mindestens in eine temporäre Datei auf demselben Dateisystem schreiben und sie nach fsync per rename ersetzen
Wenn man die gesamte DB in eine temporäre Datei schreibt und sie nach dem Flush per move ersetzt, ist das unter Unix atomar
Allerdings skaliert das überhaupt nicht. Selbst kleine Updates zwingen dazu, die gesamte Datei neu zu schreiben, und man braucht Locking. Es löst nur einen Teil von ACID
Übrigens funktioniert die OLAP-DB DuckDB auch bei Out-of-Core-Workloads hervorragend
Link zur offiziellen Dokumentation
Man kann auch ohne Kühlschrank leben, aber das wäre wohl ziemlich unpraktisch.
Wenn man einen Kühlschrank nutzen kann, gibt es keinen Grund, darauf zu verzichten.
Bist du ein Ilbe-Troll, Nora?
Heißt das, wenn man Nein sagt, ist man gleich Ilbe? Ich komme aus der Gyeongsang-Region, weißt du?
Ich möchte das melden, aber ich weiß nicht, wie man eine Meldung einreicht. Seufz.
Dieser Kommentar scheint zu zeigen, wie festgefahren das Denken koreanischer Entwickler ist und auf welchem Niveau GeekNews steht.
Sag doch mal, was für ein Niveau das überhaupt sein soll, warum du dieses Niveau so bewertet hast, und zwar mit mindestens zwei von Logik/Fakten/Wissenschaft/Statistik, okay.
Haha, schon an den Worten sieht man, dass das ein Typ von DC Inside, Ilbe oder FMKorea ist. Kümmere dich nicht darum.