27 Punkte von GN⁺ 2025-12-03 | 2 Kommentare | Auf WhatsApp teilen
  • Die Single-Writer-Architektur und die eingebettete Natur von SQLite erweisen sich experimentell sogar als Faktoren, die Skalierbarkeit und Performance erhöhen
  • Unter denselben Bedingungen fiel Postgres bei Netzwerklatenz auf 348 TPS, während SQLite ohne Netzwerk 44.096 TPS erreichte
  • Mit Batch-Verarbeitung und fein granularen Transaktionen auf Basis von SAVEPOINT unter Nutzung des Single-Writer-Modells wurden bis zu 186.157 TPS erzielt, in einer stabilen Konfiguration 102.545 TPS
  • Amdahls Gesetz erklärt den Flaschenhals netzwerkbasierter Datenbanken, während SQLite durch dessen Umgehung eine hohe Effizienz beibehält
  • Die Ergebnisse unterstreichen das Potenzial von SQLite in lokalen Umgebungen und die Bedeutung der Beseitigung von Netzwerk-Flaschenhälsen

Die Struktur von SQLite und die Testumgebung

  • SQLite hat kein MVCC und erlaubt nur einen einzigen Writer, doch genau diese Struktur ermöglicht paradoxerweise eine hohe Skalierbarkeit
    • Als eingebettete Datenbank gibt es keinen Netzwerk-Overhead
  • Die Benchmarks wurden auf einem MacBook Pro (2021) mit Apple M1 Pro und 16 GB RAM durchgeführt
  • Ziel des Tests war nicht die perfekte Optimierung, sondern zu zeigen, dass auch unter gewöhnlichen Bedingungen ein hoher Schreibdurchsatz erreichbar ist

Definition von TPS und Transaktionsbeispiel

  • TPS bedeutet nicht nur einfache Schreibgeschwindigkeit, sondern interaktive Transaktionen (Interactive Transactions)
    • Beispiel: Bei einer Überweisung zwischen Konten laufen mehrere Queries und Anwendungscode innerhalb einer einzigen Transaktion
  • Transaktionen können bei Fehlern den Zustand zurückrollen und spielen damit eine Schlüsselrolle für die Wahrung der Konsistenz

Aufbau des Benchmarks

  • Zur Simulation umfangreicher gleichzeitiger Anfragen wurden virtuelle Threads (virtual threads) auf Basis von Clojure verwendet
  • Postgres wurde mit einem Connection Pool auf Basis von HikariCP konfiguriert, SQLite mit einem Single Writer und so vielen Leseverbindungen wie CPU-Kernen
  • Beide Datenbanken verwendeten eine einfache account-Tabelle mit den Feldern id, balance und fügten 1 Milliarde Zeilen ein
  • Die Nutzeraktivität folgt einer Power-Law-Verteilung (0,9995), mit rund 100.000 aktiven Nutzern

Performance der Netzwerkdatenbank Postgres

  • Auf demselben Server erreichte Postgres 13.756 TPS
  • Bei zusätzlicher Netzwerklatenz von 5 ms fiel der Wert auf 1.214 TPS, bei 10 ms auf 702 TPS
  • Mit Serialisierungs-Isolationsstufe sank die Leistung auf 660 TPS, mit zusätzlichen Queries weiter auf 348 TPS
  • Das zeigt gemäß Amdahls Gesetz, dass Netzwerk-Flaschenhälse die Gesamtleistung begrenzen
    • Mit steigender Netzwerklatenz verschärft sich die Konkurrenz um Transaktions-Locks, wodurch Skalierung unmöglich wird

Die Vorteile von SQLite als eingebettete Datenbank

  • Nach dem Wegfall des Netzwerks erreichte SQLite 44.096 TPS
    • Da der Netzwerk-Flaschenhals verschwindet, wird der Einfluss von Amdahls Gesetz minimiert
  • Mit Batch-Verarbeitung unter Nutzung der Single-Writer-Struktur stieg der Wert auf 186.157 TPS
    • Durch dynamische Anpassung der Batch-Größe werden Latenz (latency) und Durchsatz (throughput) automatisch optimiert

Fein granulare Transaktionen mit SAVEPOINT

  • Um Ausfälle einzelner Transaktionen innerhalb eines Batches abzufangen, wurden verschachtelte Transaktionen mit SAVEPOINT eingesetzt
    • Im Fehlerfall wird nur die betreffende Transaktion zurückgerollt, der gesamte Batch bleibt erhalten
  • Auch mit diesem Ansatz blieben 121.922 TPS erhalten

