Artikel

Von Postgres nach SQLite — der unpopuläre Umzug

Fast alle Wege führen von SQLite zu Postgres. Ich bin den umgekehrten gegangen, aus Gründen, die mit Last nichts zu tun haben — und würde es wieder tun.

BlackZackBlackzack

999 Wörter · 5 Min. Lesezeit

  • datenbank
  • architektur
  • yurna

Es gibt in der Softwarewelt eine unausgesprochene Rangordnung von Datenbanken, und SQLite steht darin ganz unten: gut für Handy-Apps, gut zum Ausprobieren, aber „nicht für die Produktion".

Ich habe im März den umgekehrten Weg genommen und mein System von Postgres auf SQLite umgestellt. Anderthalb Jahre nachdem ich zum ersten Mal gemerkt hatte, dass die große Datenbank zu meiner Last nicht passt.

Warum jetzt und nicht damals

2022 war der einzige Gewinn „weniger Betriebsaufwand". Das ist zu wenig für einen Datenbankwechsel, und ich habe die Entscheidung deshalb bewusst liegen lassen.

Was den Ausschlag gegeben hat, waren zwei konkrete Vorgänge, die ich regelmäßig habe:

Umziehen. Ich verschiebe Dienste zwischen Maschinen — Testumgebung, neuer Server, ein Umzug zu einem anderen Anbieter. Mit Postgres ist das jedes Mal: Dienst stoppen, Dump ziehen, übertragen, auf der Gegenseite Datenbank anlegen, Rechte setzen, einspielen, prüfen. Mit einer Datei ist es: Dienst stoppen, Datei kopieren, starten.

Eine echte Kopie zum Ausprobieren. Ich wollte regelmäßig mit den echten Daten testen. Mit einer Datei ist das ein cp.

Beides sind keine Leistungsargumente. Es sind Argumente über den Alltag — und genau die hatte ich vorher nicht ernst genommen.

Was man dabei aufgibt

Ehrlichkeit gehört dazu, weil dieser Wechsel nicht für jeden richtig ist.

Gleichzeitiges Schreiben aus mehreren Prozessen. SQLite erlaubt zu jedem Zeitpunkt genau einen Schreiber. Mit dem Write-Ahead-Log blockieren Leser die Schreiber nicht mehr, aber zwei Schreiber warten aufeinander. Bei meinen drei Prozessen mit überwiegend lesendem Zugriff ist das unkritisch — bei einem schreiblastigen System wäre es das Ende.

Zugriff über das Netzwerk. Es gibt keinen Server. Alle Prozesse müssen dieselbe Datei sehen. Wenn Anwendungen auf verschiedenen Maschinen laufen sollen, ist SQLite raus.

Nutzerverwaltung auf Datenbankebene. Wer die Datei lesen kann, kann alles lesen. Rechte regelt das Dateisystem, nicht die Datenbank.

Ein paar Funktionen. Keine echten Aufzählungstypen, weniger Datentypen, kein direktes Ändern von Spalten in älteren Fassungen.

Die Einstellungen, die den Unterschied machen

SQLite kommt mit einer Voreinstellung, die für den vorsichtigsten Fall gedacht ist. Für einen Serverprozess sind drei Änderungen praktisch Pflicht:

PRAGMA journal_mode = WAL;      -- Leser blockieren Schreiber nicht mehr
PRAGMA synchronous = NORMAL;    -- ein fsync weniger pro Schreibvorgang
PRAGMA busy_timeout = 10000;    -- 10 s auf die Sperre warten statt sofort abzubrechen

Der erste Punkt ist der wichtigste. Ohne ihn sperrt jeder Schreibvorgang die gesamte Datei, und bei parallelen Anfragen bekommt man sofort Fehler.

Der dritte Punkt hat mich zwei Abende gekostet, bevor ich ihn kannte. Ohne Wartezeit meldet SQLite bei einer belegten Sperre sofort einen Fehler statt kurz zu warten — und im Protokoll steht dann eine Meldung über eine „belegte Datenbank", die nach einem ernsten Problem aussieht und keines ist.

Warnung

Das Write-Ahead-Log bedeutet, dass zur Datenbank zwei weitere Dateien gehören. Wer nur die Hauptdatei kopiert, während der Dienst läuft, kopiert einen unvollständigen Stand. Zum Sichern gibt es einen Befehl, der eine konsistente Kopie erzeugt, ohne den Betrieb anzuhalten — den sollte man benutzen statt cp.

Der Umzug selbst

