Zum Inhalt
flowplanDokumentationCommunityMarketplaceZur Startseite

Skalieren

PostgreSQL

Statt SQLite kann Flowplan eine PostgreSQL-Datenbank nutzen – wenn du ohnehin einen gepflegten Datenbankserver betreibst, mehr Personen gleichzeitig arbeiten oder du mehrere Instanzen im Cluster-Modus laufen lassen willst.

10 Min. Lesezeit

Auf dieser Seite
  1. SQLite oder PostgreSQL?
  2. Voraussetzungen
  3. Rolle ohne Superuser-Rechte
  4. Einrichten
  5. Mit Docker Compose
  6. Variablen
  7. Suche auf PostgreSQL
  8. Sicherungen mit pg_dump
  9. Wiederherstellen
  10. PgBouncer
  11. Weiter

SQLite oder PostgreSQL?

SQLite bleibt der Standard und für die meisten Instanzen die beste Wahl: ein Container, ein Datenordner, nichts weiter zu betreiben. Eine einzelne Abfrage ist auf SQLite sogar schneller, weil sie im selben Prozess läuft.

PostgreSQL lohnt sich, wenn

  • du einen Datenbankserver schon betreibst oder mietest (mit eigener Überwachung, Replikation und Point-in-Time-Recovery) und Flowplan dort einordnen willst,
  • regelmäßig mehr als etwa 20 Personen gleichzeitig in Flowplan arbeiten (siehe Dimensionierung),
  • du mehrere Flowplan-Instanzen hinter einem Load Balancer betreiben willst – der Cluster-Modus setzt PostgreSQL voraus.

Funktional gibt es keinen Unterschied: Alle Funktionen, die Suche und die Sicherungen arbeiten mit beiden Datenbanken. Wechseln kannst du jederzeit in beide Richtungen, siehe Datenbank wechseln.

Auch mit PostgreSQL braucht Flowplan seinen Datenordner (/app/data): Dort liegen die hochgeladenen Dateien (ohne S3), secrets.key, die Push-Schlüssel und Zwischendateien von Sicherungen. Nur die Datenbank wandert nach PostgreSQL.

Voraussetzungen

  • PostgreSQL 16 oder neuer, eine leere Datenbank (oder ein leeres Schema) und eine Rolle, der sie gehört. Superuser-Rechte braucht Flowplan nicht – und sollte sie auch nicht haben, siehe Rolle ohne Superuser-Rechte.
  • Locale mit UTF-8, etwa en_US.utf8 oder de_DE.utf8 – so ist es im offiziellen postgres-Image und bei den meisten verwalteten Diensten. Mit der Locale C findet die Suche chinesischen und japanischen Text zwar weiterhin richtig, aber deutlich langsamer.
  • Erweiterung pg_trgm für die Volltextsuche. Sie gehört zu jedem normalen PostgreSQL und ist „trusted“: Flowplan installiert sie selbst in ein eigenes Schema flowplan_ext, wenn die Rolle in der Datenbank Objekte anlegen darf (CREATE-Recht, als Eigentümerin der Datenbank gegeben). Sonst einmal als Administrator: CREATE EXTENSION pg_trgm;. Ohne pg_trgm funktioniert die Suche weiter, durchsucht aber jedes Mal alle Einträge (das Log sagt das).
  • Optional pgvector für die semantische KI-Suche. Ab pgvector 0.8 installiert Flowplan sie selbst wie pg_trgm. Vor 0.8 ist die Erweiterung nicht „trusted“ – dann führt ein Administrator in der Datenbank von Flowplan einmal CREATE EXTENSION vector; aus, und Flowplan nutzt sie ab dem nächsten Start. Ohne pgvector rechnet Flowplan die semantische Suche selbst, wie auf SQLite; abschalten lässt sie sich mit FLOWPLAN_PGVECTOR=off.
  • Für die Sicherungen pg_dump und pg_restore mindestens in der Hauptversion des Servers. Das Docker-Image bringt die Werkzeuge von PostgreSQL 18 mit; sie sichern und spielen auf Servern von Version 16 bis 18 ein. Wer das Image selbst baut, wählt die Version mit --build-arg PG_CLIENT_MAJOR=18. Ohne Docker das Paket postgresql-client passend installieren oder mit FLOWPLAN_PG_BIN auf den Ordner mit den Programmen zeigen.
