Upgrading & Migration
This page records the compatibility facts the code actually guarantees. Anything not verified by a test or an explicit version check is marked untested — verify before relying rather than claimed as a guarantee. Source: the repo's upgrading guide.
Always back up first. Take and
relata verify-backup <path>a full snapshot before touching any node. Restore forward (old → new), not backward — backward format/version compatibility is untested.
Upgrading to 2.4.0
2.4.0 is not a breaking release in the semver sense — no format or SQL changes — but it carries several server/cluster-profile boot-time enforcement changes that will crash-loop an existing deployment upgraded in place unless the required env vars are already set:
RELATA_ERASURE_SIGNING_KEYis now a hard boot dependency onserver/cluster(#4797). The GDPR erasure-proof preflight nowFATAL-exits at startup if this key is unset — previously it only failed lazily, on the first erasure request. Before upgrading aserver/clusterdeployment, setRELATA_ERASURE_SIGNING_KEY.free-profile deployments are unaffected (an unset value still derives a key fromRELATA_BEARER_TOKEN, #1389). Data is never at risk — only availability (the container will not start until the var is present).- Lone weak-security opt-outs no longer boot (#4945).
RELATA_KMS_LOCAL_DEV(and siblingsRELATA_UNSAFE_LOCAL_FENCING,RELATA_GRPC_PLAINTEXT_OK) now requireRELATA_SECURITY_MODE=opento take effect onserver/cluster— set alone, theyFATALat boot instead of silently degrading security. If your dev/lab deployment (commonly copied from pre-2.4.0 docs or compose files) sets any of these, addRELATA_SECURITY_MODE=openalongside them before upgrading — or remove them if the opt-out was unintentional. RELATA_MAX_TENANTSnow clamps to the profile/license ceiling instead of overriding it (#4967). A deployment that set this above its actual license or profile ceiling to work around the old bug will silently get the lower, correct ceiling after upgrading — not a boot failure, but worth checking if you rely on tenant capacity near the limit./queryis read-only; writes go through the ingest door. This has been the engine's contract since #782, but pre-2.4.0 quickstarts (including earlier versions of this portal's) documented anINSERT-via-query()write path by mistake — 2.4.0's #4977 corrected them. Any script still sendingINSERT/UPDATEthrough/query'squery()/execute()path gets400 Purpose rejected: /query is read-only; use the SDK ingest client (IngestClient.bulk()/POST /ingest).
2.4.1 is a patch on top with no new boot gates — its headline fix is the org-isolation Guard 6 fallback to dynamic ownership records (#5088), so under multi-tenant mode a custom ontology type no longer needs to be pre-declared in RELATA_TYPE_OWNERS to be readable by its own owning tenant. Full details: the project's CHANGELOG.
Validate the exact upgrade shape you use (container recreated on preserved volumes, or a fresh deploy pointed at existing storage) against a staging copy first — see Rolling-upgrade ordering for the general procedure.
Upgrading to 2.2.0
2.2.0 is not a breaking release — no format, config, or SDK changes — but it carries one fix that repairs a broken deployment class:
- Release Docker images now embed the production license-verification key (#4694). Every published image ≤ 2.1.0 (
ghcr.io/relatadb/relataand the Docker Hub mirror alike) was built with a dev/test fixture key embedded, so every full (non-trial) license issued by Relata failed activation on release images withnode configuration signature invalid: signature mismatch— while the self-issued 30-day trial (which uses its own embedded key) worked, which is why this went unnoticed. If you run a release image with a purchased license, upgrade to ≥ 2.2.0 — there is no workaround, and re-issuing the license does not help. Builds from source are unaffected. - Creating a tenant is now idempotent at the
max_tenantscap (#4689). Re-POSTing an already-existing tenant id once the tenant count is at the license/profile cap succeeds as a no-op instead of returning a spurious403 license-limit. "Create tenant if absent" bootstrap scripts are safe on every restart now; only a genuinely new tenant over the cap is rejected. See Multi-tenancy.
Full details: the project's CHANGELOG.
Upgrading to 2.1.0
2.1.0 is not a breaking release — no format, config, or SDK changes — but it carries two security fixes worth knowing about before you upgrade, since they change what previously worked as a credential:
RELATA_BEARER_TOKENno longer authenticates the HTTP data plane, with no opt-out. In earlier 2.x builds this was an opt-in-hardened behavior; it is now mandatory on every profile. If anything in your deployment was still using the raw bearer token against/query,/ingest,/search, etc. (rather than a registry-minted, tenant-scoped token fromPOST /admin/tokensor/tokens/self/*), it will start getting401s after upgrading — mint and switch to a tenant-scoped token first. See Auth & Security.- Tenant self-service token routes (
/tokens/self/list,/tokens/self/:id,/tokens/self/audit) no longer accept a sys-admin credential. If any tooling was relying onRELATA_ADMIN_TOKENworking there, switch it to the tenant's own token, or to the loopbound/admin/tokens*routes for cross-tenant management.
Also in this release: POST /admin/tokens can now bootstrap the first tenant-scoped credential for a brand-new tenant directly (pass tenant_id in the request body as the static admin) — see Reaching the admin listener from a second (sidecar) container if you need a durable credential for a long-running second container/service. Full details: the project's CHANGELOG.
Upgrading to 2.0.0
2.0.0 is a major release with breaking changes (semver-mandated by the Zero-Trust / licensing / tenancy work). Review before upgrading from 1.x:
- Multi-tenant gating.
RELATA_TENANCY_MODE=multinow FATALs onfreeandserver(both fixed atmax_tenants=1). Multi-tenant mode is cluster-only and requires an effectivemax_tenants > 1(license value, or theRELATA_MAX_TENANTSoverride). If you ranmultionfree/serverin 1.x, move toclusterwith a multi-tenant license or stay onsingle. See Deployment. - Licensing model v3. The binary
"unlimited-storage"capability is gone. Licenses carry two numeric parameters on the signedNodeConfig—storage_max_gbandmax_tenants(0= unlimited). Re-issue licenses with the v3 tool; old.licfiles without these fields are rejected. See Licensing & Tiers. - Zero-Trust admin surface.
/admin/*and/platform/*moved to a separate loopbound listener (RELATA_ADMIN_BIND, default127.0.0.1:9091); they are no longer served onRELATA_HTTP_BIND. SetRELATA_ADMIN_TOKENand reach the surface via port-forward / sidecar. - Auth posture uniform. No more implicit
free-profile dev bypass — setRELATA_OPEN_DEV_ALLOWED=trueexplicitly for unauthenticated local dev, on every profile. - Go SDK →
github.com/relatadb/sdk-go/v2. The major-version import-path suffix is mandatory; update your imports. See SDKs. - Version lockstep. Server and all SDKs/tray/Grafana/Helm now ship one version, enforced by the repo's
check_versions.py.
Validate on a staging copy of your data first; back up before upgrading.
On-disk / artefact format compatibility
Manifest (object-store layout)
relata-storage::manifest::ManifestVersion is the source of truth for the commit-manifest layout. Two variants exist: V1Single (legacy single file) and V2Sharded (sharded layout) — new writes always emit V2Sharded. Readers detect the version from the index object. There is no in-place V1→V2 rewriter; the migration story for an existing V1 store is untested — verify before relying (prefer a fresh V2 store seeded from a restore over an in-place flip).
Backup snapshot format
relata-storage::backup::BackupPayload is a self-describing JSON artefact (full-<ts>.json / incr-<ts>.json) with schema_version: u32 (currently 1). Cross-tenant restores are enforced (assert_agency aborts a payload/organisation mismatch). Tenant-scoped restore (POST /admin/restore {"tenant": "..."}) is rejected outright (501) — the store-swap is an unconditional whole-store replacement, so a tenant-scoped request would silently wipe every other tenant. Restore only without a tenant field until a true per-tenant merge ships.
Restoring a backup taken by a newer build into an older binary is untested — verify before relying. Always run relata verify-backup <path> before depending on a snapshot.
Ontology version monotonicity
relata-ontology::OntologyVersion(u64) is a monotonically-increasing counter, bumped on every type addition/modification and stored in the commit manifest. The version only ever moves forward within a branch; it never rewrites history. Downgrading the binary does not roll the ontology version back — an older binary reading a higher ontology version is untested — verify before relying.
Rolling-upgrade ordering (recommendation)
This ordering is a recommendation, not an enforced/tested invariant:
- Back up first — take and
verify-backupa full snapshot before touching any node. - Validate config — the CLI fails fast (exit
78,EX_CONFIG) on an invalidRELATA_*enum/numeric value. Apply config changes to one node and confirm it starts clean before rolling out. - Upgrade followers before the coordinator/writer — in a cluster, roll reader/follower nodes first so the write path stays on the known-good version longest; promote the coordinator/writer last.
clusterprofile only — drain the node before stopping it. Runrelata cluster drain <node-id> --waitand confirmsafe_to_stop: truebefore deleting/restarting a writer's pod. Otherwise the passive heartbeat-timeout rebalancer (RELATA_CLUSTER_DEAD_AFTER_SECS, default 90 s) may evict and re-move partitions during a slow restart — real, avoidable data movement.- One pod at a time — the operator's StatefulSet uses
RollingUpdate(default one pod at a time, orspec.upgrade.maxUnavailable). Wait forGET /health/ready200 and (cluster) forrelata_replication_lag_secondsto settle before continuing. - Roll back by restoring, not by downgrading in place — backward format/version compatibility is untested.
Config migration
relata config --migrate # migrate relata.toml / env vars across versionsThe CLI now fails fast on invalid RELATA_* enum/numeric values and logs the offending variable, so config drift surfaces at startup rather than at runtime.
Data migration
| Path | When | Docs |
|---|---|---|
relata import --from postgres|csv | Migrate an existing database into Relata | Connectors & Extensions · Ingestion |
relata config --migrate | Migrate config across versions | above |
| Embedding-model migration | Change the sidecar model / vector dimension | LLM & Embedding configuration |
| Backup → restore | Whole-store migration / disaster recovery | Backup & Restore |
For Neo4j / MongoDB / ClickHouse, relata import --from <source> is an honest stub today — each prints the documented CSV/NDJSON export workaround and exits non-zero.
v1.4.2 → v1.5.0 (reference)
A backward-compatible upgrade. /query responses added processing_time_ms alongside elapsed_ms (the latter retained for one release, removed in v1.6.0). QueryError exposed stable REL_* codes; the RFC 7807 type URI changed from about:blank to https://relatadb.dev/errors/{code}. 429 responses added X-RateLimit-* headers. Admission control moved to a real cost estimate (row count × join multiplier). Rolling: upgrade reader/indexer nodes first, writers last; all in-flight writes are safe (formats compatible).
See also
- Deployment — the three profiles and profile-specific gates
- Backup & Restore — snapshots,
verify-backup, restore semantics - Connectors & Extensions —
relata import --frommigration connectors - Licensing & Tiers — the two-parameter licensing model
- Cluster Setup — graceful-restart / drain procedure