Files
felhom.eu/documentation/tests/campaign10-evidence-2026-07-31/rto-m-band-2026-08-02.md
T
admin 5f35aa0346 Campaign 10: M-band RTO measured — RTO ~= 40s + 26.9s/GB, and a capacity ceiling that matters more
The S figures (66 MB -> 42.0s, two passes agreeing to 0.6s) had a spread tight
enough to prove fixed work dominates, which is exactly why they said nothing about
M. Second point taken 327x larger, same app, same method: clock from restore
request to the app serving the correct discriminator.

rallly's postgres volume grown 66 MB -> 21.1 GB (200k rows, STORAGE EXTERNAL so
TOAST cannot compress it into a fake number). Two reps:

  rep 1  backup 406.4s  unit 41149 MB  RTO 624.5s  discriminator correct
  rep 2  backup 387.2s  unit 41133 MB  RTO 591.8s  discriminator correct

327x the data cost 14.5x the time - strongly sub-linear:
  RTO ~= 40s + 26.9 s/GB      backup ~= 29s + 17.4 s/GB
  10 GB -> 5.2 min   20 GB -> 9.6 min (measured 10.1)   100 GB -> 46 min
The fixed ~40s dominates below ~1.5 GB, which IS the S band and explains its tight
clustering.

The more consequential result is capacity. A DB-backed app's recovery unit is
1.90x its data (volume tar PLUS SQL dump): 21.1 GB produced a 40.2 GB unit. The
default appliance ships /mnt/sys_drive at 20 GB, so the largest app that can hold
a local Tier-1/2 recovery unit on a default box is about 10 GB - and that fills the
volume. The M band does not fit on a default box at all; this test only reached
21 GB because sys_drive was first grown 20G -> 70G with the same operation the
product performs via SysDataGrowGB. A tier-sizing decision, not a defect, but it
is invisible until an app crosses it.

Caveats stated in the doc: two points define a line but do not test linearity; the
1.90x is DB-app-specific and a file-only app should be nearer 1.0x (inferred, not
measured); synthetic incompressible data; one app, one box.
2026-08-02 08:31:51 +02:00

77 lines
3.8 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# RTO — the M band, measured (2026-08-02)
The S-band figure (66 MB → 42.0 s, two independent passes agreeing to 0.6 s) had a spread tight
enough to prove **fixed work dominates**, which is exactly why it said nothing about M. This adds a
second point 327× larger, measured on the **same app, the same way**: clock from the restore request
to the app **serving the correct discriminator**, read over the path `DATABASE_URL` actually names.
## Method
`rallly`'s postgres volume grown 66 MB → **21.1 GB** by inserting 200 000 rows into a `cc_bulk` table.
`ALTER TABLE … SET STORAGE EXTERNAL` is load-bearing: without it TOAST compresses `repeat(...)` to
almost nothing and the volume never really grows, which would have produced a fake M-band number.
The `cc_proof` canary row is untouched, so the discriminator is unchanged.
`/mnt/sys_drive` was first grown 20 G → 70 G with `pct resize` — the same operation the product
performs via `SysDataGrowGB` (`felhom-agent/internal/reconcile/bringup.go:426`). **It had to be**:
see the capacity ceiling below.
## Results — 2 reps, both returning the correct discriminator
| rep | volume | backup | recovery unit | **RTO to correct data** | discriminator |
|---|---|---|---|---|---|
| 1 | 21 616 MB | 406.4 s | 41 149 MB | **624.5 s** | correct |
| 2 | 21 615 MB | 387.2 s | 41 133 MB | **591.8 s** | correct |
| **mean** | **21.1 GB** | **396.8 s** | **40.2 GB (1.90×)** | **608.1 s (10.1 min)** | **2/2** |
## The two-point model
| | 66 MB (S) | 21.1 GB (M) | ratio |
|---|---|---|---|
| data | ×1 | **×327** | |
| RTO | 42.0 s | **608.1 s** | **×14.5** |
Time grew 14.5× for 327× the data — strongly sub-linear, i.e. a large fixed cost plus a
byte-proportional term:
> **RTO ≈ 40 s + 26.9 s/GB** · **backup ≈ 29 s + 17.4 s/GB**
| size | projected RTO |
|---|---|
| 1 GB | 67 s |
| 5 GB | 2.9 min |
| 10 GB | 5.2 min |
| **20 GB** | **9.6 min** (measured **10.1 min** — the model is fit through the mean) |
| 50 GB | 23.1 min |
| 100 GB | 45.5 min |
**Where the bands actually break.** The fixed ~40 s dominates below roughly **1.5 GB** — that is the S
band, and it is why the S numbers were so tightly clustered. Above it the per-GB term takes over and
RTO becomes essentially linear in data.
## The capacity ceiling — the more consequential result
**The recovery unit is 1.90× the app's data**, because for a DB-backed app it carries *both* the
volume tar and the SQL dump: 21.1 GB of data produced a **40.2 GB** unit.
The default appliance ships `/mnt/sys_drive` at **20 GB** (`mp1 … size=20G`, the golden's baked size;
`SysDataGrowGB` is a per-customer grow that was 0 for this customer). So on a default box:
> **the largest app that can hold a local Tier-1/Tier-2 recovery unit is ≈ 10 GB — and that fills the
> volume completely.** A realistic ceiling is nearer **8 GB**.
**The M band does not fit on a default box at all.** This test only reached 21 GB because
`/mnt/sys_drive` was grown to 70 G first. That is a sizing decision for the S/M/L tiers, not a defect
— but it is the constraint that actually bites, and it is invisible until an app crosses it.
## Caveats — stated, not buried
- **Two points define a line; they do not test linearity.** A third point (~5 GB) would confirm the
model's shape. Not taken.
- **The 1.90× ratio is DB-app-specific.** It is volume-tar + SQL-dump. A file-only app (sqlite, no DB
container) should be nearer 1.0×, but that was **not measured** and is inference.
- **Synthetic data.** `repeat(md5(...))` stored uncompressed is deliberately incompressible; real app
data that compresses well would move both the unit size and the per-GB rate.
- One app, one shape, one box. The fixed term in particular is a property of this stack's stop/start
and health-wait, which differs per app.