Fundierte Artikel über Technologien, die das Kommende formen.

Verteiltes SQLite: 5 Mythen nach 12 Jahren im Test

Nach zwölf Jahren mit verteiltem SQLite (Raft, Litestream, Turso, D1) prüfen wir fünf Annahmen zu Konsistenz, Replikation und dem Einsatz von Postgres.

Ein Ring aus kleinen Aktenschränken, verbunden durch Lichtfäden, einer leuchtet als Leader
Zwölf Jahre verteiltes SQLite: viele Designs, ein Kerngedanke, der immer wieder neu erfunden wird.

Verteiltes SQLite hat sich von einem Wochenendprojekt einer einzelnen Person zu einer risikokapitalfinanzierten Produktkategorie entwickelt, ohne dass die meisten Entwickler das dazwischenliegende Jahrzehnt bemerkt haben. Im September 2014 setzte Philip O'Toole einen Raft-Log vor SQLite, repliziert jeden Schreibvorgang über einen Cluster und stellte den Code als rqlite auf GitHub. Zwölf Jahre später gibt es ein ganzes Ökosystem (dqlite, Litestream, LiteFS, cr-sqlite, libSQL/Turso, Cloudflare D1), und vieles von dem, was die Leute darüber glauben, ist auf interessante Weise falsch.

Ich habe geclusterte Datenbanken im Produktivbetrieb laufen lassen und die Scherben ihrer Ausfallmodi aufgeräumt, also erspare ich dir ein paar Schmerzen: Die Diskussionen über verteiltes SQLite drehen sich fast nie um SQLite. Es geht darum, welche Frage man eigentlich stellt: Was passiert, wenn die Maschine ausfällt? Was passiert, wenn die Maschine ausfällt und ich nicht warten kann? Oder was passiert, wenn es gar keine Maschine gibt? Hier sind fünf Mythen, getestet anhand von zwölf Jahren Geschichte.

Mythos 1: Verteiltes SQLite ist eine neue Idee

Die aktuelle Welle an Texten über replizierte SQLite-Datenbanken beginnt ungefähr 2021, als Litestream und die Bewegung „SQLite reicht“ laut wurden, und Turso und Cloudflare D1 mit Finanzierung und Launch-Beiträgen auf den Plan traten. So setzte sich die Annahme durch, dass irgendjemand Schlaues kürzlich bemerkt habe, SQLite lasse sich replizieren. Tatsächlich wurde das erste Raft-über-SQLite-System schon 2014 veröffentlicht, und es war nicht einmal die erste Replikation von SQLite. Expensify hatte das intern jahrelang mit einem Paxos-artigen Protokoll gemacht, bevor 2016 Bedrock veröffentlicht wurde, und ActorDB gab in etwa derselben Zeit jedem Erlang-Actor seine eigene Raft-replizierte SQLite-Datenbank.

Warum ist das über die Anerkennung hinaus wichtig? Weil ein Design, das zwölf Jahre unabhängiger Neuerfindung übersteht, etwas aussagt. Mindestens acht verschiedene Teams mit unterschiedlichen Anforderungen und unterschiedlichen Protokollen haben sich das Problem angesehen und dieselbe Architektur gewählt: SQLite als Speicher-Engine, Replikation außen angeflanscht. Niemand fand einen Grund aufzuhören. Wenn du dieses Muster siehst, konvergente Neuimplementierung über ein Jahrzehnt, betrachtest du eine wirklich gute Idee und keinen Hype-Zyklus. Vergleich das mit Kategorien, die aufflammen und wieder verschwinden; diese hier hat sich kontinuierlich aufgebaut.

Es ist auch wichtig, weil die alten Designentscheidungen immer noch tragend sind. rqlite hat sich 2014 für ein CP-System entschieden: Schreibvorgänge laufen über den Leader, eine Mehrheit muss bestätigen, und eine abgetrennte Minderheit verweigert Schreibzugriffe. Davon hat es nie abgerückt. Diese eine Wahl hat die HTTP-API, die Lesekonsistenzstufen und das Snapshot-Verhalten bestimmt. Teams, die Konsistenz als Konfigurationsschalter behandeln, der später entschieden wird, bereuen es meist. Die Lücke zwischen der Spezifikation, die man impliziert hat, und der, die man tatsächlich implementiert hat, ist genau der Nährboden für verteilte Bugs.

Mythos 2: Raft auf SQLite ergibt ein günstiges verteiltes Postgres

Dies ist der teuerste Mythos auf der Liste. Die Versprechung klingt verlockend: Man nehme die meistverbreitete Datenbank der Welt, füge Konsens hinzu und spare sich den operativen Aufwand eines Client-Server-Systems. Was man tatsächlich bekommt, ist eine Datenbank mit einem einzigen Schreiber, bei der jeder Schreibvorgang einen Netzwerk-Roundtrip zu einer Mehrheit der Knoten bezahlt und bei der die SQL-Oberfläche von SQLite (keine gespeicherten Prozeduren, ein schlankes Typsystem, begrenzte Nebenläufigkeitsprimitive) trotzdem genau die von SQLite bleibt.

Der Schreibpfad ist die ehrliche Rechnung. Ein lokaler SQLite-Commit kostet ein paar fsyncs. Ein Raft-replizierter Commit läuft so ab: Der Leader hängt den Eintrag an sein Log an, schickt ihn an die Follower, wartet, bis eine Mehrheit ihn persistiert hat, wendet ihn auf SQLite an und antwortet dann. In einem gesunden LAN sind das vielleicht ein bis zwei Millisekunden statt einiger zehn Mikrosekunden. Über Regionen hinweg ist es die Lichtgeschwindigkeit plus Festplatte. Für leselastige Workloads mit moderaten Schreibraten, etwa Konfigurationsspeicher, Metadaten für Service Discovery oder kleine Control Planes, ist das in Ordnung, und genau für diese Nische wurde rqlite gebaut. Seine Entstehungsgeschichte: Ein geclustertes System brauchte etwas relationalen Speicher, und es gab keine gute Antwort darauf, welcher Knoten ihn halten sollte. Für einen heißen OLTP-Workload ist es das falsche Werkzeug.

Und dann ist da die operative Realität, die niemand in die README schreibt: Leader-Wahl ist ein Fehlermodus, den du jetzt besitzt. Split-Brain wird durch Mehrheitsabstimmung verhindert, ja, aber das heißt, dass ein Drei-Knoten-Cluster, der zwei Knoten verliert, alle Schreibzugriffe verweigert. Du hast „die Datenbank ist down, weil der Server gestorben ist“ gegen „die Datenbank ist down, weil das Quorum gestorben ist“ getauscht, und Letzteres ist deinem Bereitschaftsdienst um 3 Uhr morgens viel schwerer zu erklären. Wenn du noch nicht darüber nachgedacht hast, dein gewähltes System mit Jepsen-artigen Tests zu prüfen, solltest du das tun. Ein Kommentator auf Hacker News stellte die pointierte Frage, ob rqlite jemals mit Jepsen getestet wurde, denn Systeme, die das nicht wurden, haben meist Überraschungen auf Lager. Ein erfahrener Ingenieur in derselben Diskussion merkte unterdessen an, dass Postgres selbst mit moderner Tooling inzwischen so einfach zu betreiben ist, dass „nimm einfach einen Datenbankserver“ wieder ein legitimer Standard ist. Er hat nicht unrecht.

Eine Waage, auf der ein einfacher Block gegen ein Gewirr aus Ketten und Zahnrädern abgewogen wird
Konsens kauft dir Failover; bezahlt wird bei jedem einzelnen Schreibvorgang.

Mythos 3: Replikation bedeutet Konsens

Wenn die zwölfjährige Geschichte eine einzige Erkenntnis liefert, dann diese: „Replizierte SQLite“ ist nicht ein Design, sondern drei Ecken eines Designraums, und sie tauchten in einer aufschlussreichen Reihenfolge auf. Sie zu verwechseln ist der Grund, warum Teams einen Raft-Cluster betreiben, um ein Backup-Problem zu lösen.

  • Konsens (CP). rqlite, dqlite. Ein Leader, starke Konsistenz, ein echter Cluster. Beantwortet: „Was passiert, wenn die Maschine ausfällt und ich mit der Wiederherstellung nicht warten kann?“ Preis: Schreiblatenz und Quorum-Operationen.
  • Streaming (ein Schreiber). Litestream, später LiteFS. Kein Konsens überhaupt: Das SQLite-WAL wird in Object Storage gestreamt, was nahezu kontinuierliche Notfallwiederherstellung für einen einzelnen Knoten ermöglicht. Beantwortet: „Was passiert, wenn die Maschine ausfällt?“ Preis: ein kleines Fenster möglichen Datenverlusts und eine Wiederherstellungszeit von Sekunden bis Minuten.
  • Merge (AP). cr-sqlite und die Local-First-Bewegung. CRDTs als SQLite-Erweiterung, viele Schreiber, die ohne Leader auseinanderlaufen und wieder zusammengeführt werden. Beantwortet: „Was passiert, wenn es überhaupt keine zentrale Maschine gibt?“ Preis: eventuelle Konsistenz und Merge-Semantik, die du bewusst entwerfen musst.

Ben Johnsons Argumentation für Litestream verdient Respekt, weil sie für die meisten Anwendungen richtig war: Sie laufen auf einer Maschine und brauchen einen Weg zur Wiederherstellung, wenn diese ausfällt, nicht einen Cluster. Die erste Frage, „Was passiert, wenn die Maschine ausfällt“, ist weit häufiger als die zweite, „und ich kann nicht warten, bis es wiederhergestellt ist“. Ich habe Teams gesehen, die einen Drei-Knoten-Raft-Cluster deployt haben, um eine Datenbank zu schützen, deren RTO-Anforderung „irgendwann vor dem Mittagessen“ lautete. Das ist keine Zuverlässigkeitstechnik, das ist Cargo-Kult. Zum schönen Schluss: Litestreams WAL-Streaming-Ansatz hat letztlich beeinflusst, wie rqlite selbst das WAL in Version 8 behandelt. Der Streaming-Zweig hat auf den Konsens-Zweig zurückgewirkt.

Schreib deshalb, bevor du ein Werkzeug wählst, die Frage auf. Wortwörtlich: „Wenn Knoten X ausfällt, müssen Schreibvorgänge innerhalb von N Sekunden wieder laufen, und wir akzeptieren den Verlust der letzten M Sekunden bestätigter Daten.“ Wenn M einige Sekunden und N eine Minute beträgt, ist Litestream-zu-S3 deine Antwort, und es wird dich nie wegen eines Quorums aus dem Schlaf klingeln. Wenn M null und N Sekunden sind, bist du im Konsens-Terrain und zahlst bei jedem Schreibvorgang dafür. Das ist dieselbe Disziplin wie bei jedem Threat Model: Benenne den Fehler, bevor du die Abwehr kaufst, so wie du dich Systemen nähern solltest, die Katastrophen widerstehen, indem du vorab entscheidest, gegen welche Katastrophe du dich wappnest.

Mythos 4: Verwaltetes „SQLite am Edge“ beseitigt die Zielkonflikte

