Repository navigation
backup (stage C, enterprise): include cold-tier objects in backups and restores #1086
Description
⚠️ Internal work only. Community contributions are not being accepted for this issue.This is part of a four-stage piece of planned work on backups (per-database scoping, remote targets, cluster safety, cold tier) that Basekick Labs is implementing internally. Please do not open PRs against it; they will be closed without review. Discussion and questions in the comments are welcome.
Stages: 0 cluster safety #1083 → A database scoping #1084 → B remote targets #1085 → C cold tier #1086. Order is fixed; A and B can ship to OSS before 0 only if the docs say "not for clusters yet", and the node gate from 0 lands before any of them is advertised to a cluster. Origin: a prospect asked for a different backup target per database (their audit database to its own bucket). For an audit database, long retention and mostly cold, the answer is complete only with C.
Summary
A backup walks the hot backend only (dataStorage.ListObjects(ctx, ""), internal/backup/backup.go:114). Cold-tier objects live in a separate backend and tiering deletes a migrated file's Raft manifest entry (cmd/arc/main.go:4533-4558), so cold files are in neither the listing nor the manifest. For an audit database with long retention, most of the data is cold, so a per-database backup of it would be mostly empty today and the operator would not notice until a restore. The remote-targets stage makes that visible with an INCOMPLETE marker; this stage makes the backup contain the data.
Changes
- The cold walk cannot reuse the cluster-safety stage's manifest cross-check, since cold entries are gone from the manifest. It walks the cold backend for the scoped databases guided by the tier rows (
internal/tiering, per-node since fix(tiering): keep a cluster node's tier metadata in step with its own disk #1062; the rows on a just-promoted primary may lag, so the walk reconciles rows against the cold listing and reports the difference). - The backup manifest records the tier per file.
- Restore writes cold files back to the node's cold tier when it has one, recording the tier row, or to hot otherwise, reported as a count; the query layer routes from tier rows, so the row is what makes the restored file queryable.
- Per-target routing applies to cold files the same way as hot ones.
- Enterprise: tiering is licence-gated, so this stage inherits the gate; no new
CanUseX.
Matrix rows
| Configuration | Expected |
|---|---|
| Standalone with tiering, scoped db partly cold | hot and cold files both in the backup; manifest records tier |
| Restore onto a node with a cold tier | cold files to cold, rows recorded |
| Restore onto a node without a cold tier | cold files to hot, counted |
| Pattern 1, primary just promoted, tier rows lagging | walk reconciles rows against the cold listing; difference reported |
| Pattern 2 | cold bucket is shared; files written once |
| Cold backend unreachable mid-run | run fails; stated in status |
Acceptance
Regression tests that fail on main for the cold walk, tier recorded per file, both restore destinations. A binary run against the SeaweedFS cold tier with a scoped database that has cold rows, backup then restore, verify the measurement is queryable without a manual tiering scan. Release note in RELEASE_NOTES_2027.01.1.md; docs.
Activity
feat(backup): carry the cold tier in backups and restores (Basekick-L…