Skip to content
Lumaft documentation contents
Lumaft documentation

PostgreSQL operations

Run Lumaft on a PostgreSQL server you operate — requirements, connections, replicas, maintenance, backups, and what Lumaft leaves to you.

PostgreSQL operations

Lumaft on PostgreSQL is customer-operated: you run, size, patch, back up, and make the server available; Lumaft owns only its schema and migrations. This page is the operating contract for that split.

Two things are gated by license, and only two: beginning an adoption from SQLite or a fresh installation on PostgreSQL requires Business, and serving from more than one replica requires Enterprise. Continuing to run an adopted database never depends on the license.

When to choose PostgreSQL

Situation PostgreSQL?
Compute that cannot keep an exclusive block volume (Fargate, Container Apps, scale-to-zero) Required
You want zero downtime across restarts and rollouts Yes, with Enterprise replicas
SQLite read p95 or cold readiness repeatedly exceeds the measured envelope at your sizing Consider it, and measure — a provider switch alone does not guarantee more throughput
One replica on a VM with an EBS volume, tens of thousands of operations SQLite is complete and supported; stay

Requirements

  • A PostgreSQL server on a currently supported major version, reachable from every Lumaft replica. Managed services (Amazon RDS, Aurora PostgreSQL, Azure Database for PostgreSQL, Cloud SQL) are fine for everything except the transcript capacity measurement described below.
  • A dedicated database (or schema) and a role that owns it, so Lumaft can create and alter its own tables. It needs no superuser, replication, or server-filesystem privileges.
  • TLS. sslmode=require at minimum; sslmode=verify-full with your CA bundle where you can supply it.
  • A connection budget that the formula below fits inside.
CREATE ROLE lumaft LOGIN PASSWORD '<password>';
CREATE DATABASE lumaft OWNER lumaft;

The connection URL:

postgresql://lumaft:<password>@db.internal.example:5432/lumaft?sslmode=verify-full

Put it in a protected file and name the file with LUMAFT_POSTGRES_URL_FILE. Connection values never appear in logs, health responses, or the console.

Starting on PostgreSQL

Fresh installation

Set exactly one of LUMAFT_POSTGRES_URL or LUMAFT_POSTGRES_URL_FILE and nothing else that selects a database. Do not also set LUMAFT_SQLITE_PATH. Conflicting sources refuse startup. Lumaft creates its schema on first start and migrates it forward on every later one.

Adopting from SQLite

Adoption is a console workflow on Administration → Database that transfers and verifies the durable record, then makes PostgreSQL authoritative:

  1. Take and verify a cold SQLite backup and keep it; a SQLite file is not a PostgreSQL backup.
  2. Enter the PostgreSQL connection string in the write-only form and confirm your username and current password.
  3. Review the preflight.
  4. Confirm. Lumaft pauses writes, transfers the data, verifies it, and saves the provider configuration atomically in the data directory.
  5. Restart. PostgreSQL is now authoritative.

Guards you can rely on:

  • A failed or cancelled adoption leaves SQLite authoritative, never activates the provider file, and removes temporary target credentials.
  • Starting an image with a PostgreSQL variable against a non-empty SQLite installation that has no completed-transfer stamp refuses. Changing a variable is not a migration.
  • The UI-managed provider file is mode 0600 and contains credentials; treat the data directory as you treat a backup. Environment-managed PostgreSQL cannot be changed through the console.

Connections

Each replica opens a pool of LUMAFT_POSTGRES_POOL_MAX connections plus two dedicated sessions: one that holds job leases through advisory locks, and one that LISTENs for change notifications. At startup Lumaft reads max_connections and the reserved-connection settings and refuses with pool-topology-exceeds-server unless:

LUMAFT_REPLICAS_MAX × (LUMAFT_POSTGRES_POOL_MAX + 2) + LUMAFT_POSTGRES_CONNECTION_HEADROOM
  ≤ max_connections − reserved connections
Setting Default Meaning
LUMAFT_REPLICAS_MAX 1 Honest ceiling, including rollout surge
LUMAFT_POSTGRES_POOL_MAX 1 Pooled connections per replica
LUMAFT_POSTGRES_CONNECTION_HEADROOM 10 Connections reserved for your own tools, backups, and monitoring

Example: three replicas with a pool of two and ten headroom need 3 × 4 + 10 = 22 non-reserved connections. A managed instance whose max_connections is derived from memory may need a larger class or a parameter-group change; check the number before you scale.

Replicas (Enterprise)

All replicas must share the same release, database, backend definitions, authentication, and licensing settings. A replica started from different configuration is a different installation sharing a database, which nothing makes safe.

