Plainwire Self-Hosting2.0

PostgreSQL to Scylla migration

Do not switch an existing instance directly from PostgreSQL to Scylla.

Phase 0: healthy PostgreSQL#

PLAINWIRE_MESSAGE_BACKEND=postgres
PLAINWIRE_SCYLLA_ENABLED=false

Take backups and verify messages, uploads, and account operations.

Phase 1: create schema#

Configure Scylla connectivity, then run:

PLAINWIRE_SCYLLA_LOCAL_DC=datacenter1 \
PLAINWIRE_SCYLLA_REPLICATION_FACTOR=3 \
./scripts/scylla-migrate

Use replication factor 1 only for a single-node test system. A three-node single-DC cluster normally uses RF 3.

Enable connectivity while messages remain PostgreSQL-backed:

PLAINWIRE_SCYLLA_ENABLED=true
PLAINWIRE_MESSAGE_BACKEND=postgres

Check storage health.

Phase 2: dual write#

PLAINWIRE_MESSAGE_BACKEND=dual

New message work commits to PostgreSQL first and is mirrored to Scylla through the durable outbox.

Phase 3: backfill#

./scripts/storage-migrate status
./scripts/storage-migrate backfill 50000

Backfill progress is checkpointed. Reruns resume.

Phase 4: verify#

./scripts/storage-migrate verify 0 1000
./scripts/storage-migrate shadow 500
./scripts/storage-migrate parity 500
./scripts/storage-migrate flush-outbox

A successful process exit is not enough. Review mismatch counts, backlog, latency, and health.

If needed:

./scripts/storage-migrate reconcile-intents 500
./scripts/storage-migrate reconcile 500

Phase 5: make Scylla authoritative#

Only after backfill is complete, outbox is caught up, repeated verification is clean, and operational metrics are acceptable:

PLAINWIRE_MESSAGE_BACKEND=scylla

Keep PostgreSQL message rows through a confidence window.

Rollback#

PLAINWIRE_MESSAGE_BACKEND=postgres

Restart normally. Do not delete the PostgreSQL recovery mirror during initial Scylla adoption.

Plainwire Self-Hosting Handbook · verified against Plainwire 2.0.0 · 2026-09-17