Files
felhom.eu/documentation/audits/sizing-2026-10-13/METHOD.md
T
admin c885b5acf4 Audit: what a Felhom box needs — floor, 64 apps' memory and CPU, power (N100 bench); R-993..R-995; R-231 due check read green
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.
2026-10-13 13:23:51 +02:00

6.5 KiB
Raw Blame History

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.