Skip to content

perf(cli): faster snapshot create with multi-threaded zstd in one container#1061

Merged
Dr0p42 merged 1 commit into
mainfrom
singularity/unruffled-hodgkin-a27ef7
Jul 7, 2026
Merged

perf(cli): faster snapshot create with multi-threaded zstd in one container#1061
Dr0p42 merged 1 commit into
mainfrom
singularity/unruffled-hodgkin-a27ef7

Conversation

@Dr0p42

@Dr0p42 Dr0p42 commented Jul 7, 2026

Copy link
Copy Markdown
Contributor

What

Speeds up abi stack snapshot create by replacing the per-volume single-thread gzip archiving with multi-threaded zstd, all done in a single helper container.

Changes

  • zstd instead of gzip. Volume archives are now .tar.zst, compressed with zstd -T0 (all cores). A tiny helper image (abi-snapshot-helper:zstd1 = alpine + zstd, ~6 MB) is baked once via ensure_snapshot_helper_image() before the stack is stopped, so its one-time build never lands in the downtime window and there's no per-snapshot package-mirror dependency. Compression runs inside the container, so only the compressed bytes cross the Docker VM→host boundary.
  • One container for all volumes. archive_volumes() mounts every present volume read-only at /from/<key> and streams tar cf - | zstd -T0 -3 to /to/<key>.tar.zst in a single docker run, paying container/image startup once instead of 7×. set -eo pipefail so a failed tar aborts the run rather than being masked by zstd's exit code.
  • extract_volume() decompresses with zstd -dc | tar xf -. Filenames centralised in volume_archive_name().

Why

On a real 3.88 GB TDB2 (fuseki) volume, gzip took ~146 s and produced a 1.06 GB archive. Benchmarking showed the wall-clock was dominated by single-thread gzip on the one large volume (which is ~81% of the bytes), not I/O.

approach time size
gzip (before) 146 s 1.06 GB
zstd -T0 -3 (after) 23 s 501 MB

Result: ~6× faster on the dominant volume and roughly half the on-disk/export size.

Notes for reviewers

  • No backward-compat shim for old .tar.gz archives — there are no existing snapshots to migrate.
  • Cross-volume parallelism was deliberately not added. One volume is ~81% of the bytes, so parallel archiving caps at ~1.2× (Amdahl), and a per-archive -T0 already saturates all cores; combining both would oversubscribe them.
  • The clean docker compose stop is kept. docker pause was considered and rejected: the freezer doesn't flush/fsync, so it only yields a crash-consistent, in-flight-lossy image — unsafe for Fuseki/TDB2 (unflushed mmap indexes + journal at an unclean boundary).
  • storage/ intentionally stays on host-side gzip to preserve the audited _safe_extract/_assert_safe_members guard and avoid a host-zstd dependency; it's the minority of bytes.

Testing

  • 56 unit tests pass (snapshot_runtime_test.py, snapshot_test.py), including new coverage for the helper-image build (present/missing/failure) and the batched archive command.
  • Verified end-to-end on real Docker volumes: one container archives multiple volumes and each extracts back byte-identical (files, symlinks, spaces in names, binary blobs).
  • Pre-commit suite (ruff + mypy + Nexus build) passed.

…ontainer

`abi stack snapshot create` archived each stateful volume with a separate
`alpine tar czf` (single-thread gzip) container, run sequentially. On a real
3.88GB TDB2 (fuseki) volume this took ~146s and produced a 1.06GB archive,
with the wall-clock dominated by single-thread gzip on the one large volume.

Two changes:

- Compress with multi-threaded zstd instead of gzip. A tiny helper image
  (alpine + zstd, ~6MB) is baked once via `ensure_snapshot_helper_image()`
  before the stack is stopped, so its one-time build never lands in the
  downtime window and there is no per-snapshot package-mirror dependency.
  Compression runs inside the container, so only the compressed bytes cross
  the docker VM->host boundary. Measured: ~146s -> ~23s on the fuseki volume,
  and the archive roughly halves (1.06GB -> 501MB).

- Archive every volume in a SINGLE helper container (`archive_volumes`)
  instead of one container per volume, paying container/image startup once.
  Volumes are mounted read-only at /from/<key> and streamed
  `tar cf - | zstd -T0 -3` to /to/<key>.tar.zst. `set -eo pipefail` so a
  failed tar aborts the run rather than being masked by zstd's exit code.

Volume archives are now `.tar.zst` (centralised in `volume_archive_name`);
`extract_volume` decompresses with `zstd -dc | tar xf -`. No backward-compat
shim is needed (no existing snapshots). Cross-volume parallelism was
deliberately not added: one 3.88GB volume is ~81% of the bytes, so parallel
archiving caps at ~1.2x, and a per-archive `-T0` already saturates all cores.
The clean `docker compose stop` is kept (Fuseki/TDB2 needs a clean checkpoint;
`docker pause` only yields a crash-consistent, in-flight-lossy image).

`storage/` intentionally stays on host-side gzip to preserve the audited
`_safe_extract`/`_assert_safe_members` guard and avoid a host-zstd dependency.

Tests: 56 pass; batched archive + per-volume extract verified byte-identical
end-to-end on real volumes.
@cursor

cursor Bot commented Jul 7, 2026

Copy link
Copy Markdown

Bugbot is not enabled for your account, so this pull request was not reviewed.

Enable Bugbot in the Cursor dashboard to get automatic reviews on future PRs.

@cloudflare-workers-and-pages

Copy link
Copy Markdown

Deploying abi-docs with  Cloudflare Pages  Cloudflare Pages

Latest commit: 6644bc4
Status: ✅  Deploy successful!
Preview URL: https://6acbb8b6.abi-docs-76c.pages.dev
Branch Preview URL: https://singularity-unruffled-hodgki.abi-docs-76c.pages.dev

View logs

@Dr0p42 Dr0p42 merged commit dd5d58a into main Jul 7, 2026
5 checks passed
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