Skip to content

20260824 - Move tar1090's /run to tmpfs to stop ~7 GB/day of SD-card writes - #32

Merged
Purple10101 merged 1 commit into
mainfrom
20260824-tmpfs-run-sd-endurance
Aug 24, 2026
Merged

20260824 - Move tar1090's /run to tmpfs to stop ~7 GB/day of SD-card writes#32
Purple10101 merged 1 commit into
mainfrom
20260824-tmpfs-run-sd-endurance

Conversation

@Purple10101

Copy link
Copy Markdown
Collaborator

Closes the endurance half of 86cb8m8j8.

Problem

Every node writes ~7–8 GB/day to a consumer SD card for no functional benefit. The tar1090 container declares tmpfs for /var/log only, so /run/readsb, /run/tar1090, /run/graphs1090 and /var/lib/collectd all sit on the container overlay — the card. On a normal bare-metal tar1090 install /run is already tmpfs and none of this ever touches storage.

The waste is not the data, it's where it lands. readsb produces ~8 KB/min of real JSON and costs ~2.4 MB/min of flash writes — roughly 300× write amplification, because each tiny file still dirties a full 4 KB block plus a journal entry. readsb measured a flat 3.34–3.36 GB/day across four nodes, all with zero aircraft, which is what proves these writes are cadence rather than traffic: they will never improve on their own.

Changes

  1. /run as tmpfs (exec required — s6-overlay execs service scripts from /run/service and /run/s6-rc)
  2. GRAPHS1090_REDUCE_IO=true — runs the RRD set from /run/collectd, writes back one gzipped tarball instead of 48 in-place updates/min. Only effective because /run is tmpfs; alone it would move writes from one part of the card to another.
  3. Blank the adsb_source default — it shipped as 192.168.8.183, a developer's LAN address no deployed node can reach and every node has been repeatedly connecting to.

The ADS-B pathway is deliberately unchanged. readsb still writes aircraft.json at 1 Hz and the proxy still reads that file first on every request, so a local receiver behaves exactly as before — only the backing store differs.

Results — 46-hour soak on owl-ded9 and jonathan-node-1

Process owl-ded9 jonathan-node-1
collectd 5.65 → 0.000 GB/day 5.933 → 0.000 GB/day
readsb 3.36 → 0.088 3.362 → 0.095
graphs1090 0.59 → 0.000 0.615 → 0.000
Block layer 4.39 → 0.712 6.317 → 0.793

RAM available went 501 → 571 MB and 773 → 774 MB — the tmpfs costs ~35–40 MB but frees more in overlay page cache. /run grew ~1 MB over 46 hours. Zero restarts, all endpoints 200, map and graphs rendering.

Reviewer notes

  • /run sizing is validated against two nodes only. RRD sets vary (23M owl, 30M jonathan-node-1), putting the latter at 63% of 64M. A node with a larger set could approach the cap, and a full /run breaks collectd with ENOSPC. 96M would cost nothing (size= is a cap, not a reservation) — I kept 64M because that's what was soaked, but it's worth changing before fleet rollout.
  • The adsb_source change is untested on hardware. Verified end-to-end through the merger (no READSB_NET_CONNECTOR emitted when blank, still emitted when set), and 19/19 config-merger tests pass, but no node has run with it.
  • Not addressed here: fstrim still has never run on any node, and GRAPHS1090_REDUCE_IO_FLUSH_IVAL is still the 1-day default (1h would cut the unclean-shutdown loss window 24× for ~70 MB/day).
  • Separately discovered: the tar1090 container declares no docker volumes at all, so graph and heatmap history has never survived a container recreation. Needs its own ticket.

🤖 Generated with Claude Code

…writes

Every node writes ~7-8 GB/day to a consumer SD card for no functional benefit.
readsb rewrites a 67-byte aircraft.json at 1 Hz forever - whether or not any
aircraft exist - and collectd rewrites 48 RRD files every 60s. All of it landed
on the container overlay, i.e. the card, because the tar1090 container declared
tmpfs for /var/log only. On a normal bare-metal tar1090 install /run is already
tmpfs and none of this ever touches storage; the container is the anomaly.

The waste is not the data, it is where it lands. Measured across four nodes,
readsb costs a flat 3.34-3.36 GB/day to produce ~8 KB/min of real JSON - about
300x write amplification, because each tiny file still dirties a full 4 KB ext4
block plus a journal entry. The figure being identical on nodes with zero
aircraft is what proves the writes are cadence, not traffic: they will never
improve on their own, on any node.

Mount /run as tmpfs and enable GRAPHS1090_REDUCE_IO, which runs the RRD set
from that tmpfs and writes it back as a single gzipped tarball instead of 48
scattered in-place updates per minute. REDUCE_IO uses /run/collectd as its
working directory, so it is only effective *because* /run is now tmpfs -
enabled on its own it would relocate the writes from one spot on the card to
another. exec is required on the mount: s6-overlay execs its service scripts
from /run.

The ADS-B pathway is deliberately unchanged. readsb still writes aircraft.json
at 1 Hz and the proxy still reads that file first on every request, so a node
with a local receiver behaves exactly as before - only the backing store
differs. Gating readsb off when no receiver is configured was considered and
rejected: it saves ~5 MB of RAM, and it would put the local-receiver case on a
separate rarely-exercised code path, which is how a hardcoded developer IP
reached production here in the first place.

Also blank the adsb_source default. It shipped as 192.168.8.183,30005,beast_in
- a developer's LAN address that no deployed node can reach, and that every
node in the fleet has been repeatedly opening TCP connections to on whatever
customer network it sits on. Local-receiver support lives in the config key,
not in the default value, so blanking it costs no capability: the merger still
emits READSB_NET_CONNECTOR whenever adsb_source is set.

Verified on owl-ded9 and jonathan-node-1 over a 46-hour soak:

    block layer   6.32 -> 0.79 GB/day  (jonathan-node-1)
    block layer   4.39 -> 0.71 GB/day  (owl-ded9)
    collectd      5.93 -> 0.00 GB/day
    readsb        3.36 -> 0.10 GB/day  (residual is /var/globe_history,
                                        which stays on flash by design)

RAM available was unchanged or improved on both - the tmpfs costs ~35-40 MB but
frees more than that in overlay page cache. /run held steady at 36-40M of 64M
across 46 hours, growing ~1 MB. 64M is sized for the RRD set, which varies by
node (23M on owl, 30M on jonathan-node-1); 32M would not have been enough.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@Purple10101
Purple10101 merged commit 5c3750f into main Aug 24, 2026
1 check passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant