- 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
Wenn man keinen externen Server nutzt und nur in einer lokalen Umgebung arbeitet, muss man dann wirklich die Steuer namens Netzwerk zahlen? (
VFSvs. Socket)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
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
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
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
Mit 64 Verbindungen könnte der Durchsatz um das 8-Fache steigen. Man sollte die Client-Konfiguration hochskalieren, bis die Grenze erreicht ist
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?“
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
Bei Migrationen kann Downtime entstehen. Dank Litestream sind Replikation und Backups inzwischen einfacher geworden
Der Autor hat
PRAGMA synchronous="normal"gesetzt, das heißt, es wird nicht bei jedem Mal fsync ausgeführtFür einen fairen Vergleich sollte auf
"full"gesetzt werden"normal"aber auch okay. Bei Stromausfall verliert man die Durability, aber die Konsistenz der Transaktion bleibt erhaltenIch frage mich, wie eine HA-(Hochverfügbarkeits-)Konfiguration für SQLite aussieht
Es sollte zumindest automatische Failover-Fähigkeit geben
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
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