SQLStreams

the messaging platform that is just Postgres

You last visited on 9999-99-99 Show what's new since then

Upgrades & Migrations

Edit this page
Posted: 2026-09-12 · Report this thread
brandon Site Admin brandon profile Posts: 677

SQLStreams’s tables live in your Postgres database, so upgrading SQLStreams can mean migrating schema. This page is the contract for how that works: what a version is, which upgrades are safe to roll, and how a binary refuses — early and legibly — when it can’t run against what it finds.

Two version counters

The schema versions in two independent scopes, both starting at v1:

  • System scope — the shared control-plane tables (the stream catalog, the worker fleet). One counter for the whole database.
  • Stream scope — the per-stream table families (message log, deliveries, cursors, leases). Each stream carries its own counter, so streams can sit at different versions mid-migration without breaking each other.

Both counters read off the client: client.System().MigrationVersion(ctx) for the system scope, client.Stream[T](name).MigrationVersion(ctx) for one stream. sqlstreams migrate status prints every counter beside the version the binary was built with.

CLI

Migration commands take --target-version, matching the client’s targetVersion argument. For example, sqlstreams migrate stream up orders.created --target-version 1 targets version 1 for orders.created. The flag is required for both up and down; migrate versions lists the versions the binary supports. JSON mutation results name the target target_version. Migration status reports registered, including false when the system has not been registered.

These are SQLStreams’s internal DDL versions. They are unrelated to the SchemaVersion your Message types declare, which versions your message payloads row by row (schema versions); no SQLStreams migration ever touches your payloads.

The gate

Every producer, consumer, and worker checks both scopes at Register and refuses to start rather than run against a shape it doesn’t understand:

  • Database older than the binary — the binary requires schema the database doesn’t have yet: schema version is older than this build requires — migrate the database up first (SQL0022).
  • Database migrated past what the binary tolerates — a newer release applied a breaking migration: schema version is newer than this build understands — upgrade the binary (SQL0023).

Rolling deploys: the minimum compatible version

Every migration step declares its minimum compatible version — the oldest release whose SQL still runs correctly against the schema the step produces. Most steps are additive (a new column, a new table) and declare no requirement at all; a breaking step names the floor it raises.

The gate admits a binary when no applied step requires anything newer than it. That is what makes the standard rolling upgrade safe:

  1. Migrate the database with the new release.
  2. Roll binaries at your own pace — old instances keep producing and consuming through the window, because every step past them is additive.

When a release does carry a breaking step, its notes say so, and the order inverts: stop the old binaries first, migrate, then start the new ones. The gate refuses old binaries that try to start after the migration, but a binary already running is past its gate — stopping it first is your job, not the machinery’s.

Transactional migration steps run under a 2-second lock_timeout, so a step queued behind your traffic gives up and retries instead of stalling the queries queued behind it. When contention outlasts the retries, the run stops with SQL0053 naming the blocking session as the thing to clear.

Which builds a database admits

The rule is two comparisons, but its consequences are easier to see than to read. Each cell is the gate’s answer for one pair — a binary built at some schema version, a database sitting at another. Both scopes sit at v1 today, so there is exactly one pair to ask about:

Stream scope — v1
build v1
database v1runs

The grid gains a row and a column per migration step, and settles into three regions:

  • The diagonal — binary and database agree. Always admitted.
  • Over the diagonal — the binary expects schema the database doesn’t have yet. Always SQL0022: migrate the database up.
  • Under it — the database is ahead of the binary. This is the rolling-deploy window, and it reaches exactly as far back as the newest breaking step allows. A run of additive steps leaves it wide; one breaking step closes it to a single column, then it reopens at the new floor.

No verdict is drawn by hand. A build-time export walks every pair through the same comparison the library runs at Register, so this page holds no second copy of the rule that could drift from the one your binaries enforce.

The grid reads a database that reached its version by applying steps in order. A rolled-back database records the downgrade, so its floor is whatever the steps at or below its current version declare — the gate reads the recorded trail, never the declared one.

Compatibility table

One row per release that changes schema, kept current with every release and verified mechanically (a compatibility harness drives the prior release’s binaries against a freshly migrated database before a release ships).

ReleaseSystem schemaStream schemaMinimum compatible releaseNotes
pre-1.0v1v1Pre-release: schema changes edit the baseline in place; recreate the database on upgrade.

Until v1.0 ships there is no migration trail: each pre-release build defines the baseline schema outright, and upgrading means recreating the database. The table starts earning rows with the first post-v1.0 schema change.