Skip to content

Deployment Guide

Synchro uses PostgreSQL logical replication for change detection. This requires configuration changes that persist across restarts.

Set wal_level to logical (requires a PostgreSQL restart):

ALTER SYSTEM SET wal_level = 'logical';

Recommended settings:

ParameterMinimumRecommendedNotes
wal_levellogicallogicalRequires restart
max_replication_slots14+One per WAL consumer instance
max_wal_senders14+One per replication connection

Create a publication for all tables that participate in sync:

CREATE PUBLICATION synchro_pub FOR TABLE tasks, comments, categories;

The synchrod reference server creates the publication automatically on startup if it does not exist. When embedding the library, you must create it manually or as part of your migration pipeline.

The WAL consumer creates its replication slot automatically on first connection. No manual slot creation is needed. The default slot name is synchro_slot.


Synchro ships synchrod, a standalone sync server binary for development and production use.

Terminal window
# Build
go build -o bin/synchrod ./cmd/synchrod
# Run
DATABASE_URL="postgres://user:pass@host:5432/db?sslmode=disable" \
REPLICATION_URL="postgres://user:pass@host:5432/db?replication=database&sslmode=disable" \
JWT_SECRET="your-secret" \
bin/synchrod
VariableRequiredDefaultDescription
DATABASE_URLYesPostgreSQL connection string
REPLICATION_URLYesReplication connection string (append replication=database)
JWT_SECRETYes*JWT HMAC signing secret
JWKS_URLYes*JWKS endpoint URL for RS256/ES256 verification
JWT_USER_CLAIMNosubJWT claim containing the user ID
LISTEN_ADDRNo:8080HTTP listen address
SLOT_NAMENosynchro_slotLogical replication slot name
PUBLICATION_NAMENosynchro_pubPostgreSQL publication name
MIN_CLIENT_VERSIONNoMinimum client SDK version (semver)
LOG_LEVELNoinfoLog level: debug, info, warn, error

*One of JWT_SECRET or JWKS_URL is required for authentication. If neither is set, synchrod falls back to the X-User-ID header (development only).

MethodPathDescription
POST/sync/registerRegister or re-register a client
POST/sync/pullPull changes from server
POST/sync/pushPush changes to server
POST/sync/snapshotFull snapshot (initial sync or recovery)
GET/sync/schemaSchema definition with column types and defaults
GET/sync/tablesTable metadata (sync config, dependencies)
GET/healthzHealth check

Sync runs inside your application process. Zero extra infrastructure.

graph TB
    subgraph "Your Application Process"
        API[App API]
        Sync[Synchro Engine]
        WAL[WAL Consumer]
    end
    DB[(PostgreSQL)]
    API --> DB
    Sync --> DB
    WAL --> DB

Use when: early stage, low-to-medium write volume, simplicity is the priority.

// In your main application
engine, err := synchro.NewEngine(synchro.Config{
DB: db,
Registry: registry,
Logger: logger,
})
// Start WAL consumer in-process
consumer := wal.NewConsumer(wal.ConsumerConfig{
ConnString: replicationURL,
SlotName: "synchro_slot",
PublicationName: "synchro_pub",
Registry: registry,
Assigner: synchro.NewJoinResolverWithDB(registry, db),
ChangelogDB: db,
Logger: logger,
})
go consumer.Start(ctx)
// Mount sync routes alongside your API
h := handler.New(engine)
mux.Handle("/sync/", authMiddleware(h.Routes()))

Tradeoffs:

AdvantageDisadvantage
Single binary to deployWAL consumer competes for CPU/memory
Shared connection poolApplication restart restarts WAL consumer
No inter-process latencyHarder to scale sync independently

Dedicated sync worker process. Same database.

graph TB
    subgraph "App Process"
        API[App API]
    end
    subgraph "Sync Worker"
        Sync[Synchro Engine]
        WAL[WAL Consumer]
    end
    DB[(PostgreSQL)]
    API --> DB
    Sync --> DB
    WAL --> DB

Use when: sync CPU competes with API, you need process isolation before splitting the database.

Tradeoffs:

AdvantageDisadvantage
Process isolationTwo processes to deploy
Independent scalingShared DB connection limits
App restarts don’t affect syncShared DB can still be a bottleneck

Full isolation with a dedicated sync store.

graph TB
    subgraph "App Process"
        API[App API]
    end
    subgraph "Sync Worker"
        Sync[Synchro Engine]
        WAL[WAL Consumer]
    end
    PrimaryDB[(Primary DB)]
    SyncDB[(Sync Store)]
    Replica[(Read Replica)]
    API --> PrimaryDB
    WAL --> PrimaryDB
    Sync --> SyncDB
    Sync --> Replica

Use when: high write throughput, large bucket fanout, strict OLTP protection needed.

Tradeoffs:

AdvantageDisadvantage
Full DB isolationMultiple databases to manage
Independent resource allocationCross-DB consistency is eventual
Read replicas for pull scale-outHigher operational complexity

  • wal_level = logical confirmed (SHOW wal_level;)
  • Publication created for all synced tables
  • Replication slot capacity sufficient (max_replication_slots)
  • Infrastructure tables migrated (migrate.Migrations())
  • RLS policies applied (GenerateRLSPolicies)
  • JWT authentication configured (JWT_SECRET or JWKS_URL)
  • Database user for sync has REPLICATION privilege
  • Application database role (for push RLS) is non-superuser
  • WAL consumer running with replication connection
  • Compaction enabled (background goroutine or cron)
  • MIN_CLIENT_VERSION set if enforcing SDK upgrades
  • WAL consumer replication lag
  • sync_changelog table size and growth rate
  • Client sync latency (time since last_sync_at)
  • Push/pull error rates and latency histograms
  • Replication slot pg_replication_slots.active status
  • Backup strategy includes sync_changelog and sync_clients tables
  • Tested restore procedure for sync infrastructure tables
  • Replication slot recreation documented for disaster recovery