Storage and backups
Twenty-one storage and backup lessons, mostly about the gap between a backup that runs and a backup that is proven.
Almost every lesson in this theme is a version of the same sentence. Something
reported success, and the success was not the thing anyone actually needed.
An scp that failed silently and a delete in the same command that ran
anyway. A photo library that held pointers rather than copies. A capacity
guard that logged the failure it had predicted and then carried on into it. A
backup datastore that had never had a job pointed at it and therefore never
failed. A lifecycle setting whose plain-English name described the opposite
of the protection it gave. In each case the signal an operator would naturally
look at, an exit code, a green job status, a quiet dashboard, a configuration
file on disk, was a claim rather than a proof.
The order matters, because the lessons built on each other. The first, on 20260723, cost real data and produced the rule that every later deletion in the project was gated on: transfer, verify, delete, as three separate observed steps. The lessons that followed were mostly about discovering where data actually lived: in RAM on an appliance NAS, inside a fixed-size container image, referenced in place by a media tool rather than copied, or on an offsite drive whose free space was being consumed by the best-protected data instead of the least. From 20260821 the work shifted from finding gaps to proving the absence of them, with a weekly restore rehearsal that restores real files and reports “skipped” as its own outcome. The autumn lessons (20260905 onward) came out of rebuilding the whole backup topology around a new central server, and are about invariants enforced in code rather than remembered: a destination never holds a backup of itself, a generated configuration is never hand-edited, a new host is not finished until the fleet-wide check can see it.
Declined #
- A resolver role moved with a server migration. It was a real fact corrected in the as-built capture, but a documentation placement error rather than an event with a cost, so no practice changed.
- The ceph8 bay-9 “dirty connector, two failure signatures” incident and the wider work on hidden drive health counters are genuine lessons. They are hardware and RMA content, though, and belong to theme 1, not here.
- Bootstrapping a nine-host Ceph cluster in a day and a half, and the hardware provisioning story around it, is compute and hardware-provisioning content. It is assigned to theme 1.
- A fleet backup repository count differed across documents over time (seventeen, then twenty, then sixty-nine). That is ordinary staleness in prose as the fleet grew. The verification sweep’s own live number is the authoritative one on any given day, and nothing changed beyond a note.
- A prune list on the old NAS had drifted (ten hosts with no retention policy, one entry with no repository behind it). It was a real incident, but a direct instance of an existing fleet-wide rule that hardcoded lists go stale silently, so it was declined as a duplicate.
- Four different hostname variants fragmented one machine’s backup history and were unified on 20260809. It was a data-hygiene rename with no cost incurred and no process change.
- A backup client’s host identity was pinned against reverse-DNS drift, fixed on 20260722. It was folded straight into the standard configuration baseline rather than surfacing as a dated incident with a measurable cost.
The lessons, in order
- 3.1Never delete in the same command as an unverified copy20260723as-is
- 3.2The library that is not a copy: an external-library rule for Immich20260724as-is
- 3.3A RAM root: a year of scripts that never survived a reboot20260726as-is
- 3.4The scratch directory setting that the helper program ignored20260726 to 20260801after redaction
- 3.5The append-only backup server's orphan-pack loop20260727 to 20260803as-is
- 3.6The comment character hiding at the front of a folder name20260806as-is
- 3.7Trash is a choice, not a default, and the two tools disagree20260806as-is
- 3.8The NAS boot flash failed, and the backup built to survive it20260808after redaction
- 3.9What an offsite drive protects, and the guard that let it overflow anyway20260818after redaction
- 3.10A hand-edited config that hides its own drift for weeks20260818as-is
- 3.11The exclude that used to be true20260818as-is
- 3.12A backup nobody has restored is a belief, and skipped is not passed20260821as-is
- 3.13Everything moves off the old NAS: the migration and its USB boot saga20260903 to 20260906after redaction
- 3.14The recursion rule: a backup of a backup is worse than no backup20260905as-is
- 3.15Rebalancing a ZFS pool: the math nobody runs before doing it anyway20260905after redaction
- 3.16The bucket that was declared purged and was not20260905, discovered stale and deleted 20260911after redaction
- 3.17Editing the config by hand took out both nightly backups at once20260909after redaction
- 3.18The PG autoscaler that silently did nothing, because two rules overlapped20260911after redaction
- 3.19One unique password, and a backup the verifier could not see20260917 to 20260918after redaction
- 3.20The setting named keep less was the one that removed the protection20260726 (original trap identified); corrected 20260920after redaction
- 3.21The second storage tier nobody was writing to20260920after redaction
Source: node0 lessons v0.1, section 3. Sanitized: checklist v0.1, 20260921; voice pass 20260921. Part of oznog.com/node0.
