Architecture
Multi-Device Synchronization Architecture
go-oversync implements a bundle-based replication model for SQLite clients and PostgreSQL servers. This page describes the architecture implemented by the current repository.
Overview
The current architecture has four main pieces:
- your application HTTP server
oversync.SyncServiceon PostgreSQLoversqlite.Clienton SQLite- the bundle/snapshot HTTP protocol between them
How It Works
- Your server registers the PostgreSQL tables that participate in sync.
Bootstrap()validates the server schema and prepares the sync metadata/runtime topology. A fresh layout atomically adopts populated registered tables; a committed existing layout attaches after declaration and managed-definition validation without scanning business or operational sync rows.- The SQLite client runs
Open()on launch, thenAttach(userID)after sign-in. - The SQLite client tracks local writes in
_sync_dirty_rowswith triggers. AfterDetach(), those triggers stay active so offline anonymous writes continue to enqueue local dirty state immediately. PushPending()freezes those writes into_sync_outbox_*, uploads them through/sync/push-sessions, then replays the authoritative committed bundle returned by the server. Process restart keeps that singleton outbox durable and resumes from it instead of recollecting a new local bundle.PullToStable()reads complete committed bundles from/sync/pulland advances the local checkpoint only after durable local apply.Rebuild(ctx)rebuilds managed tables from/sync/snapshot-sessionswhen a client is new or falls behind retained history. For stale/out-of-order source recovery,oversqlitechooses the rebuild-plus-rotate path internally. The server materializes those frozen snapshots inside PostgreSQL session tables and serves chunk reads statelessly bysnapshot_id. During rotated recovery the client persists one replacement source id durably, the server reserves that replacement source explicitly, and the old source is retired fail-closed until rebuild finishes.
flowchart LR
subgraph Clients["SQLite Clients"]
DeviceA["Device A<br/>(oversqlite)"]
DeviceB["Device B<br/>(compatible client)"]
end
subgraph Server["Server"]
HTTPServer["Your HTTP Server"]
SyncService["oversync.SyncService"]
PostgreSQL["PostgreSQL"]
end
DeviceA <-->|"push / pull / snapshot"| HTTPServer
DeviceB <-->|"push / pull / snapshot"| HTTPServer
HTTPServer --> SyncService
SyncService --> PostgreSQL
Authoritative State
PostgreSQL business tables are authoritative. Sync metadata is derived from committed
business-table effects and stored under the sync schema.
Important runtime tables include:
sync.metasync.table_catalogsync.user_statesync.scope_statesync.source_statesync.row_statesync.bundle_logsync.bundle_rowssync.scope_write_receiptssync.server_source_reservationssync.push_sessionssync.push_session_rowssync.snapshot_sessionssync.snapshot_session_rows
The server uses compact internal identities in hot sync tables: user_pk for users, table_id
for registered tables, key_bytes for row keys, and op_code for operations. Visible protocol
fields such as schema, table, key, op, row_version, and payload JSON are reconstructed
at the HTTP boundary.
sync.bundle_log is the durable committed-bundle metadata and accepted-push replay source. Bundle
history is retained only above each user’s retained_bundle_floor; below that floor, clients
recover through snapshot rebuild rather than best-effort historical replay.
Bootstrap Trust Boundary
Fresh layout creation and populated-table adoption are one fail-closed PostgreSQL transaction with exact post-persistence validation. After that transaction commits, restart attachment trusts PostgreSQL’s durable row state and validates only registered declarations plus the exact managed definition. It does not scan, rewrite, backfill, hash, reconcile, or repair business or operational sync rows.
Registered-table guards still reject ordinary direct DML without a bundle context and reject every
TRUNCATE. Privileged trigger bypass and direct managed-row edits are outside the supported trust
boundary; Bootstrap does not detect or repair them.
Server-Originated Writes
If your application writes registered PostgreSQL tables outside client push handling, it should do
so through ScopeManager.ExecWrite(...) in the common case, or WithinSyncBundle(...) if your
application needs the lower-level primitive directly. Both paths ensure the committed business
transaction is captured as one sync bundle visible to other clients. See
Server-Originated Writes for the
retryable database-only callback contract, durable operation identity, commit-ambiguity handling,
and examples.
Core Concepts
Core Concepts →
Read the core concepts page for the exact vocabulary used by the current contract.