With logs off flash, the dominant remaining eMMC writer is zwave-js-ui: its
value DB is fsync'd on every value change (jsonl-db; zwave-js-ui doesn't
expose the driver throttle). On a busy network that fsync storm churned
~2.6 GiB/day for ~0.1 KiB/s of real data — near-pure write amplification
(each fsync forces a block write + ext4 journal commit).
Preload libeatmydata into the zwave-js container (musl .so extracted from the
image's Alpine base, mounted ro, LD_PRELOAD) so fsync/fdatasync become no-ops
and the kernel batches writes via normal writeback. The WS/MQTT value stream
to HA and the on-disk cache are unchanged — full data resolution, no
re-interview on restart. No ext4 mount option can do this (fsync is a
durability contract; commit=/data=writeback/nobarrier don't batch it, and the
only mount-level alternative — tmpfs — makes the whole store volatile).
Also:
- noatime -> noatime,lazytime on root: batches the inode-timestamp writeback
(e.g. the zwave-js lockfile heartbeat) without weakening data durability.
- edge-zwave-settings-backup.timer: hourly validated rolling backup of
settings.json (Z-Wave network keys), since neutralized fsync means a
power-loss mid settings-write could truncate it. Value-cache loss on crash
is harmless (devices re-report; jsonl-db tolerates a partial trailing line).
Verify the drop on the eMMC-write monitor; rollback = drop the LD_PRELOAD env
+ .so mount from the compose file.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>