Das Geld in dieser Kategorie ist in die Plattformen geflossen: Turso sammelte Risikokapital ein und wurde von Supabase übernommen, Cloudflare hat D1 in sein Edge-Netzwerk eingebaut. Die Versprechung lautet, dass sich jetzt jemand anderes um die schwierigen verteilten Teile kümmert. Manchmal stimmt das. Manchmal ist es Marketing, und dann kommt das schmutzige Geheimnis ans Licht: Ein Cloudflare-Entwickler hat öffentlich zugegeben, dass D1 im Grunde ein Wrapper um ein einzelnes Durable Object ist, das seine SQLite-Datenbank freigibt, und dass man oft besser direkt den Durable-Objects-Speicher nutzt. Das ist keine verteilte Datenbank im Konsenssinn, sondern eine einzelne SQLite-Datei mit einer Plattform drumherum. Nutzer, die D1 produktiv betreiben, berichten entsprechende Zuverlässigkeit.

Das heißt nicht, dass die verwalteten Optionen schlecht sind. Es heißt, dass die Zielkonflikte nicht verschwunden sind, sondern hinter eine API verlagert wurden, in die du nicht hineinsehen kannst. Wenn du rqlite selbst betreibst, weißt du genau, was CP bedeutet: drei Knoten, ein Leader, Mehrheitsbestätigungen, eine abgetrennte Minderheit verweigert Schreibzugriffe. Wenn du „SQLite am Edge“ kaufst, musst du dieselben Fragen an eine Blackbox stellen: Wo ist der Schreiber, was passiert bei einer Partition, und wie groß ist mein tatsächliches Haltbarkeitsfenster? Wenn die Antwort des Anbieters vage ist, wird dein Incident-Review nicht besser ausfallen.

Replikation als Dienst löscht die Konsistenzfrage nicht. Sie verlagert die Antwort nur in die Infrastruktur von jemand anderem, wo du sie nicht prüfen kannst.

Das historische Muster lohnt sich zu verinnerlichen: Die Open-Source-Systeme (rqlite, dqlite, Litestream) haben acht Jahre lang an der Idee gearbeitet, bevor sie jemand verkauft hat, und das Kapital floss zu denjenigen, die die Idee in einen Dienst mit Rechnung verwandelt haben. Das ist das übliche Schicksal von Infrastruktur. Kaufen ist oft die richtige Entscheidung, aber kauf mit offenen Augen, welche der drei Ecken du eigentlich bekommst.

Eine Schneekugel mit einer warmen Hütte im Inneren, die in einem nebligen Feld voller Serverracks steht
Verwaltetes Edge-SQLite: eine gemütliche Einzeldatenbank, verpackt in eine Plattform, in die man nicht hineinsehen kann.

Mythos 5: Starke Konsistenz ist ein kostenloses Upgrade gegenüber Backups

Die Wahl wird oft als Backups (schwach, billig) gegen Konsens (stark, teuer, aber strikt besser) dargestellt. Das stimmt nicht. Starke Konsistenz über Raft ist ein anderes Produkt, keine bessere Version desselben Produkts, und sie erhebt laufende Abgaben, die Backups nie verlangen.

Abgabe eins ist der Durchsatz. Ein Praktiker, der beide genutzt hat, merkte an, dass rqlite die Garantie „an mehr als einem Ort gespeichert“ bietet, aber deutlich langsamer ist als eine Ein-Knoten-Datenbank. Das ist der Konsens-Roundtrip, und der verschwindet nie. Abgabe zwei ist subtile Semantik. Wenn du Replikation über das Verschicken von SQL-Anweisungen umsetzt, erbst du Determinismusprobleme: Funktionen wie random() oder datetime('now') können zwischen Knoten auseinanderlaufen, sofern die Schicht sie nicht umschreibt. Deshalb unterstützen manche Implementierungen nur eine Teilmenge des SQLite-SQL. Repliziert man stattdessen auf WAL-Ebene, umgeht man das, koppelt sich aber an das On-Disk-Format und das Seitenlayout von SQLite. Es gibt keine Platzierung dieser Schicht ohne Kompromiss. Abgabe drei: Verteilte Transaktionen sind wirklich schwer. Ein Ingenieur beschrieb, wie sich sein Design entwickelte: Jede Anweisung in einer Transaktion war ein eigener Raft-Befehl, korrekt-ish, aber langsam. Später führte der initiierende Knoten die gesamte Transaktion lokal aus, rollte sie zurück und schickte einen einzigen Commit-Befehl mit dem ganzen Batch. Das ist die Art hart erkaufter Lektion, die man nur beim Betrieb solcher Systeme lernt.