Die Migration lief in vier Schritten und hat einen Abend gedauert:

  1. Schema für SQLite erzeugen. Bei einem schemabasierten Zugriffswerkzeug ist das ein geänderter Anbietereintrag und ein neuer Lauf.
  2. Daten übertragen, Tabelle für Tabelle, in der Reihenfolge der Abhängigkeiten.
  3. Zeilen zählen und vergleichen. Vorher und nachher, pro Tabelle. Das ist der Schritt, den man nicht auslassen darf.
  4. Umschalten, mit der alten Datenbank als Rückweg im Hintergrund.

Die zwei Stolpersteine dabei: Datums- und Zeitwerte werden anders abgelegt, und Wahrheitswerte sind in SQLite Zahlen. Beides fällt bei einer Zeilenzählung nicht auf, sondern erst, wenn eine Auswertung merkwürdige Ergebnisse liefert. Deshalb gehören zur Prüfung nicht nur Zeilenzahlen, sondern ein paar inhaltliche Stichproben.

Was es gebracht hat

  • Ein Dienst weniger im Betrieb, in der Überwachung, bei Updates und beim Umzug.
  • Sicherungen, die man tatsächlich anfasst. Eine Datei kann man kopieren und lokal öffnen. Ein Dump muss man einspielen, um hineinzusehen — und deshalb tut man es nicht.
  • Schnellere Abfragen bei kleinen Lesevorgängen, weil kein Netzwerkweg dazwischen liegt.
  • Testen mit echten Daten als Zwei-Sekunden-Vorgang.

Was sich nicht verändert hat: die Antwortzeiten der Anwendung im Alltag. Die Datenbank war nie der Engpass, in keiner der beiden Welten.

Die Prüfungen, die ich beim Umzug gemacht habe

Ein Datenbankwechsel ist der Vorgang, bei dem stille Fehler am teuersten sind. Deshalb war der Prüfteil länger als der Umzug selbst.

Zeilenzahl pro Tabelle, vorher und nachher. Der Basistest.

Summen über Zahlenspalten. Punkte, Münzen, Zähler. Eine gleiche Zeilenzahl bei falscher Summe bedeutet, dass Werte verlorengegangen sind.

Stichproben über Datumsangaben. Der wahrscheinlichste Ort für Fehler, weil beide Systeme Zeit unterschiedlich ablegen. Ich habe für jede Tabelle den ältesten und den neuesten Datensatz verglichen.

Wahrheitswerte. In einer Datei-Datenbank sind sie Zahlen. Ein Wert, der als Text ankommt, ist immer wahr — auch wenn dort „false" steht.

Ein vollständiger Durchlauf der Anwendung gegen die neue Datenbank, bevor umgeschaltet wurde.

Gefunden habe ich dabei genau einen Fehler: Zeitstempel ohne Zeitzone, die um zwei Stunden verschoben ankamen. Das hätte man an Zeilenzahlen nie gesehen.

Wie ich seitdem Datenbanken sichere

Der schönste Nebeneffekt der Datei-Datenbank ist die Sicherung, und dabei gibt es genau eine Regel, die man kennen muss.

Nicht kopieren, sondern die eingebaute Sicherung benutzen. Ein einfacher Kopiervorgang während des Betriebs erwischt einen unvollständigen Stand, weil zur Datenbank weitere Dateien gehören.

VACUUM INTO '/pfad/sicherung-2024-03-16.db';

Das erzeugt eine in sich stimmige Datei, ohne den Betrieb anzuhalten. Danach ist es eine ganz normale Datei: kopieren, packen, wegschaffen.

Und der Test, der dazugehört: einmal im Quartal eine Sicherung öffnen und ein paar Abfragen darauf laufen lassen. Bei einer Datei-Datenbank dauert das zwei Minuten — und genau deshalb mache ich es tatsächlich, was bei der vorherigen Lösung nie der Fall war.

Die eigentliche Lehre

Der Wechsel war für mich vor allem eine Übung im ehrlichen Bewerten der eigenen Anforderungen.

Ich hatte Postgres nicht gewählt, weil ich es brauchte, sondern weil es die seriöse Antwort ist. Und ich habe anderthalb Jahre gebraucht, um von „das ist eigentlich zu groß" zu „ich ändere es" zu kommen — weil die Umstellung erst dann sinnvoll war, als sie einen Vorgang verbessert hat, den ich regelmäßig habe.

Beide Teile dieser Geschichte halte ich für richtig: die Erkenntnis früh haben und trotzdem warten, bis es einen konkreten Grund gibt.