How the topology behaves:

  • Every durable thing is a row, read per request. Replicas cache nothing across requests. Change notifications flow through PostgreSQL LISTEN/NOTIFY, with a fifteen-second poll fallback if the listener drops.
  • Periodic jobs run on exactly one replica at a time — observation sweeps, retention expiry, purge, forget, notification dispatch, license renewal, schema migration — each under a session-level advisory lock with a fencing token. Loss of the lock session is detected within ten seconds and a job never writes under a lost grant.
  • One replica is installation-primary and serves at any license level. Every other replica serves only while the Enterprise entitlement verifies. On lapse a standby stays alive, holds no job, returns 503 readiness, and keeps contending to become primary. A lapse degrades to one serving replica, never to zero.
  • Application transactions serialize installation-wide through a deployment-row lock with a five-second contention bound. Ordinary reads and object-store work stay concurrent. More replicas give coordination and failover, not proportional write throughput.
  • Sign-in throttling is per replica. At N replicas the combined attempt budget is N times one replica's. Rate-limit sign-in at your ingress.
  • Diagnostic reports are per replica, stamped with the replica identity. Never read one as an installation total.

Operational rules that follow:

Rule Why
Route traffic on readiness; restart on liveness A standby is alive and not ready, by design
While lapsed, scale to one replica A rolling update waiting on standby readiness cannot complete
Rolling deploys only when every pending migration is startup-safe Gated migrations need one replica and the explicit apply
After a point-in-time restore, restart every replica Replicas re-read the schema lineage and re-elect job ownership
Keep LUMAFT_REPLICAS_MAX honest It sizes both the connection check and the sign-in budget

Exactly one replica migrates, under the schema-migration lease; the others wait up to LUMAFT_MIGRATION_WAIT_SECONDS (default 300) and then serve at the same revision or refuse. On PostgreSQL the recovery point for a gated migration is your backup — Lumaft copies nothing.

Maintenance you own

  • Vacuum and bloat. Retention expiry, purge, and forget delete rows; PostgreSQL reclaims the space through vacuum. Leave autovacuum on and watch table bloat on the evidence tables after large purges. Lumaft makes no claim that deleting rows returns disk to the host.
  • Disk. Lumaft's /api/v1/storage-health measures the application's own data directory, not the database server's disk. Monitor the server's storage with the server's tools.
  • Connections. Alert when used connections approach max_connections − headroom.
  • Upgrades of PostgreSQL itself. Stop Lumaft, upgrade the server, start Lumaft. Lumaft needs nothing from a major-version upgrade beyond a reachable database.
  • Role and password rotation. Update the URL file on every replica and restart them.

Backups and recovery

Use your server's tooling. A physical base backup with WAL archiving gives point-in-time recovery; pg_dump of the Lumaft database gives a logical copy. Take logical dumps while Lumaft is stopped, or accept that in-flight writes may straddle the dump.

Two things live in the database that make its backup a credential:

  • Connected-licensing state — the instance identity, credential revision, and signed lease.
  • Every account's credential hash and every integration token digest.

Protect backups accordingly, and never restore a copy into a second live installation and run both: connected licensing binds one agreement to one installation identity, and two installations sharing it are indistinguishable clones.

After any restore, restart every replica. Restoring rewinds Lumaft's record to the backup point; current projections converge forward from live state, and durable history earlier than the backup is whatever the backup held. See Backup and disaster recovery.

Transcript and state capture on PostgreSQL

Optional transcript capture and whole-state retention need a fresh measurement of the PostgreSQL server's actual storage before each admission. SQLite measures its own filesystem; PostgreSQL cannot be measured from inside the application container, and Lumaft refuses to substitute a guess.

Lumaft supplies a producer script that runs on the PostgreSQL host (or in its mount namespace) with psql and Node.js, uses read-only SQL and statfs, and atomically writes a report file every five seconds. Mount that report read-only into every replica and set:

LUMAFT_TRANSCRIPT_POSTGRES_CAPACITY_FILE=/run/lumaft-capacity/capacity.json

Requirements and limits:

  • The database role must be able to execute pg_catalog.pg_control_system(); stock PostgreSQL grants this to PUBLIC. The producer role additionally needs pg_read_all_settings to read data_directory, plus USAGE on the schema and SELECT on transcript_postgres_capacity.
  • Supported layout: the default tablespace with data and WAL on one filesystem. Separate WAL devices, external tablespaces, and standbys refuse collection.
  • A report expires fifteen seconds after its database-clock read. Missing, stale, malformed, writable, or mismatched reports keep capture closed — operation evidence is unaffected.
  • Managed providers are not covered. Without host filesystem access there is nothing to measure, so transcript and state capture stay closed on RDS, Aurora, Azure Flexible Server, and Cloud SQL. Everything else on this page works there.

Capture preserves the greater of 2 GiB or 10 % of measured space, plus a further 1 GiB or 5 % margin, and reserves capacity in the same transaction as the evidence it admits. A healthy status is not a reservation; every admission re-checks.

Startup refusals specific to PostgreSQL

Log line contains Meaning Fix
pool-topology-exceeds-server The connection formula does not fit the server Lower the ceiling or pool, or raise max_connections
multi-replica-requires-postgresql LUMAFT_REPLICAS_MAX above 1 on SQLite Adopt PostgreSQL first
Conflicting database sources Both SQLite and PostgreSQL selected, or both URL forms Set exactly one
Readiness stays 503, process alive The database cannot be queried: network, TLS mode, credentials, or a standby awaiting entitlement Check the URL file, reachability, and the license state
schema-newer-than-binary A newer release migrated the database Start the newer image, or restore

The general startup table is in Database integration.