Hier eine minimale Illustration des Unterschieds im Schreibpfad, anhand der HTTP-API von rqlite:

# Local SQLite: one process, one fsync, microseconds.
sqlite3 app.db "INSERT INTO kv VALUES ('flag', 'on')"
# rqlite: this HTTP call hides a Raft round trip —
# leader log append, replication to a majority, then apply.
curl -XPOST 'localhost:4001/db/execute?pretty' \
-H 'Content-Type: application/json' \
-d '["INSERT INTO kv VALUES (?, ?)", "flag", "on"]'
# The guarantee changed: the write now survives the loss
# of any single node. So did the latency budget.

Und beachte, was du nicht bekommst: Lese-Skalierung. Raft repliziert das Log, damit Follower übernehmen können. Es macht Follower aber nicht standardmäßig zu stark konsistenten Lesequellen (rqlite bietet Lesekonsistenzstufen genau deshalb an, weil dies ein Zielkonflikt ist). Wenn du wegen „verteilt“ auf mehr Lesekapazität gehofft hast, wolltest du Read Replicas, nicht Konsens.

Was zwölf Jahre Raft auf SQLite tatsächlich lehren

Die Geschichte dieser Kategorie ist ungewöhnlich klare Evidenz dafür, wie Infrastrukturideen reifen. Zuerst baut jemand das prinzipientreue Ding für ein konkretes Bedürfnis (rqlite, aus einem echten Clustering-Problem bei einem echten Job). Dann bauen andere es für ihre Paketierungsanforderungen neu: Canonicals dqlite ist eine Bibliothek, die man einbettet, weil Canonical eine Datenbank innerhalb seiner Produkte brauchte, während rqlite ein Server ist, den man neben jedem beliebigen Produkt betreibt. Dann argumentiert jemand, dass der Regelfall kleiner ist als die prinzipientreue Lösung annimmt (Litestream), wird bestätigt, und das ganze Feld justiert sich neu. Schließlich verpacken die Plattformen es und verkaufen es. Wenn du lange genug in der Infrastruktur arbeitest, wirst du diesen Film noch einmal sehen. LLM-Agenten lernen gerade auf dieselbe Weise die Lektionen verteilter Systeme neu.

Meine unverblümte Empfehlung nach alledem: Fang mit einem Ein-Knoten-SQLite plus WAL-Streaming in Object Storage an. Es ist erstaunlich, wie weit man damit kommt, und deine Ausfallgeschichte ist eine Wiederherstellung, kein Postmortem zum Konsens. Steig auf ein Raft-basiertes System erst um, wenn du in einem Satz die Verfügbarkeitsanforderung formulieren kannst, die Streaming nicht erfüllt, und akzeptiere die Rechnung aus Schreiblatenz und Quorum-Operationen, die dazugehört. Greif zu Postgres, sobald du seine SQL-Oberfläche oder Multi-Writer-Durchsatz brauchst. Und wenn nichts davon passt, hat auch die breitere Welt eingebettbarer Datenbanken Optionen, von eingebettbaren Graphdatenbanken bis zur Local-First-CRDT-Ecke.

Die Zusammenfassung zum Mythen-Check: Verteiltes SQLite ist nicht neu, kein günstiges Postgres, nicht ein Design, nicht durch einen verwalteten Wrapper sicher und kein kostenloses Upgrade gegenüber Backups. Es ist ein zwölf Jahre altes, gut kartiertes Designfeld mit drei Ecken, von denen jede für eine andere Frage richtig ist. Die Ingenieure, die sich die Finger verbrennen, sind diejenigen, die die Frage nie aufgeschrieben haben.