feat: automatic node eviction on low disk - #138
Conversation
Production nodes are running out of disk space and rejecting chunk uploads. When free space at a node's data directory falls to 500MB (the same reserve at which ant-node itself refuses to store), the daemon now automatically stops the smallest co-located node and deletes its data directory to reclaim space for the others. The smallest node is chosen to minimise the re-replication cascade on the network. A node that is the only one on its partition is never evicted; that surfaces as critical fleet health instead, so an operator can act manually. Evicted nodes get a persisted `evicted` status with an explanatory reason that survives daemon restarts, are never restarted, and can be dismissed from the list. An extensible fleet-health layer (green/warning/critical) surfaces disk pressure ahead of time and names the next eviction candidate; it is shown in `ant node status` and served at GET /api/v1/health. - Add NodeStatus::Evicted + persisted EvictionRecord on NodeConfig (serde-default, backwards compatible) - Add daemon/disk.rs: per-partition measurement + smallest-node candidate selection (shared by the monitor and the health layer so they never disagree) - Add daemon/health.rs: extensible FleetHealth model, disk check first, fixed 500MB/1024MB internal constants (deliberately not user-configurable) - Add the eviction monitor (stop -> delete data dir -> persist marker -> emit event), NodeEvicted and FleetHealthChanged events, and GET /api/v1/health - Add `ant node dismiss <id>` and a fleet-health summary line in `ant node status` Tested: cargo test -p ant-core is green, including the new disk/health/eviction/types unit tests and the daemon integration test. The only workspace failure was an unrelated OOM/SIGKILL on the gigabyte-scale e2e_huge_file upload test (client data path, untouched by this change). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
An evicted node's data directory has been deleted, so trying to start it fails with a confusing "No such file or directory", and `start-all` reports it as a failure. Treat evicted nodes as non-actionable: `start-all`/`stop-all` skip them silently (they stay visible as `Evicted` until dismissed), and single start/stop return a clear message pointing at `ant node dismiss`. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Guards backward compatibility of node_registry.json across the evm-network and eviction changes: a registry written by an older daemon (carrying the removed network_id/metrics_port fields and lacking evm_network/eviction) must still load, with the unknown fields ignored and the new ones defaulted. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
On Windows a just-killed node can keep its data files open for a moment after exit (its LMDB memory map and its own copied ant-node binary), and antivirus/indexers can grab transient handles, so remove_dir_all fails with "access denied / in use". The eviction then stopped the node without reclaiming any space, and the reason text falsely claimed it was reclaimed. Retry the delete with bounded exponential backoff (~4.5s) so the OS has time to release the handles; on Unix the first attempt still succeeds. If deletion ultimately fails, the node is still marked Evicted (its process is gone) but the reason text and reclaimed_bytes reflect that no space was reclaimed, and the failure is logged at error level. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Hermes review-team summaryVerdict: hold for two eviction-safety fixes. I reviewed this together with a 6-agent review team and then independently verified the concrete findings locally. Blockers
The same function uses Suggested direction:
Suggested direction:
Related existing issue, not introduced here
CI / non-blocking notes
|
Addresses the review on WithAutonomi#138. - Eviction candidate sizing (disk.rs): rank and report by actual reclaimable disk usage. dir_size now sums allocated blocks (blocks() * 512 on Unix) instead of logical file length, so LMDB's sparse data.mdb no longer mis-ranks the "smallest" node or misreports reclaimed bytes. It also uses symlink_metadata and skips symlinks so the walk cannot escape the node tree via a link. Adds regression tests for sparse sizing and symlink skipping. - Start/evict race (supervisor.rs): a concurrent POST /nodes/{id}/start could spawn a node during its stop/delete window. evict_node now makes the node unstartable *before* that window, via an in-memory `evicting` flag set under the supervisor lock (atomically excluding start_node) and a persisted eviction marker written first (which also survives a daemon crash mid-delete). Rollback is intentionally not attempted: once stopped, the node stays Evicted. Adds a regression test. - CI: apply rustfmt and fix two rustdoc intra-doc links (a private-item link and an unresolved EvictionRecord link). The security-audit failure (quinn-proto RUSTSEC-2026-0185 via saorsa-transport) and the is_running/UpgradeScheduled observation are pre-existing on main and out of scope here, per the review. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
|
Thanks for the thorough review @dirvine — both blockers were real. Addressed in 7bbb49e. 1. Eviction candidate sizing (symlinks + logical length)
2. Eviction/start race
CI
Out of scope (agree with your assessment)
|
dirvine
left a comment
There was a problem hiding this comment.
Re-reviewed current head 7bbb49eaf6c702550a7d73b9d4f0876a1fab11b6 after the eviction-safety fixes.
The two issues I previously flagged are addressed:
dir_sizenow uses non-followingsymlink_metadata, skips symlinks, uses allocated blocks on Unix, and has regression coverage for symlink escape and sparse-file sizing.- eviction now marks a node as
evictingand persists an eviction marker before the stop/delete window, sostart_noderejects it while deletion is in progress.
Focused local checks passed:
cargo test -p ant-core --lib disk -- --test-threads=1— 12 passedcargo test -p ant-core --lib eviction -- --test-threads=1— 5 passedcargo test -p ant-core --lib start_node_rejects_a_node_being_evicted -- --test-threads=1— 1 passedcargo fmt --all -- --check— passedRUSTDOCFLAGS='-D warnings' cargo doc --all-features --no-deps— passed
CI note: the remaining Security Audit failure is the pre-existing quinn-proto advisory already present on main, not introduced by this PR. E2E jobs were still pending when checked, so this approval is for the code review; branch protection should still enforce final CI.
Summary
Automatic node eviction when a node's disk runs low, driven by the shared daemon so it applies to
both the
antCLI and theant-guiapp.ant-nodeitself refuses to store chunks), the daemon stops the smallest co-located node and deletes its
data directory to reclaim space for the others. Smallest-first minimises the re-replication
cascade on the network.
evictedstatus with an explanatory reason (survives daemonrestarts), are never restarted, and can be dismissed with
ant node dismiss <id>.green/warning/critical) that warns before aneviction and names the next candidate — shown in
ant node statusand served at a newGET /api/v1/health.health so the operator can act manually.
start-all/stop-allskip evicted nodes; single start/stop reject them with a clear message.releases the just-killed process's file handles (LMDB map / the node's own copied binary).
evictionfield is#[serde(default)], so pre-upgradenode_registry.jsonfiles load unchanged.Test plan
cargo test -p ant-coregreen — unit + daemon integration, including new tests for candidateselection, health levels,
Evictedstatus derivation, pre-upgrade registry load, and thedelete-retry helper.
— node evicted at 500 MB free, data directory removed, status shows
evicted, dismiss works, andthe fleet-health indicator escalated green → warning → critical.
🤖 Generated with Claude Code