c885b5acf4
Measured on demo-felhom (N100, 16 GB): 61 apps deployed and removed through the product, sampled every 5 s; heavy apps with one real action (Immich 200 photos, Jellyfin transcode, Paperless OCR, Nextcloud 500 MB, ...). sizing.json for the estimator (tools/build_sizing.py), SIZING.md with the proposed minimum machine, METHOD.md. Power is RAPL CPU-package only (no meter). R-993 asks the operator to approve the minimum machine; R-994 lists catalog limits/requests that are too low; R-995 records leftover unnamed volumes after removal. R-231's dated check measured: dooplex-backup.service success, journal green.
88 lines
6.5 KiB
Markdown
88 lines
6.5 KiB
Markdown
# Method — sizing bench, 2026-10-13
|
||
|
||
**Bench:** `demo-felhom` — Intel N100 (4 cores), 16 GB RAM, 512 GB SATA SSD + 1 TB HDD, Proxmox VE; guest 9201
|
||
(3 cores, 12 GiB) running controller 0.309.0. Tier 0 (`runbooks/target-selection.md`). Baselines: felhom.eu
|
||
`c2a6cf7a58`, app-catalog `8ee687e544`, felhom-controller `1b2f74c52f`.
|
||
|
||
## Sources, in the spec's order
|
||
|
||
| # | Source | What it is | Where |
|
||
|---|---|---|---|
|
||
| 1 | telemetry | hub App Telemetry, last 30 days, one row per 15-min report; app = sum of its containers. Used when one box has ≥ 7 days | `evidence/telemetry-30d.json` (from a hub DB copy with `-wal`, freshness proven: newest host report 2 min old) |
|
||
| 2 | onboarding | measured peaks from earlier onboarding/bench sessions; per run, the app's containers summed, anon basis only | `evidence/onboarding-peaks.json` (collected by a read-only helper; every figure cites its file:line) |
|
||
| 3 | measured | this run, on the N100 | `evidence/<batch>/` |
|
||
| 4 | unmeasured | catalog `mem_request` / `mem_limit` | `.felhom.yml` |
|
||
|
||
Telemetry under 7 days is used only when nothing better exists (`short-telemetry`). When peaks from two sources differ by
|
||
more than 50 %, both are kept (`evidence/apps-table.json`) and the higher goes into `sizing.json`.
|
||
|
||
**Telemetry is mostly demo and test boxes at rest** (demo-hp, tester-1, demo-felhom): it shows idle use, not household
|
||
use. Its `peak` is the highest of 1-minute `docker stats` samples, not the true peak. Its CPU is docker's per-core
|
||
percent; divided by 4 here to approximate the N100 scale (other CPUs, approximate).
|
||
|
||
## The bench measurement
|
||
|
||
- **Deploy** through the product: the endpoints the dashboard calls (`GET /api/stacks/<n>/deploy-fields`,
|
||
`POST /api/stacks/<n>/deploy`), the box's own login + CSRF. The data location: the box has no registered drive, and the
|
||
deploy page then says „Nincs regisztrált adattároló — add meg kézzel az útvonalat”; we typed `/mnt/sizing-bench`
|
||
(the guest system disk — the controller container sees only `/mnt`). **Removed** through the product
|
||
(`stop`, `remove` with data and backups); the product keeps data it cannot resolve (R-442), so the folder was then
|
||
deleted by name. Every removal checked: no stack, no container, no volume (`evidence/*/run.log`).
|
||
- **Sampling every 5 s** from each container's cgroup v2 files in the guest (`tools/guest_sampler.sh`), and on the host
|
||
(`tools/host_sampler.sh`): memory, RAPL package energy, CPU temperature.
|
||
- **Memory, two numbers:** `used` = `memory.current − inactive_file` (what `docker stats` and the fleet telemetry show)
|
||
and `anon` (the app's own heap). `sizing.json` uses `used`. The kernel's own high-water mark (`memory.peak`,
|
||
includes page cache) is recorded too — it is the figure closest to what a container limit is measured against.
|
||
- **Windows:** *work* = from the app's deploy to the end of its rest; *rest* = the last 15 minutes, nothing deploying.
|
||
Typical = average of rest; peak = highest 5-s sample of work.
|
||
- **Batches:** heavy apps alone (deploy → one real action → 15 min rest). Light apps 6–7 per batch, deployed one after
|
||
another, then 15 min rest together. Per-container numbers are exact either way; host power in a batch is shared.
|
||
- **CPU:** `usage_usec` deltas, summed per app, as **% of the whole N100 (4 cores = 100 %)**. `cpu_peak_1m` = the busiest
|
||
60 seconds. Classes: **light** — rest < 1 % and busiest minute < 15 %; **heavy-in-bursts** — busiest minute ≥ 50 %
|
||
(two cores fully busy for a minute); **medium** — between.
|
||
- **The floor:** 15 min with only the platform running. One app (opengist, ~23 MB) was already deployed there and was
|
||
left alone; it is excluded from the platform figures.
|
||
|
||
## Actions (content: generated or CC0; no personal data — `tools/make_content.py`)
|
||
|
||
| App | Action |
|
||
|---|---|
|
||
| Immich | first user; upload 200 photos (140 generated 12 MP, 60 CC0 portrait paintings from The Met — faces without living people); wait until every job queue is empty |
|
||
| Jellyfin | startup wizard; library of three generated 1080p H.264 films; scan; one forced transcode (H.264 3 Mbit/s, CPU — no GPU passed through) |
|
||
| Paperless-ngx | 20 image-only A4 scans at 300 dpi through the API; wait until all are OCR'd |
|
||
| Nextcloud | first start; 50 × 10 MiB over WebDAV |
|
||
| Home Assistant | first start; onboarding (first user) |
|
||
| Calibre-Web Automated | 20 generated EPUBs into the ingest folder |
|
||
| RomM | 15 dummy files copied into the library; **the scan was not triggered** (it needs a websocket client) |
|
||
| Mealie, Docmost, AdventureLog | first start (inside their batch) |
|
||
|
||
## Power
|
||
|
||
RAPL package energy on the host throughout (`evidence/*/host.tsv`): **CPU package only — not wall power.** The wall
|
||
reading is the operator's STOP (§ power in `SIZING.md`).
|
||
|
||
## Limits
|
||
|
||
- One box, one run per app, 15 min at rest — a household's long tail (a nightly backup, a library of 50,000 photos,
|
||
several users) is not in these numbers. The Immich import is 200 photos, not a family archive.
|
||
- The batch neighbours share CPU during first start; a burst can be slower (and lower) than alone.
|
||
- Apps that need an account or key to do work (Usenet, the *arr set, Plex sign-in) were measured at rest only.
|
||
- The bench path is on the guest system disk; on a real box the data sits on a separate drive — no effect on memory.
|
||
|
||
## Mixing bases — a known softness
|
||
|
||
Onboarding peaks are **anon** (the app's own heap); telemetry and this bench are **used** (anon + active file cache).
|
||
`used` is usually the larger. Where onboarding is the chosen source, its peak can read low — the >50 % rule then lifts it
|
||
to the bench figure (e.g. Immich, Grimmory, Grafana); within 50 % it does not.
|
||
|
||
## Teardown — three layers
|
||
|
||
- **Machine (guest 9201):** all 61 bench deploys removed through the product, each checked (no stack, container or
|
||
labelled volume). After the queue the guest still held **8 unnamed Docker volumes** made by the bench (4–72 KB:
|
||
Immich 1, RomM 2, PrivateBin 3, OnlyOffice 2) and **3 empty import folders** the deploys created (`import/paperless`,
|
||
`import/calibre`, `import/grimmory` on the system drive). Recorded (`evidence/leftover-volumes-after-bench.txt`), then
|
||
removed by name — no prune. `/root/sizing` and the 1.8 GB of content deleted. End state = start state: 5 containers
|
||
(platform + opengist), 3 volumes, 12 images, opengist's own backup folder only.
|
||
- **Host (felhom-pve):** samplers stopped, `/root/sizing` deleted. Nothing else was touched.
|
||
- **Hub:** read only (a DB copy). The box reported its own telemetry for the bench apps, as any box does; no hub write.
|