-- als Administrator (Superuser) auf dem PostgreSQL-Server
CREATE ROLE flowplan LOGIN NOSUPERUSER NOCREATEDB NOCREATEROLE PASSWORD '<Passwort>';
CREATE DATABASE flowplan OWNER flowplan;
-- in der neuen Datenbank die Erweiterungen in ein eigenes Schema:
\c flowplan
CREATE SCHEMA IF NOT EXISTS flowplan_ext AUTHORIZATION flowplan;
CREATE EXTENSION IF NOT EXISTS pg_trgm SCHEMA flowplan_ext;
-- optional, für die semantische Suche (vor pgvector 0.8 nur so):
CREATE EXTENSION IF NOT EXISTS vector SCHEMA flowplan_ext;

Rolle ohne Superuser-Rechte

Lass Flowplan mit einer Rolle verbinden, der die Datenbank gehört, die aber kein Superuser ist. Flowplan legt in seiner Datenbank Tabellen, Funktionen und Trigger an und ändert sie – dafür reicht die Eigentümerschaft. Ein Superuser dagegen kann Dateien auf dem Datenbankserver lesen und dort Programme starten (COPY … TO PROGRAM); eine Lücke in Flowplan oder ein Administrator, der sie ausnutzt, käme damit bis auf den Host.

Viele Images und Charts machen den eingestellten Benutzer zum Superuser – das offizielle postgres-Image etwa POSTGRES_USER. Lege dann wie oben eine eigene Rolle an. Die Erweiterungen installiert der Administrator am besten gleich mit, in das Schema flowplan_ext: Flowplan nutzt eine installierte Erweiterung dort, wo sie liegt, und legt pg_trgm (und pgvector ab 0.8) sonst selbst in flowplan_ext an. pgvector vor 0.8 kann nur ein Administrator installieren; ohne läuft die semantische Suche langsamer.

Eine bestehende Rolle, die Superuser ist, stuft ein anderer Superuser zurück:

-- einmalig, solange flowplan noch Superuser ist (als flowplan):
CREATE ROLE admin LOGIN SUPERUSER PASSWORD '<Passwort>';
-- dann als admin:
ALTER ROLE flowplan NOSUPERUSER NOCREATEROLE NOCREATEDB;

Datenbank und Tabellen gehören weiter flowplan, und die bisher installierten Erweiterungen bleiben nutzbar. Den Zugang admin hebst du für die Verwaltung auf; Flowplan braucht ihn nicht.

Einrichten

PostgreSQL wählst du mit DATABASE_URL. Beginnt der Wert mit postgres:// oder postgresql://, nutzt Flowplan PostgreSQL; fehlt er, bleibt es bei SQLite.

DATABASE_URL=postgres://flowplan:<Passwort>@db.example.internal:5432/flowplan
# mit TLS zum Datenbankserver:
# DATABASE_URL=postgres://flowplan:<Passwort>@db.example.internal:5432/flowplan?sslmode=require

