Wo dein Content liegt, ist eine Betriebsfrage: eine Instanz oder mehrere, Content im Git-Repo oder in einer Datenbank. RustyCMS macht daraus keine Produktfrage: Drei Backends stehen hinter derselben REST-API, denselben Schemas und derselben Konformanzsuite. Und die Entscheidung ist nicht endgültig.
Dateien: der Default, diffbar in Git
<content_dir>/<env>/<collection>/<slug>.json5
Ein Eintrag, eine Datei. git log sagt, wer was wann geändert hat, ein Diff im Pull-Request zeigt eine Textänderung, rsync schiebt den Baum auf den Server. Kein Dienst, kein Backup außer dem Repo. Writes sind atomar — Temp-Datei, fsync, rename.
Die Grenze ist real: eine Liste mit Filter und Sortierung liest die ganze Collection. Bis etwa 1000 Einträge je Collection ist das billiger als ein Datenbank-Roundtrip, bei 10 000 kostet dieselbe Liste 1,4 s statt 2 ms. Und zwei Prozesse auf demselben Verzeichnis überschreiben einander.
SQLite: eine Instanz, viele Einträge
Eine Datenbankdatei je Space. Filter, Sortierung und Fenster laufen als SQL statt als Scan, ohne dass jemand einen Datenbankserver betreibt. Der Preis: der Content ist binär — kein Diff, kein Review im PR.
Postgres: mehrere Instanzen
Eine Datenbank für alle Spaces, gebaut für zwei Container hinter einem Load-Balancer, Blue/Green, Managed-Hoster. Jeder Write meldet sich per LISTEN/NOTIFY an alle Instanzen, die ihren Cache verwerfen. Hintergrundjobs nehmen je Tick eine Sperre in der Datenbank, damit nur eine Instanz arbeitet. Edit-Locks gelten instanzübergreifend.
Dazu kommt, was nur eine Datenbank kann: Filter, Facetten und Referenz-Joins laufen als SQL — 50 Treffer mit einem Referenzfeld kosten 2 Anweisungen statt 51. index: true im Schema legt Sortier-Indizes an: 100 000 Zeilen sortiert mit Fenster, 17,6 → 0,74 ms. Lese-Views öffnen den Content für SQL- und BI-Tools. Volltext läuft als tsvector in derselben Transaktion wie der Eintrag. Beim Schreiben ist Postgres etwa 15-mal schneller als der Dateimodus.
Wechseln, mit Nachweis
# ohne --apply: Dry-run, nichts wird geschrieben
store-migrate --from file --to postgres --content-dir ./content \
--database-url postgres://… --apply
Nach dem Kopieren liest das Werkzeug das Ziel zurück und vergleicht je Collection die Anzahl und je Eintrag einen sha256 über kanonisches JSON. Weicht etwas ab, bricht der Lauf ab, der Modus bleibt wie er war. --to file geht genauso — ein Archiv, das in fünf Jahren ohne Postgres lesbar ist. Der Modus ist in _config/store.json5 festgeschrieben; ein Start mit widersprechender Konfiguration bricht ab, statt still einen leeren Store hochzufahren.
Ehrlich: welchen nehmen
Der Dateimodus bleibt die Empfehlung. Mit eingeschaltetem Antwort-Cache sind alle drei auf der Leseseite messbar gleich — Postgres nimmst du für mehrere Instanzen oder SQL-Zugriff, nicht für Tempo. Und es ist ein Dokumentenmodell: ein Eintrag je Zeile, keine Tabelle je Collection, keine Fremdschlüssel in der Datenbank. Genau das ist die Bedingung dafür, dass der Rückweg in Dateien existiert.