Skip to content

feat(cache): add 'dt cache relink' to symlink cache onto a local remote (0.28.0) - #194

Merged
johnyaku merged 1 commit into
mainfrom
feat/cache-relink
Sep 29, 2026
Merged

johnyaku merged 1 commit into
mainfrom
feat/cache-relink

Conversation

@johnyaku

Copy link
Copy Markdown
Contributor

Summary

Adds dt cache relink [remote], which replaces every regular file in the primary DVC cache with a symlink to its content-addressed twin in a locally-accessible remote — reclaiming the duplicated space. On a shared-filesystem HPC setup the local remote already holds a good copy of every pushed object, so the identical bytes sitting in each clone's cache are pure duplication. The cache stays fully usable: dvc checkout still resolves each object at its normal path, it's just a symlink now.

Behaviour

  • Verify-first by default. The match is content-addressed (a cache object and its remote copy share the md5 that names them), so the relink is existence-only and never re-hashes. Because that's only trustworthy on a known-good remote, dt remote verify runs first (incremental via the remote's ledger, so repeat runs are cheap) and the relink aborts if it finds bad/incomplete blobs.
    • --force — relink anyway despite bad blobs (warns).
    • --skip-verify — skip the pass entirely (only safe if already verified).
  • Missing-in-remote objects are left untouched (e.g. not yet pushed) — symlinking them would dangle. Count-only by default; -v lists each.
  • Idempotent — objects already symlinked are skipped, so re-running is safe.
  • Atomic — each replacement is a temp symlink renamed over the object, so an interrupted run never leaves a half-written cache object.
  • --dry previews and counts reclaimable space without touching the cache.

Handles v3/v2/mixed layouts and .dir manifests, and only renames within existing prefix directories so shared-cache directory permissions are never disturbed.

Remote selection mirrors the rest of the remote/cache groups: positional [remote], defaulting to the DVC default remote (via the shared resolve_local_remote).

dt cache relink                 # verify default remote, then relink
dt cache relink coldstore       # relink onto a named remote
dt cache relink --dry           # preview + count reclaimable space
dt cache relink --skip-verify   # skip verify (already verified)
dt cache relink --force         # relink despite bad blobs
dt cache relink -v              # list every object touched or missing

Changes

  • dt/cache_relink.py — new module (sweep + helpers)
  • dt/cli.py — new cache relink command
  • tests/unit/test_cache_relink.py — 13 unit tests
  • docs/cache.md, docs/commands.md — docs
  • pyproject.toml, dt/__init__.py — version bump 0.27.1 → 0.28.0

Testing

  • New unit tests: 13 passed (helpers, end-to-end sweep with mocked cache/remote, verify-gate abort + --force, .dir handling, idempotency, dry-run).
  • Related suites — cache, cache_ops, remote_verify, fetch, utils: 290 passed, no regressions.
  • Live end-to-end in a throwaway DVC repo with a local remote: dry-run → real relink → symlinks created → dvc checkout still resolves; idempotent re-run; missing/.dir handling; bad-blob abort (exit 1) and --force override all verified.

🤖 Generated with Claude Code

…te (0.28.0)

Replace every regular file in the primary DVC cache with a symlink to its
content-addressed twin in a locally-accessible remote, reclaiming the
duplicated space. On a shared-filesystem HPC setup the local remote already
holds a good copy of every pushed object, so the identical bytes in each
clone's cache are pure duplication; the cache stays fully usable because
'dvc checkout' still resolves each object at its normal path.

The match is content-addressed (a cache object and its remote copy share the
md5 that names them), so the relink is existence-only and never re-hashes.
Because that is only trustworthy on a known-good remote, 'dt remote verify'
runs first by default (incremental via the remote's ledger) and the relink
aborts if it finds bad/incomplete blobs; --force overrides, --skip-verify
skips. Objects absent from the remote are left untouched; already-symlinked
objects are skipped so re-runs are idempotent. Each replacement is atomic
(temp symlink renamed over the object). Handles v3/v2/mixed layouts and .dir
manifests, and only renames within existing prefix dirs so shared-cache
directory permissions are never disturbed.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@johnyaku
johnyaku merged commit 884f9fb into main Sep 29, 2026
1 check passed
@johnyaku
johnyaku deleted the feat/cache-relink branch September 29, 2026 23:33
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