Skip to content

Versions and compatibility

What 1.0.0 covers, why the HTTP API is the only compatibility surface, what the boot-time schema apply does and does not do, and why coming from 0.16 means a fresh database.

Updated View as Markdown

Queen MQ 1.0.0 is one broker binary plus a set of client SDKs released at the same version: JavaScript, Python, Go, PHP (Laravel), C++, and a Go command-line client. The broker reports its version on GET /health and in its boot log; the value is compiled in from a single file, so the binary, the container tag and the health document can never disagree.

The HTTP API is the compatibility surface

The contract is the HTTP API: the paths under /api/v1 and /streams/v1, their methods, their JSON field names, and the status-code behaviour described in Status codes and error bodies. An SDK is a convenience over that surface, not a separate contract: anything an SDK can do, a plain HTTP call can do.

Everything else is an implementation detail and changes without notice:

  • the SQL schema, the table shapes, and all stored-procedure names and signatures;
  • the routes under /internal/, which exist for broker-to-broker traffic;
  • the multi-broker mesh frame format;
  • the embedded dashboard bundle;
  • the boot log’s field names.

Two consequences follow from taking the JSON field names (rather than their meaning) as the contract. First, fields are added, not renamed: a client must ignore fields it does not know. Second, some fields are opaque by contract. seq and startOff on a pop response kept their names across the storage-engine rewrite while their meaning changed completely: seq now carries a segment’s base offset and startOff a frame index within it. A client that treats them as tokens to hand back is correct; a client that does arithmetic on them is not.

The schema is applied at every boot

The broker owns its own schema. It embeds schema.sql and 23 stored-procedure files (001023) into the binary at compile time and applies all of them on every start, in lexical file order, before it accepts a request.

Three properties make that safe to do on every boot:

  • Idempotent statements. CREATE TABLE IF NOT EXISTS, CREATE OR REPLACE FUNCTION, ADD COLUMN IF NOT EXISTS. Re-applying is a no-op when nothing changed.
  • Serialised across instances. The apply holds a session advisory lock, so a rolling restart of several brokers cannot run two DDL passes concurrently.
  • Fail-fast. Any DDL error aborts the boot with the failing file named in the message. A broker never serves against a schema it could not bring to the expected shape.

The apply only creates. Every table and every function is defined exactly once, by exactly one file, in its final shape: nothing is dropped, migrated or folded at boot, and schema.sql carries no upgrade ALTERs: the CREATE TABLEs declare the final columns directly. Order in the file list is load-bearing only where a function body has to resolve a table at creation time. The table files come first, and the partition-counter trigger attachment comes after both the table it attaches to and the functions it binds.

The deployment model is always-virgin

A deployment starts from an empty database (or an empty schema): the boot apply is the schema, not a migration engine. There is no migration step, no version table, and nothing to run by hand, because there is nothing to migrate. Restarting the same version re-applies the same definitions as a no-op, and a rolling restart of identical binaries is safe.

What the apply does not do is bring an existing database forward. A CREATE TABLE IF NOT EXISTS against a table that already exists in an older shape leaves the older shape in place, so pointing a newer build at a database created by a different build is unsupported: stand up a fresh database and cut over. The same applies to a downgrade: an older broker would re-apply its own older definitions over a schema it does not understand. Roll back by restoring a dump into a fresh database, not by restarting an older image against the current one.

One rolling-restart detail survives from the file mechanics: a definition whose return type changed between builds cannot be a CREATE OR REPLACE, so a few are expressed as a drop followed by a create (the pop family is the notable case), and the advisory lock does not stop an older instance from serving while a newer one applies. Roll instances one at a time, and do not point two different broker versions at one database as a steady state.

Running as a low-privilege user

QUEEN_APPLY_SCHEMA=0 skips the apply entirely. A privileged role then owns the DDL and pre-applies it, and the broker’s own database user needs no CREATE rights. The trade is that the ordering guarantee becomes yours: a broker that skips the apply and finds an older schema fails on the first request that needs a missing object, not at boot.

With the apply enabled, the database user needs CREATE SCHEMA and nothing else. The schema requires no extensions: gen_random_uuid() is used as a core built-in, not the pgcrypto function. It does require PostgreSQL 15 or newer, because one unique index is declared NULLS NOT DISTINCT. The test matrix runs against PostgreSQL 16.

Coming from the retired 0.16 broker

The 0.16 broker was a different implementation: C++, and a different storage engine. Replacing it with 1.0.0 is not a data migration, and there is no tool that makes it one.

There is no importer. The migrate subcommand still exists so the CLI entry compiles, and all it does is log migration is not supported on the log engine and exit 1. It never opens a database connection. There are no migration HTTP endpoints either.

The boot apply does not touch old objects. It only creates: nothing is dropped, altered or folded at boot, whatever the database already holds. That is not a compatibility promise, it is the always-virgin model: 1.0.0 expects an empty database, and against one carrying an earlier broker’s tables it would simply create its own objects alongside data it will never read or reconcile.

The practical upgrade is therefore a cutover, not an in-place migration: stand up 1.0.0 against a clean database, move producers over, let the old consumers drain the old broker, and keep a dump of the old database as the record.

Backup and restore are plain pg_dump and pg_restore. The broker offers no backup endpoint.

Mixed versions in a mesh

A multi-broker deployment can be rolled one instance at a time. Where a frame from an older peer omits information a newer instance expects (the tenant on a wake or invalidation frame), the newer instance fans the effect out rather than guessing: it wakes every tenant holding that queue name and drops every tenant’s cache entry for it. Over-waking costs one empty probe; over-invalidating costs one lazy re-fetch. Neither is a correctness problem, so a mixed-version mesh is safe.

Navigation

Type to search…

↑↓ navigate↵ selectEsc close