---
title: "Versions and compatibility"
description: "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."
---

> Documentation Index
> Fetch the complete documentation index at: https://queenmq.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Versions and compatibility

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](/reference/errors). 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 (`001`–`023`) 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 `ALTER`s: the
`CREATE TABLE`s 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.

> **Danger**
>
> Pointing 1.0.0 at a database that carries any earlier broker's schema (the 0.16
> engine's tables, or any pre-release build of this one) is **unsupported**. The
> boot will not drop the old data, but nothing reads it, nothing migrates it, and
> same-named tables left in an older shape make the broker's behaviour undefined.
> Give 1.0.0 a fresh database, full stop, and keep a `pg_dump` of the old one as
> the record.

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.

> **Danger**
>
> This tolerance is not authentication. Post-handshake mesh frames are
> unauthenticated JSON, and the frame set includes one that flips maintenance mode.
> Firewalling the mesh port is a requirement, not a recommendation. See
> [Self-hosting](/selfhost).

## Related pages

- [Environment variables](/reference/config) — QUEEN_APPLY_SCHEMA, the mesh variables, and everything else the boot reads.
- [Self-hosting](/selfhost) — Deployment, database privileges, TLS termination and the mesh.
- [Internals](/internals) — The log engine, segments, offsets and the maintenance steps.

Source: https://queenmq.com/reference/compatibility/index.mdx
