vq auto-cleanup¶
Audience: operators. Part of Running a host.
vq cleanup manages retained job artifacts on each queue host. It only acts on
terminal jobs. Running, pending, and suspended jobs are never archived or
deleted by cleanup.
Manual Cleanup¶
Dry-run is the default:
vq cleanup HOST
vq cleanup HOST --archive --older-than 30d
vq cleanup HOST --delete --older-than 180d
Add -x or --execute to perform the action:
vq cleanup HOST --archive --older-than 30d -x
vq cleanup HOST --delete --older-than 180d -x
vq cleanup HOST --archive --jobid <jobid> -x
vq cleanup HOST --delete --jobid <jobid> -x
vq cleanup HOST --restore <jobid> -x
Archive mode creates <archive_dir>/<job-name-or-jobid>.tar.bz2, removes the
workspace directory, and leaves the spec in the queue with archived_at and
archive_path. vq fetch HOST JOBID transparently fetches archived jobs from
that tarball.
Delete mode removes the spec, workspace, workdir and archive. It is irreversible.
What the sizes mean¶
The listing has three size columns, and #63 added the middle one:
column |
tree |
removed by |
|---|---|---|
WORKSPACE |
the job’s |
|
WORKDIR |
the job’s |
|
ARCHIVE |
the tarball, under the state root’s |
|
reclaimable is their sum, with the workdir total called out separately.
Before #63 the workdir column did not exist and its bytes were not counted.
On the host that prompted the issue, vq cleanup reported about 1 GB across
~1,200 terminal jobs while workdirs/ held 81 GB — a verb that looked like
cleanup and could not see where the growth was.
Each job is measured and cleaned at the path it recorded, so a host part-way
through the phase 5 scratch migration sees both roots: jobs
dispatched before the split are accounted and deleted under the state root,
jobs dispatched after it under the scratch root. A host that has since been
pointed at a third root is no different — the record is what is followed, and
the age sweep walks every root the queue’s own records still name on top of the
configured ones. Under a root you have configured away from it removes only a
directory a job record names at exactly that path. A workdir whose record is
gone, or an earlier attempt’s workdir after a retry recorded a new one, is left
there for you to remove by hand. --archive tars the workspace
only and says so, because the workdir it leaves behind is usually the larger
number.
A workdir is removed only when the recorded path is still the per-job directory vq created under a root vq manages. One pointed somewhere else — by hand, or by a record from a host whose layout was rearranged outside vq — is reported and left alone.
Both are all-or-nothing on purpose. An archive whose workspace holds an entry
that cannot be read fails and leaves the workspace in place, because the
archive is followed by deleting that workspace, and a tarball with the entry
missing would turn an unreadable file into a lost one; a half-written tarball
is removed. Fix the permission and archive again. In the daemon’s sweep the
failure is counted and logged and the job is retried on the next sweep.
--restore fails the same way on a tarball it cannot read in full and leaves
the archive untouched. This differs deliberately from vq fetch, which copies
what it can and records what it skipped: a fetch leaves the workspace where it
was, an archive does not (#62).
What cleanup does and does not cost the daemon¶
Retention is not what the daemon’s per-tick scan costs, and cleanup is not
how you bound it. As of v0.26.10 the scan leaves out every settled row —
one that has reached a terminal state and carries no scheduler transaction that
could still be open — whether or not anybody ever archived it. Such a row costs
one stat per pass instead of an open, a JSON parse and a model validation
(#22). The first pass after a daemon start still reads every row once; that is
what proves each one settled, and records the on-disk identity (mtime, size,
inode, device) the later passes compare against. Any rewrite of the file — an
archive, a --restore, a vq fetch read stamp, a hand edit — changes that
identity and the row is read again on the very next pass.
So plan retention on the things retention actually governs:
Archive to reclaim disk, not to speed up dispatch. Archiving tars a workspace away, which is usually where the bytes are. It does not change what the daemon’s scan costs, because an unarchived terminal row is already off that scan.
Delete only to discard history.
--deleteis still the only verb that removes a spec, and it is still irreversible. It is no longer the way to keep a busy driver dispatching promptly.Nothing about a retained job becomes less retrievable.
vq queue,vq status,vq show,vq fetch,vq cleanupand--restoreresolve a job by id or run their own listing pass; none of them go through the daemon’s scan. Two kinds of row stay on that scan however old they are: one whose scheduler submit is still unresolved — it must keep reserving its slot — and every row at daemon startup, where a terminal job’s leaked process group is reaped.
Daemon Policy¶
Enable host-side auto-cleanup with the CLI rather than hand-editing the policy JSON:
vq cleanup HOST --auto-enable \
--archive-after 30d \
--archive-after-state failed:90d \
--delete-after 180d \
--workdir-max-age 14d \
--interval 24h \
--reason "fleet retention policy"
Check or disable it with:
vq cleanup HOST --auto-status
vq cleanup HOST --auto-disable
--auto-status reports whether the next daemon sweep is already due, paused,
or scheduled for a specific next_run_at timestamp. Use that before manual
disk cleanup so you can tell whether the daemon is waiting on the interval or
whether a due sweep should run on the next daemon loop.
The policy lives at <state_root>/auto-cleanup.json. In multi-user mode it is a
daemon-wide policy under the system vq root, while each user’s jobs archive into
that user’s archive directory.
Retention Rules¶
--archive-after DUR archives terminal jobs older than DUR.
--delete-after DUR deletes terminal jobs older than DUR.
Per-state overrides use STATE:DUR and win over the global value for that
state:
vq cleanup HOST --auto-enable \
--archive-after 30d \
--archive-after-state failed:90d \
--delete-after 180d
Valid states are completed, failed, killed, interrupted,
oom_killed, starved, time_exceeded, and aborted_by_queue.
Use --archive-dir DIR or $VQ_ARCHIVE_DIR when archive tarballs should live
on a larger filesystem than the queue state root. scratch_root (or
$VQ_SCRATCH_DIR) does the same for the disposable trees, and is the bigger
lever: on two measured hosts those were over 99 % of the footprint.
Scheduler Workspaces¶
Scheduler-backed jobs, such as PBS/Torque jobs, stage a remote workspace under
the scheduler host’s configured scratch_root and copy the full result tree
back into the local vq workspace when the job reaches a terminal state. The
workspace each job was staged into is recorded on its spec before submission
(#63), so cleanup reclaims it at that exact path even if scratch_root has
changed since — which used to make it permanently un-reapable.
Cleanup now reclaims that duplicate remote scheduler workspace too:
At archive age, the auto-cleanup sweep removes the scheduler-side workspace and stamps
scheduler_remote_workspace_cleaned_aton the spec.Archived specs whose remote workspace cleanup previously failed are retried on later sweeps until they are stamped clean.
Before deleting a scheduler spec, cleanup tries the remote workspace cleanup one last time. If the scheduler-side cleanup fails, the delete is skipped so the spec keeps the recorded remote path for a later retry.
This remote cleanup uses the same scheduler dispatcher configuration as submit, status, and fetch. It is idempotent: an already-missing remote directory counts as a successful removal.
On a Lustre filesystem, rm -r stats every file before unlinking it, one
metadata-server round trip per file. Remote tree removal therefore checks each
tree first. When stat -f reports lustre and munlink is installed, vq
unlinks files and symlinks with find -P TREE \( -type f -o -type l \) -print0 | xargs -0 munlink, removes the emptied directories with find -P TREE -depth -type d -empty -delete, and then runs rm -rf on whatever remains. Links are
never followed. Every other filesystem uses rm -rf directly. The same rule
applies to node-local scratch removal in generated job scripts and to
runtime-source stage reclaim. It needs no configuration.
Orphaned scheduler workspaces (#91)¶
Retention removes a scheduler workspace only at the path recorded on its local spec. A workspace whose spec is gone (a deleted spec, lost state, a crashed submit) is never found that way. To list them, run:
vq cleanup CLUSTER --orphans # dry run: list and classify
vq cleanup CLUSTER --orphans -x # remove the removable ones
vq cleanup CLUSTER --orphans --older-than 30d --include-unowned -x
The command lists the configured jobs root on the scheduler host in one remote
call and compares it with every local spec. Several drivers can share one jobs
root, so since #91 each staged workspace records an opaque driver identity in
_vq/driver-id: 32 random hex characters kept in the state root, naming no
host or user. A workspace is removable only when all of these hold:
no local spec records it;
this driver stamped it (or it is unstamped and
--include-unownedis given);it is older than
--older-than(default 14 days, by directory mtime);the scheduler does not report its recorded job id as queued or running.
A workspace stamped by another driver is never removed, since that driver may still fetch it.
Each read-only workspace or archive inventory has a 180-second budget and an 8 MiB output limit. When Python 3 is available on the login host, one directory traversal reads the metadata without spawning commands for every workspace; otherwise the portable shell listing remains available. An incomplete inventory aborts cleanup, reports the failed operation and limit, and is not retried automatically. Malformed or unreadable metadata also fails closed on the Python path. Ordinary remote commands keep their existing timeouts.
The same command lists stray transfer archives in the jobs root:
<job>.upload.tar (submit staging) and <job>.result-<id>.tar (a fetch
copy). Each is normally removed by the operation that wrote it, so one that
remains means that operation died. With -x an archive is removed once it is
older than --older-than, unless its job still has a live local spec. If the scheduler cannot be asked, the command refuses. Removal
goes through the same guarded, Lustre-aware path as retention. It is never run
automatically.
An ambiguous removal outcome is not automatically retried; inspect a fresh
inventory before deciding whether another cleanup is needed.
Node-local scratch created by scheduler scripts is separate. When
node_scratch_dir is configured, the generated job script copies outputs back
to the scheduler workspace, writes the exit marker, and removes its temporary
node-local directory on normal script completion.
Leftovers Of Interrupted Operations¶
Every temporary vq creates is normally removed by the operation that made it. A crash, a kill or a failed final cleanup leaves it behind. To list what vq can attribute with certainty on this host, run:
vq cleanup [HOST] --leftovers # dry run, entries older than 1 day
vq cleanup [HOST] --leftovers --older-than 3d -x
It considers:
Admin update leftovers beside each registered venv:
.<venv>.vq-admin-backup-<txn>(the pre-update environment kept for rollback) and.<venv>.vq-admin-failed-<txn>(a failed target moved aside). While an admin update or recovery marker exists,vq admin recover-updateowns them and they are always kept. Removal holds the admin-update ownership and the program’s lifecycle lock.Fetch staging in vq’s own job trees:
.vq-fetch-*directories and archives and.vq-stale-*retired snapshots, at the top of each jobs root and of each job workspace.
Entries must be owned by the invoking user and not be symlinks. Never considered:
migration and serving-daemon rollback stages, which their receipts keep as evidence;
remote uploads kept after an unproven submit, which guard against a duplicate job;
temporaries in directories vq does not own, such as a
vq fetch -odestination.
When auto-cleanup is enabled, every sweep also removes leftovers that are at least a week old.
Workdirs¶
Local per-job workdirs, exposed to payloads as $VQ_WORKDIR, are distinct from
the workspace. Jobs can opt into immediate cleanup with:
vq submit HOST --clean-tmp ...
The auto-cleanup model also supports aged workdir sweeping through
--workdir-max-age DUR. Use it conservatively because workdirs may contain
operator-readable scratch from long debugging runs.
The sweep visits every workdir root vq manages — the scratch root and, until it is empty, the pre-#63 one under the state root — and within each it is gated hard:
a workdir whose job is not terminal is never removed, whatever its age or its directory mtime (a long SCF loop writing into files that already exist leaves that mtime frozen at dispatch, which is how an mtime-only heuristic would delete live scratch);
a terminal job’s workdir ages by its
finished_at, not by the directory;the root itself is opened
O_NOFOLLOW, so a symlinked root fails closed instead of redirecting the removal somewhere else;an entry that is a symlink, is not a directory, or is owned by another uid is refused and counted as an error rather than removed;
nothing outside those per-job directories is reachable: the queue, archives, and anything else under either root are not sweep candidates. When configured,
vq cleanup HOST --auto-statusreports it asworkdir_max_age=<seconds>sso fleet audits can confirm temporary workdir cleanup is active.
Schedule And Safety¶
The daemon checks the policy on each loop and runs a sweep only after
interval_seconds has elapsed since last_run_at. Cleanup errors are counted
and logged; they do not crash the daemon. last_run_at is stamped after each
pass so a chronically failing cleanup item does not retry every loop tick.
A daemon with no active jobs runs the sweep in its own loop. While any local,
reattached or scheduler job is active, a long synchronous sweep could delay
child reaping, so the daemon instead starts one separate
vq cleanup localhost --auto-run-due process. It only polls that process and
never waits on it. Its output goes to <state root>/auto-cleanup-child.log.
At most one such process runs at a time, and if one exits before stamping
last_run_at, the next is started no sooner than five minutes later. A file
lock beside the policy keeps an in-process sweep and a separate one from ever
running together. A sweep that finds the lock held skips without stamping, so
the policy stays due. Before #90 a host that was never idle never swept at all.