Skip to content

feat(volumecache): adaptive size-bucketed transport#538

Open
deanq wants to merge 11 commits into
deanquinanola/sls-367-network-volume-warm-cache-for-serverless-volumecachefrom
deanq/sls-367-volumecache-adaptive-transport
Open

feat(volumecache): adaptive size-bucketed transport#538
deanq wants to merge 11 commits into
deanquinanola/sls-367-network-volume-warm-cache-for-serverless-volumecachefrom
deanq/sls-367-volumecache-adaptive-transport

Conversation

@deanq

@deanq deanq commented Jul 10, 2026

Copy link
Copy Markdown
Member

Summary

Follow-up to #531. Replaces VolumeCache's serial per-file transport with a size-bucketed hybrid, chosen from an empirical benchmark on Runpod serverless (MooseFS network volume).

  • Files < 256 KiB are packed into one small.tar on the volume (collapses per-file metadata round-trips).
  • Files >= 256 KiB are parallel-copied unpacked into big/ (incremental size/mtime diff; large-file subtree stays browsable).
  • A versioned manifest.json, written last, is the atomic commit marker. Extraction always uses stdlib tarfile with per-member path-traversal defense and per-member incremental skip.
  • Public API unchanged (hydrate/sync/context manager); additive max_workers.

Why

Benchmark across file-shape quadrants, mirror direction (local -> volume):

tree serial parallel tar
40,000 x 16 KiB 428.7s 62.0s 5.9s
3,000 x 1 MiB 31.5s 4.0s 9.4s
8 x 256 MiB 2.67s 0.59s 5.32s

Serial (today's VolumeCache) never wins and is catastrophic on many-small-file caches: 72x slower than tar and 10x slower than parallel at 40k files. Large files favor parallel (tar's single stream is dead weight). No single strategy wins everywhere, so transport adapts to mean file size.

Design

  • 256 KiB threshold sits on the tar side of the parallel/tar crossover; bimodal trees (large weights + tiny metadata) split cleanly.
  • Pack via the GNU tar binary with a tarfile fallback; if both are unavailable, small files reclassify to the unpacked big bucket (no hard failure).
  • Concurrency-safe under autoscaling: every volume write is temp + atomic os.replace; the manifest rename is the linearization point (write-once/read-many cache lifecycle).
  • Best-effort preserved: any failure degrades to a cold worker and never raises unless best_effort=False. An unknown/corrupt/future-version manifest is treated as absent so sync self-heals.

Stacking

Stacked on #531 (base is its branch), so this diff shows only the adaptive-transport delta. Retarget to main once #531 merges.

Linear: SLS-367

Test plan

  • Full VolumeCache suite green: byte-for-byte round-trip, incremental big bucket, all-or-nothing small bucket, tar/tarfile fallback and reclassify, per-member extraction safety (escaping paths refused), corrupt/foreign manifest self-heal, best-effort swallow.
  • make quality-check: 586 passing, rp_volume_cache.py coverage 97% (repo floor 90%).

deanq added 11 commits July 10, 2026 00:21
- Remove the unplanned "._" AppleDouble basename skip from the
  production extractor; it would silently drop legitimate files on a
  real worker. The macOS bsdtar artifact it worked around is now
  handled in the affected tests by monkeypatching _tar_binary to force
  deterministic pure-Python tarfile packing.
- Guard tf.getmembers() so a truncated/corrupt small.tar can no longer
  raise out of _extract_small; return the count extracted so far
  instead. Add a regression test that truncates a valid archive
  mid-body and asserts no exception propagates.
- Drop the redundant os.path.realpath(dst)/os.makedirs additions in
  _extract_small; TarFile.extract already creates parent directories
  and _is_safe_dest already resolves the path internally, matching the
  brief's structure.
Replace archive-truncation test with a mocked tarfile.open whose
getmembers() raises tarfile.TarError, deterministically exercising the
inner corrupt-member-listing guard instead of the outer open()-time
guard (truncation offset unreliably hit the outer path instead).
…l copy

- rewrite _do_sync: pack small files into small.tar, parallel-copy big files
  into big/, write manifest.json last as the atomic commit marker; fall back
  to unpacked big copy when tar is unavailable
- rewrite _do_hydrate: read manifest, extract small.tar, parallel-copy big
  entries whose destinations pass _is_safe_dest
- suppress BSD tar AppleDouble sidecars via COPYFILE_DISABLE (no-op on GNU tar)
- migrate format-coupled sync/hydrate/context-manager tests to manifest-based
  round-trip assertions
…e archive

_do_hydrate extracted small.tar unconditionally, so a stale archive left
behind by a sync that reclassified small files to big (pack failure) would
briefly overwrite fresh data before the big-copy pass caught up, inflating
the restored count. Gate the extract on manifest["small"] being non-empty,
and remove the stale archive in _do_sync before the manifest is written
whenever no small files remain.
Update the module and class docstrings and the volume-cache doc to
describe the small.tar + big/ + manifest.json layout (replacing the
stale per-file flat-mirror description) and document the max_workers
constructor argument. Add a targeted test for the isfile() skip
branch in _extract_small to keep coverage at 97%.
Treat non-dict or unknown-version manifests as absent so sync
self-heals instead of raising downstream on unexpected shapes.
Move the small.tar listing write inside the temp-cleanup try block
so a write failure (e.g. ENOSPC) can't leak the .list temp file.
Add coverage for the subprocess-tar failure path and correct the
docs' idempotency/limitations claims around manifest.json rewrites
and orphaned big/ files.
@capy-ai

capy-ai Bot commented Jul 10, 2026

Copy link
Copy Markdown

Capy auto-review is paused for this organization because the usage-cycle auto-review limit has been reached. Increase the limit or turn it off in billing settings to resume automatic reviews.

promptless Bot added a commit to runpod/docs that referenced this pull request Jul 10, 2026
…docs

Folds in runpod/runpod-python#538 (SLS-367): documents the additive
max_workers constructor argument and the size-bucketed transport
(small.tar + parallel big/ copy + versioned manifest).

Also reconciles the page to the shipping VolumeCache API: the earlier
env-var toggles (RUNPOD_VOLUME_CACHE/_MAX_GB/CACHE_DIRS), the warm()
method, max_size_gb, and automatic worker-loop wiring were removed
upstream in favor of the single explicit opt-in context manager.
@promptless

promptless Bot commented Jul 10, 2026

Copy link
Copy Markdown

Promptless prepared a documentation update related to this change.

Triggered by PR #538 (adaptive size-bucketed transport)

Docs handled via the existing VolumeCache draft (docs PR #693) rather than a new PR. Folded in this PR's user-facing delta — the additive max_workers argument plus the size-bucketed transport ("How it works"). While there, corrected the draft to match the shipping API (it still described the removed env-var/warm()/max_size_gb/auto-wiring design). Kept as a draft pending #531 → main merge.

Review: docs PR #693

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