Devlog·KW 25·15.–21. Juni 2026

SQLite gemessen, bei Postgres geblieben

Die Frage war: Könnten kleine Dienste auf eingebettetes SQLite umziehen und so Ressourcen auf dem Mac Mini sparen? Wir haben es nicht geschätzt, sondern gebaut, gemessen und gegen die Produktion gespiegelt — und uns danach bewusst gegen den Wechsel entschieden.

Worum es geht

Der ganze Verein läuft 24/7 auf einem Mac Mini (16 GB RAM) plus einer GPU-Box. Auf einem Gerät dieser Größe ist jedes Hundert-Megabyte ein echtes Argument. Also die naheliegende Frage: Unsere Datenbank-Architektur gibt jedem Dienst seine eigene Postgres-Datenbank — aber die ganz kleinen, lese-lastigen Dienste (der Link-Resolver, die Statistik-Schnappschüsse) brauchen die volle Maschinerie eigentlich nicht. Könnten die auf eingebettetes SQLite umziehen — eine Datei statt eines Datenbank-Anschlusses — und so dem Mini Luft verschaffen? Und das viel gehypte „Turso“ (libSQL) — bringt das etwas?

Wir hätten das überschlagen können. Stattdessen haben wir es gebaut und gemessen, weil eine Datenbank-Engine zu den Entscheidungen gehört, die man besser nicht aus dem Bauch trifft.

Was wir gebaut haben

Als Pilot diente mana-links, der Cross-App-Link-Resolver — der sauberste Kandidat: zwei Tabellen, fast nur Lese-Zugriffe, keine exotischen Postgres-Eigenheiten. Die Datenschicht wurde hinter eine neutrale Schnittstelle gelegt, sodass derselbe Dienst unverändert gegen drei Backends laufen kann, nur über eine Umgebungsvariable umschaltbar:

  • Postgres (wie bisher),
  • eingebettetes SQLite (Bun-intern, null externe Abhängigkeit),
  • libSQL/Turso (der Sync-fähige SQLite-Abkömmling).

Dazu zwei Mess-Werkzeuge: ein Mikro-Benchmark, der jede Engine isoliert (jedes Backend im eigenen Prozess, Speicher von außen gemessen), und ein Shadow-Read-Vergleich — zwei echte Instanzen auf identischen Daten, derselbe Anfrage-Verkehr gegen beide gespiegelt, Antworten byte-genau verglichen.

Was die Zahlen sagen

Eingebettetes SQLite ist lokal durchweg schneller als Postgres:

Operation Postgres SQLite
voller Lese-Scan 0,835 ms 0,261 ms 3,2×
Punkt-Lookup 0,100 ms 0,037 ms 2,7×
Schreiben (Insert) 0,116 ms 0,070 ms 1,7×
paralleler Lese-Durchsatz 1.636/s 3.664/s 2,2×
Startzeit bis bereit 53 ms 23 ms 2,3×

Und der Shadow-Vergleich auf prod-nahen Daten: 3.600 von 3.600 Antworten byte-genau identisch, 100 % Parität. Die Engine-Umstellung verändert das Verhalten kein bisschen — der eigentlich wichtige Beweis.

Aber genau dieser Vergleich brachte die nüchterne Einordnung: Der Geschwindigkeitsvorteil zeigt sich nur bei ungecachten Datenbank- Zugriffen. Der heiße Pfad des Resolvers liegt hinter einem 5-Minuten-Cache — und ist damit engine-egal. Im Alltag würde mana-links den SQLite-Vorsprung kaum spüren.

Die ehrliche Überraschung bei Turso/libSQL

libSQL — die Technik hinter Turso — war lokal langsamer als das schlichte eingebettete SQLite, bei Schreibzugriffen sogar langsamer als Postgres. Sein JS-Client trägt Overhead, weil er für etwas anderes gebaut ist: Replikation und Sync über Standorte hinweg, nicht für rohen lokalen Durchsatz. Klare Erkenntnis: libSQL lohnt sich nur, wenn man die Sync-Fähigkeit wirklich braucht (etwa ein Lese-Standby an einem zweiten Schweizer Standort) — nie wegen lokaler Performance. Wer keinen Sync braucht, fährt mit dem eingebauten SQLite strikt besser.

Was es auf dem Host wirklich spart

Hier wurde die Antwort am ehrlichsten — und am unspektakulärsten. Der Ruhe-Speicher eines Dienstes ändert sich kaum (62 statt 67 MB; beides dominiert von der Laufzeit, nicht von der Datenbank). Die echte Ersparnis sitzt bei Postgres selbst: Jede offene Verbindung ist dort ein eigener Prozess. Vier kleine Dienste umzustellen würde rund 20 Verbindungen einsparen — grob 100 bis 200 MB auf dem Mini.

Spürbar, aber kein Befreiungsschlag. Und entscheidend: der zentrale Postgres-Container bliebe — er bedient rund fünfzig Datenbanken; vier herauszulösen entfernt ihn nicht. Wer nur RAM frei machen will, hat einen billigeren Hebel: die Verbindungs-Pools der kleinen Dienste schlicht kleiner stellen — ohne den Stack zu spalten.

Die Entscheidung: bei Postgres bleiben

Wir bleiben bei Postgres als Norm. Die Gründe, in der Reihenfolge ihres Gewichts:

  1. Performance ist nicht das Argument. Der Cache deckt den heißen Pfad ab; der Messvorteil träfe den Alltag kaum.
  2. Der einzige echte Gewinn wäre Betrieb (eine Abhängigkeit weniger, schnellerer Start, eine Datei statt eines pg_dump fürs Backup) — und Betriebsqualität lässt sich nicht benchmarken, nur erleben.
  3. Der Preis wäre ein gespaltener Stack. „Postgres für groß, SQLite für klein“ heißt zwei Backup-Wege, zwei Denkmodelle, und für die SQLite-Dienste der Verlust unserer zentralen Postgres-Werkzeuge — Introspektion, Monitoring-Dashboards, das eine nächtliche Backup über alle Datenbanken. Das widerspricht unserem Grundsatz, den Stack einheitlich und ersetzbar zu halten.

Kurz: Wir wechseln nicht aus Neugier auf das Neue. Bewährte, langweilige Technik schlägt das Spannende, solange sie trägt — und Postgres trägt. Das Messen war kein verschwendeter Aufwand: Es ist der Unterschied zwischen „wir glauben, Postgres ist richtig“ und „wir wissen es, mit Zahlen, und können sagen, wann sich das ändern würde“.

Was offen bleibt

  • Der Pilot-Code ist fertig und liegt bereit. Sollte der Mini je unter echten Speicherdruck geraten — oder sollte uns das Backup-Argument (eine Datei lässt sich trivial an einen zweiten Standort spiegeln, was unsere größte offene Infrastruktur-Lücke ist) wichtiger werden —, ist der Weg einen begründeten Praxis-Test wert, an genau einem Dienst.
  • libSQL bleibt verdrahtet, aber bewusst aus: als möglicher Pfad zu einem Lese-Standby am geplanten zweiten Schweizer Standort, nicht als Performance-Spielerei.
  • Schreib-lastige Dienste und Job-Warteschlangen waren ohnehin nie Kandidaten — SQLite kann nur einen Schreiber gleichzeitig.

Wer mitdenken will: [email protected].

Veröffentlicht
20. Juni 2026
Repos
mana
Tags
infrastrukturdatenbanksqlitebenchmarkentscheidung