30 Punkte von darjeeling 20 일 전 | 4 Kommentare | Auf WhatsApp teilen

ultrathink.art ist ein E-Commerce-Onlineshop, der autonom von AI-Agenten betrieben wird. Vom Produktdesign über die Bestellabwicklung bis zum Schreiben von Blogbeiträgen übernimmt alles die AI. Dieser Artikel schildert Erfahrungen aus dem Betrieb dieses Shops mit SQLite in einer Produktionsumgebung, in der sogar echte Stripe-Zahlungen verarbeitet werden.


Aufbau: 4 Dateien, 1 Volume

In der Produktionsumgebung werden insgesamt vier SQLite-Datenbanken betrieben: primary (Bestellungen, Produkte, Nutzer), cache (Rails-Cache), queue (Background Jobs) und cable (Action Cable). Alle werden in einem einzigen Docker-Volume gespeichert.

Rails 8 hat SQLite zur erstklassigen Option gemacht, und tatsächlich profitiert man von Vorteilen wie vereinfachten Deployments, keinem Bedarf an Connection-Pool-Management und keinem separaten DB-Server.


Wie der WAL-Modus Parallelität ermöglicht

Der Standard-Journal-Modus von SQLite sperrt beim Schreiben die gesamte DB und ist daher für Web-Apps mit vielen gleichzeitigen Requests ungeeignet. Im WAL-Modus (Write-Ahead Logging) werden Schreibvorgänge an eine separate -wal-Datei angehängt, während Lesezugriffe weiter die Hauptdatei verwenden. Dadurch sind viele Lesezugriffe und ein einzelner Schreibvorgang gleichzeitig möglich. Rails 8 aktiviert den WAL-Modus für SQLite standardmäßig.


Der Vorfall: Zwei Bestellungen verschwanden

Am 4. Februar wurden innerhalb von zwei Stunden 11 Commits auf main gepusht. Mit jedem Push lief Kamals Blue-Green-Deployment an, wodurch es zu einer Überlappungsphase kam, in der der bestehende Container und der neue Container gleichzeitig dieselbe WAL-Datei geöffnet hatten. Mit 11 überlappenden Deployments entstand die Situation, dass Container A gerade drainte, während B startete, und noch bevor B vollständig bereit war, begann bereits das Deployment von C.

Die Bestellungen Nr. 16 und 17 wurden in Stripe erfolgreich bezahlt, und das Geld wurde auch vom Konto der Kunden abgebucht, aber in der DB blieb kein Datensatz zurück. Ein Blick in sqlite_sequence zeigte, dass der Autoincrement-Zähler auf 17 stand, obwohl tatsächlich nur 15 Zeilen vorhanden waren.


Die Lösung: Verlangsamt eure Deployments

Die Lösung war nicht technischer, sondern prozessualer Natur. Relevante Änderungen wurden für ein gemeinsames Deployment gebündelt, und eine Regel zur Vermeidung schneller aufeinanderfolgender Pushes wurde in einer Governance-Datei für die AI-Agenten (CLAUDE.md) festgehalten.

Das ist kein SQLite-Problem, sondern ein Problem der Deployment-Pipeline. PostgreSQL verbindet sich über TCP-Sockets, daher verbinden sich auch neue Container mit demselben DB-Server, und die DB-Engine verwaltet die Reihenfolge der Schreibvorgänge. SQLite dagegen verlässt sich auf Dateisystem-Sperren in einem gemeinsamen Docker-Volume — und wenn sich Container überlappen, bricht das auseinander.


sqlite_sequence als forensisches Werkzeug nutzen

Die Tabelle sqlite_sequence ist eines der am meisten unterschätzten Debugging-Werkzeuge in SQLite. Sie merkt sich den höchsten jemals vergebenen Autoincrement-Wert, auch wenn die betreffende Zeile später gelöscht wurde. Wenn die aktuelle Zeilenzahl und der Sequenzwert unerwartet stark auseinanderliegen, ist das ein Signal dafür, dass irgendwo Zeilen irrtümlich gelöscht wurden.


Fallstricke, von denen einem niemand erzählt