Gemischter Lese-/Schreib-Lasttest

  • 75 % aller Requests waren Lesezugriffe, 25 % Schreibzugriffe
  • Mit einem separaten Thread-Pool für Lesevorgänge wurden Leseanfragen so getrennt, dass sie Schreibvorgänge nicht behindern
  • Das Ergebnis waren 102.545 TPS

Zusammenfassung des Performance-Vergleichs

Bedingung Postgres SQLite
Kein Netzwerk 13.756 44.096
5 ms Latenz 1.214 n/a
10 ms Latenz 702 n/a
10 ms + Serialisierung 660 n/a
Batch-Verarbeitung n/a 186.157
Batch + SAVEPOINT n/a 121.922
Batch + SAVEPOINT + Lesen n/a 102.545

Fazit

  • SQLite erreicht dank Single-Writer-Modell und eingebetteter Architektur deutlich höhere TPS als netzwerkbasierte Datenbanken
  • Durch das Umgehen der von Amdahls Gesetz beschriebenen Grenzen von Netzwerk-Flaschenhälsen wird die Effizienz maximiert
  • Der gesamte Code ist auf GitHub veröffentlicht, außerdem werden Materialien zu Themen wie Amdahls Gesetz, Power Law und Skalierungsbeispielen mit SQLite bereitgestellt
  • SQLite ist eine sehr effektive Wahl für hochperformante Transaktionsverarbeitung in lokalen Umgebungen

2 Kommentare

 
ppp123 2025-12-10