Sonderzeichen im Passwort (@, :, /, # …) müssen in der Adresse prozentkodiert sein, z. B. %40 für @.

Beim ersten Start legt Flowplan alle Tabellen, Indizes und Trigger selbst an. Bei jedem Update ergänzt es, was die neue Version braucht – es löscht nie etwas, und mehrere Instanzen stimmen sich dabei über eine Sperre ab. Ein Update geht also wie gewohnt: neues Image holen, Container neu starten.

Mit Docker Compose

Ein Beispiel für einen einzelnen Container mit PostgreSQL daneben. Für pgvector statt postgres:16 das Image pgvector/pgvector:0.8.0-pg16 nehmen.

services:
  flowplan:
    image: ghcr.io/yannicschuller/flowplan:latest
    env_file: .env
    environment:
      FLOWPLAN_DATA_DIR: /app/data
      DATABASE_URL: postgres://flowplan:${POSTGRES_PASSWORD}@postgres:5432/flowplan
    ports:
      - "127.0.0.1:3000:3000"
    volumes:
      - flowplan-data:/app/data
    depends_on:
      postgres:
        condition: service_healthy
    restart: unless-stopped

  postgres:
    image: postgres:16
    environment:
      POSTGRES_USER: postgres
      POSTGRES_PASSWORD: ${POSTGRES_ADMIN_PASSWORD}
      FLOWPLAN_DB_PASSWORD: ${POSTGRES_PASSWORD}
    volumes:
      - postgres-data:/var/lib/postgresql/data
      - ./postgres-init.sh:/docker-entrypoint-initdb.d/10-flowplan.sh:ro
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -h 127.0.0.1 -U flowplan -d flowplan"]
      interval: 5s
      retries: 20
    restart: unless-stopped

volumes:
  flowplan-data:
  postgres-data:

POSTGRES_PASSWORD (das Passwort der Rolle flowplan) und POSTGRES_ADMIN_PASSWORD (das des Superusers postgres aus dem Image, nur für die Verwaltung) stehen in der .env neben der compose.yaml (je openssl rand -hex 24). Das Skript postgres-init.sh (öffnet in neuem Tab) aus dem Cluster-Beispiel legt beim ersten Start der Datenbank die Rolle flowplan ohne Superuser-Rechte an, gibt ihr die Datenbank flowplan und installiert die Erweiterungen in flowplan_ext – leg es neben die compose.yaml. Ein Volume aus einer älteren Fassung dieses Beispiels (POSTGRES_USER: flowplan) behält seine Rollen: Dort ist flowplan der Superuser, bis du ihn zurückstufst.

Variablen

VariableStandardBedeutung
DATABASE_URLleer (SQLite)Adresse der PostgreSQL-Datenbank, postgres://… oder postgresql://….
DATABASE_SESSION_URLDATABASE_URLEigene Verbindung für Benachrichtigungen, die Leitung im Cluster und pg_dump/pg_restore/psql – nur nötig, wenn DATABASE_URL auf PgBouncer im Modus transaction zeigt (unten).
FLOWPLAN_PG_SCHEMApublicSchema innerhalb der Datenbank, falls Flowplan sich die Datenbank mit anderem teilt. Wird angelegt, wenn es fehlt.
FLOWPLAN_PG_POOL10Verbindungen je Instanz (1–500).
FLOWPLAN_PG_STATEMENT_TIMEOUT120000Höchstdauer einer Abfrage in Millisekunden; 0 = kein Limit.
FLOWPLAN_PG_LOCK_TIMEOUT10000So lange wartet eine Abfrage höchstens auf eine Sperre (ms).
FLOWPLAN_PG_IDLE_TX_TIMEOUT60000Eine Transaktion, die so lange nichts tut, wird beendet (ms).
FLOWPLAN_PG_CONNECT_TIMEOUT30000So lange wartet eine Anfrage auf eine freie Verbindung aus dem Pool (ms).
FLOWPLAN_PG_PREPAREanoff schickt jede Abfrage ohne Namen statt als benanntes Prepared Statement – für PgBouncer im Modus transaction vor 1.21 oder ohne max_prepared_statements.
FLOWPLAN_PG_SCHEMA_AUTOanoff: Die Instanz legt das Schema nicht selbst an oder aktualisiert es – das übernimmt ein eigener Schritt (node /app/scripts/db-schema.mjs, siehe Cluster-Modus).
FLOWPLAN_PGVECTORanoff nutzt pgvector nicht, auch wenn es installiert ist.
FLOWPLAN_BACKUP_FORMATpg_dumpsqlite: geplante Sicherungen im portablen SQLite-Format statt mit pg_dump (unten).
FLOWPLAN_PG_BINleerOrdner mit pg_dump, pg_restore und psql, wenn sie nicht im PATH liegen.
PGCONNECT_TIMEOUT15So viele Sekunden warten pg_dump, pg_restore und psql höchstens auf die Verbindung zum Server (die Variable von libpq).

Alle Variablen stehen auch kommentiert in der .env.example (öffnet in neuem Tab) des Repositorys.

Lange Arbeiten wie Sicherungen, Wiederherstellungen und Schema-Änderungen heben die Zeitlimits für sich selbst auf.

Verbindungen: Jede Instanz öffnet bis zu FLOWPLAN_PG_POOL Verbindungen und bis zu zwei eigene (Benachrichtigungen, im Cluster die Wahl der leitenden Instanz). max_connections des Servers muss also mindestens Instanzen × (FLOWPLAN_PG_POOL + 2) plus Reserve für Sicherungen und Administration betragen. Der PostgreSQL-Standard von 100 reicht für eine Instanz mit dem Standardpool bequem.

Suche auf PostgreSQL

Die Suche liefert auf PostgreSQL dieselben Treffer in derselben Reihenfolge wie auf SQLite: Teile von Wörtern („plan“ in „Projektplanung“), chinesischer und japanischer Text ohne Leerzeichen, Umlaute und Akzente, Titel vor Text, dieselben Ausschnitte. Technisch arbeitet sie mit Trigramm-Indizes aus pg_trgm.

Hinweis

Erster Start nach dem Update oder Wechsel: Flowplan baut die beiden Suchindizes beim Öffnen der Datenbank. Auf großen Instanzen kann das einige Minuten dauern; so lange antwortet die Instanz noch nicht. Gib Healthchecks entsprechend Zeit (start_period, startupProbe). Danach füllt sich der Index im Hintergrund – die Suche funktioniert schon und findet mit der Zeit mehr.

Den Suchindex komplett neu aufbauen, etwa nach dem Einspielen von pg_trgm oder wenn Treffer fehlen:

docker compose exec flowplan node /app/scripts/db-search-rebuild.mjs

Das Skript liest DATABASE_URL und FLOWPLAN_PG_SCHEMA aus der Umgebung des Containers; die laufende Instanz indexiert danach alles neu (Seiten, Einträge, Kommentare, Nachrichten und Dateien). Auf SQLite funktioniert derselbe Befehl.

Sicherungen mit pg_dump

Auf PostgreSQL schreiben die geplante Sicherung und Jetzt sichern (Administration → Speicher & Sicherung) ein Archiv von pg_dump im Custom-Format: flowplan-2026-11-07-03-00.dump, mit FLOWPLAN_ENCRYPTION_KEY verschlüsselt als .dump.enc. Sonst bleibt alles wie bei SQLite:

  • Ziele: S3 unter <Präfix>/backups/, SFTP oder der Datenordner – mit derselben Aufbewahrung und demselben verzögerten Löschen der Dateien.
  • Verschlüsselung wie gehabt; decrypt.mjs entschlüsselt auch .dump.enc.
  • Das Archiv enthält nur das Schema von Flowplan, ohne Suchindex und pgvector-Tabelle – beide baut Flowplan nach dem Einspielen neu auf. Es ist dadurch kleiner und lässt sich auch auf einem Server ohne pg_trgm oder pgvector einspielen.
  • pg_dump läuft außerhalb von Flowplan und belastet die Instanz kaum.

Ausweichen auf SQLite: Fehlt ein passendes pg_dump (zu alt oder nicht installiert), schreibt die geplante Sicherung stattdessen eine portable SQLite-Datei (flowplan-….sqlite) und vermerkt das im Log. Mit FLOWPLAN_BACKUP_FORMAT=sqlite ist das immer so. Eine solche Datei lässt sich auf SQLite und PostgreSQL einspielen.

Die Instanzsicherung (Administration → Speicher & Sicherung → Gesamte Instanz → Herunterladen) enthält die Datenbank immer im portablen SQLite-Format und lässt sich deshalb auf beiden Datenbanken wiederherstellen. Litestream läuft mit PostgreSQL nicht – für eine laufende Sicherung mit beliebigem Zeitpunkt nutze die Mittel deines Datenbankservers (WAL-Archivierung, Point-in-Time-Recovery des Anbieters).

Wiederherstellen

  • Ein pg_dump-Archiv auf einer laufenden Instanz (flowplan-….dump, .dump.enc): unter Administration → Speicher & Sicherung → Aus Sicherung wiederherstellen hochladen. Flowplan prüft es sofort und spielt es beim nächsten Neustart ein – im Cluster-Modus die leitende Instanz, siehe Grenzen. Nur die Datenbank wird ersetzt; die Dateien bleiben, wie sie sind.
  • Auf einer neuen Instanz mit PostgreSQL geht dasselbe auf dem Einrichtungsbildschirm: hochladen oder aus S3 bzw. per SFTP holen, wie unter Beim Einrichten wiederherstellen.
  • Eine Instanz mit SQLite lehnt solche Archive ab – dafür gibt es die Instanzsicherung oder den Wechsel der Datenbank.
  • Flowplan prüft das Archiv vorher (eine Flowplan-Datenbank, nicht aus einer neueren Version), ersetzt dann das ganze Schema in einer Transaktion und baut den Suchindex neu. Aus dem Archiv kommen dabei nur die Daten der Tabellen von Flowplan; Tabellen, Funktionen und Trigger legt die laufende Version selbst an. Ältere Archive passen so in die aktuellen Tabellen.
  • Enthält das Archiv Objekte, die Flowplan nicht selbst anlegt – eigene Funktionen, Views, Trigger oder Regeln im Schema von Flowplan –, lehnt Flowplan es ab: „Die Sicherung enthält Datenbankobjekte, die Flowplan nicht selbst anlegt (etwa Funktionen oder Trigger), und wird deshalb nicht wiederhergestellt.“ Eigene Objekte gehören in ein anderes Schema; das sichert pg_dump von Flowplan nicht mit. Der vorherige Stand bleibt als pre-restore-<Zeit>/flowplan.dump im Datenordner – plane dafür Platz für ein weiteres Archiv ein. Schlägt das Einspielen fehl, kommt der vorherige Stand zurück.
  • Ein Archiv aus einem anderen Schema (FLOWPLAN_PG_SCHEMA) lässt sich ebenfalls einspielen.
  • Die Instanzsicherung (ZIP) stellst du wie bisher an derselben Stelle wieder her; sie passt auf beide Datenbanken.

PgBouncer

Flowplan hält seine Verbindungen selbst im Pool; eine direkte Verbindung zum Server ist der einfachste Weg. PgBouncer davor sollte so funktionieren – mit einem echten PgBouncer getestet ist es bisher nicht:

  • Benannte Prepared Statements: Flowplan nutzt sie standardmäßig. Im Modus transaction braucht PgBouncer dafür Version 1.21 oder neuer mit max_prepared_statements größer als 0. Sonst FLOWPLAN_PG_PREPARE=off setzen: Flowplan schickt dann jede Abfrage ohne Namen (kostet rund ein Drittel des Durchsatzes).
  • Eine eigene Sitzung für Benachrichtigungen, Leitung und Sicherungen: LISTEN, die Sperre der leitenden Instanz im Cluster und pg_dump, pg_restore und psql brauchen eine Verbindung, die ihnen allein gehört. Sie nutzen DATABASE_SESSION_URL – zeig damit direkt auf den Server oder auf einen Pool im Modus session. Ohne die Variable nehmen sie DATABASE_URL.
  • Startparameter options: Flowplan übergibt Schema (search_path) und Zeitlimits beim Verbindungsaufbau als options. Lehnt PgBouncer das ab, hilft ignore_startup_parameters = options – dann muss das Schema public sein, und die Zeitlimits setzt du an der Rolle (ALTER ROLE flowplan SET statement_timeout = '120s' usw.).
# Pool über PgBouncer im Modus transaction, eigene Sitzungen direkt zum Server
DATABASE_URL=postgres://flowplan:<Passwort>@pgbouncer:6432/flowplan
DATABASE_SESSION_URL=postgres://flowplan:<Passwort>@postgres:5432/flowplan
# nur ohne max_prepared_statements bzw. vor PgBouncer 1.21:
# FLOWPLAN_PG_PREPARE=off

Weiter

War das hilfreich?
JaDanke!
Nein – sag uns, was fehlt

Fehler gefunden oder etwas fehlt?Auf GitHub bearbeiten