onepub-dev / onepub-dev/reVault
Improve cold reads of compressed-only lockboxes toward ZIP parity
Nobody has claimed this yet.
- Dominant language
- Rust
- Stars
- 4
- Forks
- 0
- Avg merge
- 1h 55m
- Merged PRs (30d)
- 1
Description
Cold file reads from compressed-only lockboxes are slower than equivalent ZIP reads. Investigate and optimize the read path toward ZIP parity without relying on warm caches.
Scope
- Lockbox encryption:
Encryption::None. - Lockbox signing:
Signing::None. - Compression: default Zstd (level 3); ZIP comparison uses Deflate, created with
zip -6 -X. - Vault encryption is unaffected: vaults must always remain encrypted.
- This issue records an investigation for later implementation; no production optimization was made.
Corrected benchmark results
Luna ran a release-mode Rust probe using file-backed lockboxes and in-process zip 8.6.0 (File + ZipArchive). Both read into a Vec; all output bytes were compared with the source outside the timed sections. Results are medians of 15 repetitions. Each cold measurement starts with a newly opened archive handle; the OS page cache was not flushed. These are application-cold measurements, not cold-disk measurements.
| File read | Interactive cold | ReadMostly cold | ReadMostly warm | ZIP read |
|---|---|---|---|---|
| 16 MiB repetitive text | 39.703 ms | 46.307 ms | 27.830 ms | 12.459 ms |
| 16 MiB deterministic pseudorandom data | 115.082 ms | 122.424 ms | 30.901 ms | 26.559 ms |
| One 1 KiB file in a 2,000-file archive | 4.921 ms | 4.798 ms | 0.002 ms | 0.031 ms |
Archive opening is excluded above and measured separately:
| Archive | Lockbox open (Interactive / ReadMostly) | ZIP open | Lockbox size | ZIP size |
|---|---|---|---|---|
| 16 MiB text | 0.178 / 0.177 ms | 0.065 ms | 10,560 B | 57,780 B |
| 16 MiB pseudorandom | 0.198 / 0.198 ms | 0.067 ms | 16,786,752 B | 16,779,902 B |
| 2,000 x 1 KiB | 2.249 / 2.226 ms | 14.270 ms | 2,181,440 B | 2,299,030 B |
The small-file measurement reads just one file, not the entire tree. ZIP opening includes central-directory parsing, so open-plus-first-read favors lockbox in that particular scenario. The incompressible lockbox falls back to stored data despite compression being enabled. The text sample is highly repetitive and is not representative of all text workloads.
ReadMostly's lazy, bounded 64 MiB decoded-frame cache is separate from the decoded-page cache. It dramatically improves the small-file warm result but does not establish universal warm-read parity for large files.
An earlier subprocess-based ZIP comparison was discarded: it included process startup, writing an output file, and rereading it. Use only the corrected in-process results above.
Read-path findings
These are code-level optimization candidates, not profiler-attributed percentages:
- Deep copies of whole decoded pages.
rust/revault_lockbox_api/src/storage/page_cache.rs,PageCache::read_page, clones the decoded page on cache hits and to populate the cache on misses.PagePayload::Normalowns aVec, so these are payload copies rather than cheap shared references. Reading one object can copy other objects on the same page. - Multiple intermediate buffers in the plaintext path.
file_format/page.rs,decode_page_with_format, verifies the page checksum, copies the body and object stream, and allocates individual object payloads.lockbox/files.rs,read_file_chunk_compression_frame, then assembles frame segments, verifies the frame digest, decompresses, and copies the requested slice.read_file_rangeandLockboxFileReaderadd further copies. ReadMostly can also copy decompressed data into its cache. - Repeated chunk-list work.
lockbox/files.rs,read_file_range, clones and sorts the file's complete chunk list on each call.lockbox/file_handles.rs,LockboxFileReader::read_internal, calls it for each 2 MiB logical window. This creates avoidable work as the number of chunks grows. - Read amplification. A small request processes its containing page and full compression frame. Normal small-file packing targets 4 KiB; BulkImport targets 2 MiB. Large-file frames and seekable-reader windows are 2 MiB. Data pages can contain multiple objects.
- Repeated reads and decoder setup. A page-cache miss reads the header and then rereads the header with the body, using mutex/seek/read operations. Zstd decoding constructs a fresh
FrameDecoderfor each decode. Measure these costs before prioritizing them.
Proposed work
- Add a durable repository benchmark with deterministic fixture generation, comparable in-process ZIP reads, byte verification, and separate open/first-read/warm timings. The exploratory harness is currently temporary, not a committed regression benchmark.
- Profile allocations, bytes copied, checksumming, decompression, and I/O on the compressed-only path to attribute the gap.
- Eliminate deep page clones and unnecessary intermediate copies, using borrowed/shared immutable decoded data or transferring buffer ownership as appropriate. Preserve payload lifetimes, mutation isolation, and existing integrity checks.
- Give the seekable reader a persistent chunk cursor/index; avoid repeated cloning, sorting, and scanning of the entire chunk list.
- Benchmark the Zstd decoder independently and evaluate buffer/decoder reuse after removing surrounding overhead. Keep the implementation and dependencies pure Rust.
- Measure small-file/page amplification separately, including archives created with BulkImport. A format/layout change, if needed, should be assessed separately from compatible read-path changes.
Validation and acceptance
- Demonstrate progress toward ZIP parity for application-cold reads, with encryption and signing disabled throughout.
- Cover representative text, binary/incompressible content, packed small files, larger files, sequential reads, and range/seek reads.
- Report archive size, open latency, first-read latency/throughput, warm-read behavior, and memory use; distinguish application-cold from OS-cold.
- Alternate/randomize benchmark ordering, retain distributions, and use controlled disk-cache experiments before making cold-storage claims. The initial probe used fixed ordering and reported medians only.
- Keep checksum validation, corruption handling, sparse-file behavior, seeking, and byte-for-byte correctness intact.
- Do not count a larger cache as a cold-read optimization.
Luna also ran the core library tests: 262 passed, 5 ignored, 0 failed.
Local exploratory artifacts
Harness: /tmp/coldread/Cargo.toml and /tmp/coldread/src/main.rs.
Fixtures: /home/bsutton/git/revault/target/cold-read-plaintext-20260911.
These are local temporary/ignored artifacts and are not portable repository assets. Preserve or replace them with a committed benchmark when picking up this issue.
Command used for the corrected probe:
TMPDIR=/home/bsutton/git/revault/target/compiler-tmp \
CARGO_TARGET_DIR=/home/bsutton/git/revault/target/creation-options \
cargo run --offline --release --manifest-path /tmp/coldread/Cargo.toml
Contributor guide
No contributing guide indexed for this repository
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
Research direction
Start with rust/revault_lockbox_api/src/storage/page_cache.rs, file_format/page.rs, lockbox/files.rs, and lockbox/file_handles.rs to trace page, frame, and chunk-list reads. Run the core library tests and replace the temporary /tmp/coldread harness with a committed benchmark covering the stated fixtures and ZIP comparison. Done means measured application-cold progress toward ZIP parity while preserving integrity, seeking, sparse-file behavior, and byte-for-byte correctness.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- rust
- Domain
- performance
- Issue type
- Refactor
- Difficulty
- 5/5
- Estimated time
- Over a week
- Activity status
- Active
- Clarity
- Mostly clear
- Newbie friendliness
- 45/100