Wenn man keinen externen Server nutzt und nur in einer lokalen Umgebung arbeitet, muss man dann wirklich die Steuer namens Netzwerk zahlen? (VFS vs. Socket)

 
GN⁺ 2025-12-03
Hacker-News-Kommentar
  • Ich baue derzeit einen hybriden protobuf ORM/CRUD-Server auf SQLite-Basis
    Code und Erläuterungen gibt es unter GitHub - accretional/collector
    Mit Echtzeit-Backups sind 5–15 ms Downtime möglich, Hunderte von Lese-/Schreibanfragen können in die Queue gestellt werden, die gesamte CRUD-Latenz liegt bei etwa 1 ms, und sogar Streaming-Backups auf WAL-Basis sind möglich
    Früher habe ich nur Postgres und Spanner verwendet, aber wenn Collector nur noch Partitionierung bekommt, werde ich Postgres wahrscheinlich nicht wieder benutzen

    • Mich würde interessieren, ob du auch darüber nachgedacht hast, mit einem atomic-snapshot-Dateisystem wie BTRFS zusammen mit SQLite + WAL Zero-Downtime-Backups zu machen. Nach dem Snapshot kann man das Backup langsam wegschreiben und dann löschen
  • Der Nachteil ist, dass alle Daten und Operationen auf eine einzelne Maschine passen müssen
    Mit einer AWS-u-24tb1.112xlarge-Instanz (448 vCores, 24 TB RAM, 64 TB EBS) hat man ziemlich viel Spielraum

    • Wenn man aber einen Bare-Metal-Server von Hetzner mietet, bekommt man 2–3x Performance pro Core bei 90 % geringeren Kosten
    • Die theoretische maximale DB-Größe von SQLite ist laut offizieller Dokumentation 281 TB. In der Praxis ist das Filesystem-Limit kleiner, aber es funktioniert normal
    • Vertical Scaling auf einer einzelnen Maschine ist stabil, aber bei der Elastizität schwach. Bei Traffic-Spitzen muss man überprovisionieren oder Ausfälle in Kauf nehmen
    • Wenn man sich den Link yourdatafitsinram.net mit der Frage „Passt deine Datenmenge in den RAM?“ anschaut, halte ich bei einem leistungsstarken Single Node einen dedizierten Server für sinnvoller als EC2
  • Der Artikel betont die Effizienz von SQLite, aber ich finde die Vergleichsbasis unklar
    Ausgangspunkt war offenbar eine getrennte Server-Architektur, gemessen wurde dann aber die Performance einer lokalen eingebetteten DB
    Unter denselben Bedingungen könnte man mit einem lokal getunten Postgres ähnliche Leistung erreichen

    • SQLite ist selbst gegenüber Postgres auf derselben Maschine schneller. Es ist sinnvoll, anhand von Konfigurationen aus realen Deployment-Umgebungen zu testen
    • Man kann SQLite auch hinter einen Request-Handler kapseln und auf einem anderen Server betreiben. Letztlich ist eine DB nur die Kombination aus Request-Verarbeitung und Storage
    • Auf einer Single-Box ist raw throughput entscheidend. SQLite ist 10x schneller als PG, und PG wird umso langsamer, je komplexer die Transaktionen werden
    • Zu sagen „Dann ist SQLite kein Vergleichsobjekt“ ist zu simpel. Dann würde der Artikel viel zu kurz werden
    • SQLite eignet sich nicht nur für Mobile oder Embedded, sondern auch für Server-Apps mit geringer Parallelität. Es ist keine DB nur für Webserver
  • Die Begrenzung von Postgres auf 8 Verbindungen könnte ein Bottleneck sein
    Es wäre gut, CPU- und Thread-Auslastung mit zu veröffentlichen und mit einem größeren Connection Pool erneut zu testen

    • Den Connection Pool an die Zahl der Cores (8) anzupassen, ist okay, aber wenn es innerhalb der Transaktion ein sleep gibt, entsteht ein Bottleneck
      Mit 64 Verbindungen könnte der Durchsatz um das 8-Fache steigen. Man sollte die Client-Konfiguration hochskalieren, bis die Grenze erreicht ist
    • Die Zahlen in diesem Artikel sind schwer zu glauben. Ich erreiche selbst mit netzwerkbasiertem MySQL deutlich höhere TPS
  • Der Kernpunkt ist, Netzwerklatenz als Bottleneck zu erkennen
    Bei vielen Workloads ist eine gewöhnliche lokale DB schneller als eine hervorragende Remote-DB
    Entscheidend ist nicht „Welche DB ist die beste?“, sondern „Muss man überhaupt eine Netzwerkgrenze überschreiten?

    • (Autor) Genau. Es ging nicht um eine SQLite-vs-Postgres-Debatte, sondern um die Grenzen netzwerkbasierter DBs
    • Natürlich lässt sich Performance leicht steigern, wenn man alles im Speicher hält und Redis oder Memcache verwendet. Aber dann gelten andere Regeln
  • Netzwerkbasierte DBs haben den Vorteil, dass sich Apps leicht neu deployen lassen
    Man startet eine neue Instanz und beendet die bestehende, wodurch nahezu Deployment ohne Downtime möglich ist
    Wenn SQLite auf derselben Instanz liegt, muss beim Austausch auch die DB neu hochgefahren werden, was komplexer ist. Mich würde interessieren, ob du in der Praxis solche Probleme hattest

    • Für SQLite in Production braucht man persistenten Storage und NVMe. Üblicherweise betreibt man es auf einem Bare-Metal-Single-Server
      Bei Migrationen kann Downtime entstehen. Dank Litestream sind Replikation und Backups inzwischen einfacher geworden
    • SQLite unterstützt Multi-Process-Zugriff, daher ist auch ein Austausch ohne Downtime möglich, indem man einen neuen Prozess startet und den alten beendet
  • Der Autor hat PRAGMA synchronous="normal" gesetzt, das heißt, es wird nicht bei jedem Mal fsync ausgeführt
    Für einen fairen Vergleich sollte auf "full" gesetzt werden

    • Im WAL-Modus ist "normal" aber auch okay. Bei Stromausfall verliert man die Durability, aber die Konsistenz der Transaktion bleibt erhalten
  • Ich frage mich, wie eine HA-(Hochverfügbarkeits-)Konfiguration für SQLite aussieht
    Es sollte zumindest automatische Failover-Fähigkeit geben

    • SQLite ist eine C-Bibliothek und lässt sich daher mit Projekten wie rqlite, litestream und litefs erweitern
      Ich überlege derzeit zwischen Postgres und SQLite (inklusive litestream).
      Meine App toleriert etwas Downtime, daher ist vertikales Skalieren auf einer Single-Box einfacher und günstiger
    • Kürzlich ist das Multimaster-Projekt Marmot nach zwei Jahren wiederbelebt worden.
      Im Marmot-GitHub wurde ein neuer gossip-basierter Replikationsmechanismus ergänzt
  • Ich frage mich, ob es reale Fälle gibt, in denen SQLite in Production bis an die Grenze ausgereizt wurde

  • Mich würde interessieren, wo in typischen Webapp- oder Commerce-Umgebungen ungefähr die Grenze bei der Nutzerzahl zwischen SQLite und Postgres liegt
    SQLite erlaubt seit neueren Updates gleichzeitige Lesezugriffe, aber weiterhin nur einen einzelnen Writer
    In welchen Fällen das problematisch wird und ob man mit Blick auf spätere Skalierung besser gleich mit Postgres startet, dazu würde ich gern Meinungen hören