ILIKE, das PostgreSQL-Entwickler gewohnheitsmäßig verwenden, führt in SQLite zu einem Syntaxfehler. Stattdessen muss man LOWER(name) LIKE nutzen. json_extract gibt einen Integer zurück, wenn der Wert numerisch gespeichert wurde, und Vergleiche mit Strings schlagen dann stillschweigend fehl. kamal app exec erzeugt jedes Mal einen neuen Container; wenn man das auf einem Server mit 2 GB RAM zweimal gleichzeitig ausführt, beendet der OOM-Killer den Web-Prozess.


Würde ich wieder SQLite wählen?

Ja. Für einen einzelnen Server mit moderater Schreiblast beseitigt SQLite die gesamte Infrastrukturkomplexität. Auch Backups sind mit dem sqlite3-Befehl .backup ausreichend einfach (und gehen sicher mit WAL-Modus und gleichzeitigen Schreibvorgängen um). Wenn irgendwann horizontale Skalierung oder echte Multi-Writer-Parallelität nötig werden, kann man dann auf PostgreSQL migrieren. Rails macht diesen Wechsel unkompliziert.


Original: ultrathink.art Blog, 2026.04.03

4 Kommentare

 
click 18 일 전

Selbst wenn es Probleme mit der Infrastrukturkomplexität gibt: Ist es bei einem Shop, wo viele gleichzeitige Schreibvorgänge nötig sind, nicht von Anfang an die bessere Wahl, kein SQLite zu verwenden?
Wenn das Ganze sogar mit Docker aufgebaut war, wäre die Infrastrukturkomplexität einer PostgreSQL-Konfiguration doch auch nicht besonders viel höher gewesen.
Ich habe den Eindruck, dass es dadurch, dass es mit Rails gebaut wurde und das Ökosystem auf die Nutzung von SQLite ausgerichtet ist, in diese Richtung gelenkt wurde.
Es ist ein schwerwiegender Fehler wie verlorene Bestellungen aufgetreten, und selbst wenn es bei pg ebenfalls Probleme mit gleichzeitigen Schreibvorgängen geben sollte: Ist es bei einem Shop, wo viele gleichzeitige Schreibvorgänge nötig sind, nicht von Anfang an die bessere Wahl, kein SQLite zu verwenden?
Ich denke ebenfalls, dass es dadurch, dass es mit Rails gebaut wurde und das Ökosystem auf die Nutzung von SQLite ausgerichtet ist, als gute Lösung dargestellt wurde.
Es ist ein schwerwiegender Fehler wie verlorene Bestellungen aufgetreten, und die grundlegende Lösung dafür ist die Verwendung einer für gleichzeitige Schreibvorgänge geeigneten DB wie pg.
Weil man SQLite technisch bevorzugt, darauf zu beharren, es weiter zu verwenden, klingt für mich nach einer Aussage, die meine Vertrauenswürdigkeit in ihn als Ingenieur sinken lässt.
Es wirkt auf mich wie die umgekehrte Version von lebenslaufgetriebener Entwicklung, bei der man unnötig k8s aufsetzt, eine HPA-Konfiguration mit Replica 1 baut und einen gut funktionierenden Monolithen in eine MSA umwandelt.

 
okxrr 18 일 전

Das Problem lag an einer falsch konfigurierten Volume-Einstellung beim Starten im Container, nicht daran, dass gleichzeitige Schreibzugriffe nicht möglich wären. Wenn Sie den Artikel noch einmal lesen, steht dort, dass das Problem mit gleichzeitigen Schreibzugriffen durch busy timeout ausreichend abgefangen werden kann. Das Volume-Setup lässt sich
mit ipc=host lösen.

Postgres muss letztlich betrieben werden, während man bei SQLite einfach nur das App-Binary überall verteilen muss.

Weil sich so viele Betriebserfahrungen und Fehlschläge angesammelt haben, beginnt SQLite nun populär zu werden, und
gleichzeitige Schreibzugriffe sind, wie auch im Artikel klar gesagt wird, überhaupt kein Problem.

 
darjeeling 18 일 전

Eigentlich muss man nur verschiedene Einstellungen vornehmen, aber am Ende braucht man eben Erfahrung, hehe. Jedenfalls mag ich solche Artikel.

 
darjeeling 18 일 전

Genau, gerade deshalb wäre es schön, wenn solche Beiträge gelegentlich auftauchen würden.