Four weeks ago we shipped v0.1.0: seven codecs, BLAKE3 content addressing, pure Rust. Today v0.2.36 brings the format to a different class — read-write images, 18 codec variants competing in a tournament selector, 9 profiles spanning the speed/ratio/read-heavy/write-heavy tradeoff space, a streaming write API, mmap-bounded peak memory, cross-file compress cache, and a state-of-the-art benchmark suite that runs the full matrix against DwarFS, SquashFS, and tar+zstd.
The codec stack is now pure-Rust end-to-end — 16 of 17 omnizip
crates have zero external dependencies. The only remaining external
codec dep is brotli (via omnizip-brotli, awaiting Phase C of the
full in-house port).
The headline: LimniFS now beats every existing tool on ratio across most workload classes, with 5–8× create-speed wins across the benchmark suite — while keeping the pure-Rust, content-addressed, Merkle-rooted design from v0.1.0.
Cumulative speedups since v0.1.0
| Dataset | v0.1.0 baseline | v0.2.30 | Speedup |
|---|---|---|---|
| csv-synthetic (balanced) | ~3 s | 0.39 s | 7.7× |
| wav-synthetic (balanced) | ~1.4 s | 0.16 s | 8.8× |
| fits-synthetic (balanced) | ~25 s | 3.6 s | 6.9× |
| fits-synthetic (max-ratio) | ~190 s | 102 s | 1.9× |
| tiny-files (max-ratio) | ~5 s | 1.07 s | 4.7× |
Same ratios, same output bytes. Every speedup is from removing redundant work or absorbing upstream omnizip improvements.
What’s new since v0.1.0
1. Read-write images with crash safety
v0.1.0 was read-only — pack once, read forever. v0.2.28 introduces
RwImage: open a .lim file, add/update/delete files, commit. A
write-ahead log (LIMWAL) survives crashes mid-commit; an atomic
rename(2) swap ensures the manifest is either fully old or fully new.
Three turnover modes:
- CopyOnWrite (max-write-rw): fast updates, unreferenced blocks reclaimed on turnover.
- UpdateInPlace (max-read-rw, balanced-rw): full history kept for audit. Turnover compacts when the history threshold hits.
limni open my-image.lim --rw
limni add my-image.lim new-file.txt
limni update my-image.lim config.toml
limni commit my-image.lim
Crash at any point: re-open rolls forward through the WAL or rolls back to the last commit. No partial state ever escapes.
2. Tournament codec selector with short-circuit
The v0.1.0 categorizer picked one codec per content class (text → Brotli, binary → LZ4, audio → FLAC, etc.). v0.2.28 introduces a tournament: every chunk gets compressed by each codec in the profile’s tournament list, smallest wins.
The catch: a tournament with 5 codecs is 5× slower than single-codec. We added short-circuit — when any codec achieves the profile’s target ratio (default 25% for balanced, 0% for max-ratio, 50% for max-write), the tournament accepts and stops. On highly compressible text chunks, LZ4 typically hits under 10% ratio in microseconds and we accept it without running the slower Brotli/ZSTD passes.
The same short-circuit pattern now applies to both the chunk path and the whole-file categorizer path. CSV files compress 8× faster; WAV files 11× faster; ratios unchanged.
3. Nine profiles
Different workloads need different tradeoffs. v0.2.28 ships nine built-in profiles:
| Profile | Goal | Tournament | Chunk | Special features |
|---|---|---|---|---|
max-ratio |
Smallest output | All codecs | 64 KB | PPMd7 256 MB budget |
max-speed |
Fastest create | LZ4 only | 4 KB | Categorizers off |
balanced |
General-purpose | LZ4, Brotli | 16 KB | Per-class ZSTD dict |
competitive |
Beat both | LZ4, Brotli | 8 KB | ZSTD L3 text path |
max-read |
Read-heavy | LZ4, ZSTD, Brotli | 64 KB | Larger inline threshold |
max-write |
Write latency | LZ4 only | 128 KB | skip_chunking — single drop per file |
max-write-rw |
RW writes | LZ4 + ZSTD turnover | 128 KB | CopyOnWrite mode |
max-read-rw |
RW reads | ZSTD, Brotli | 64 KB | UpdateInPlace mode |
balanced-rw |
General RW | LZ4, ZSTD | 16 KB | UpdateInPlace mode |
Switch via limni limn --profile max-ratio ./input -o out.lim.
4. The codec library
The headline change. LimniFS now ships 18 codec variants end-to-end through omnizip:
| Codec | id | Use case |
|---|---|---|
| STORE | 0x00 | Incompressible data |
| LZ4 / LZ4-HC | 0x01 / 0x13 | Fast / better ratio |
| ZSTD | 0x02 | Default text codec |
| XZ/LZMA | 0x03 | Best ratio on text |
| Brotli | 0x04 | Web text |
| DEFLATE / libdeflate | 0x05 / 0x14 | RFC 1951 interop (two impls) |
| Snappy | 0x06 | Legacy Google format |
| FLAC | 0x07 | PCM audio (WAV/AIFF) |
| ricepp | 0x08 | FITS integer-pixel scientific data |
| FSST+Brotli | 0x09 | CSV/TSV with column-aware preprocessing |
| BLOSC2+shuffle+LZ4 | 0x0A | Scientific float data |
| ZPAQ | 0x0B | Archival context-mixing |
| PPMd7 / PPMd8 | 0x0C / 0x12 | Text with long-range context |
| GLZA | 0x0D | Grammar-based text |
| shuffle+ZSTD | 0x0E | Integer-pixel scientific data |
| bitshuffle+LZ4 | 0x0F | Sparse integer data |
| bzip2 | 0x10 | Legacy archival |
| deflate64 | 0x11 | Legacy ZIPX |
| BCJ composite (×4) | 0x20-0x24 | Executable code filtering before LZ4/ZSTD |
Each codec has strongly-typed tunables (quality, memory budget, context order, dictionary size) plumbed through the profile system.
5. State-of-the-art benchmark suite
limnifs-bench is a Rust workspace crate (no Python subprocess
overhead) that runs LimniFS via direct library calls and external
tools via subprocess. Multi-profile support:
limnifs-bench run --profile balanced,max-write,max-ratio --all
Each profile produces a separate row in the report. The win/loss
matrix renders every (dataset, operation, format) triple with the
winner flagged and slow formats tagged with the multiplier (e.g.,
8.4× means 8.4× slower than the winner).
6. Other improvements
- FastCDC 4× unroll in the gear hash inner loop
- Parallel slab assembly — slabs encoded in rayon workers
- madvise(MADV_WILLNEED) prefetch on slab files
- Hot slab LRU cache with polymorphic
SlabSourcetrait - Cross-image Bloom filter sparse dedup index (opt-in feature)
- Per-class ZSTD dictionary training (FrequencyTrainer + FastCoverTrainer)
- AEAD: ChaCha20-Poly1305 (default), AES-OCB3, AES-GCM, Ascon
- Key wrap: X25519-HKDE (HPKE-style)
- Signing: Ed25519 + optional sigstore
- Reed-Solomon erasure coding per slab (systematic Vandermonde)
- Locators: file, HTTP range, S3, IPFS gateway + CAR
- 585 workspace tests passing, 0 warnings,
clippy::pedanticclean
7. v0.2.29: ZSTD 7.5× encode speedup
The single biggest external win of the cycle: omnizip-rs landed a
cached Huffman encode table (TODO 152) that lifts ZSTD-1 from 11.5
to 85.8 MB/s. ZSTD is in 4 of 9 LimniFS profiles (max-read,
max-read-rw, balanced-rw text path, max-ratio tournament), so the
speedup shows up across many datasets. FITS saw the largest swing
because its 47 MB payload runs through ZSTD L6 baseline in
process_whole_file_drop: 26.9 s → 3.7 s (7.3×) on the
balanced profile.
8. v0.2.30: Streaming write, mmap input, FSST baseline
Three LimniFS-side improvements that don’t depend on omnizip:
-
write_stream<R: Read>— new entry point that packs a single named stream from any reader. Internal buffering bounded atmax_chunk_size + 64 KiB. Lets callers pipe from network sockets, pipes, or generators without a temp file:let reader = std::io::Cursor::new(bytes); let artifact = limnifs_write::write_stream("name.bin", reader, &config)?; -
Memory-mapped input —
process_filememmaps files above 1 MiB instead ofstd::fs::read-ing them. Peak RSS drops fromtotal_input_sizeto roughlyunique_chunks × avg_chunk_sizebecause chunk compressors see borrowed slices into the mmap. Below 1 MiB plain read is faster. -
FSST+Brotli pre-computed baseline —
process_whole_file_dropnow passes its already-computed Brotli result into FSST+Brotli’s comparison check, eliminating one full Brotli pass per FSST-routed file. New public API:limnifs_core::codec::fsst_brotli::compress_with_baseline.
9. v0.2.31–v0.2.32: Snappy encoder, LZMA reusable state
Two more upstream wins absorbed:
CODEC_SNAPPY(0x06) gains a from-spec encoder with full wire-format compatibility (snap-compat). Previously Snappy was decode-only in LimniFS — round-trip with externally produced Snappy streams (Parquet, ORC, Avro, SQLite WAL) now works in both directions.XzCodecacceptsLIMNIFS_XZ_REUSE_STATEenv var — opt intoLzmaCompressor::ResetMode::ReuseStatefor ~5–10% LZMA encode speedup at the cost of run-to-run byte determinism. Default behaviour (deterministicResetMode::Full) is unchanged.
10. v0.2.33–v0.2.34: LimniFS-discovered LZ4 bug, fixed upstream
We found a bug in omnizip-lz4 0.14.18’s new from-spec encoder: it
produced output its own decoder rejected on inputs with match/literal
lengths exactly at the code-nibble-15 boundary. Filed upstream as
docs/omnizip-proposals/lz4-from-spec-broken.md. omnizip-rs PR #115
fixed it within one release cycle and added 5 regression tests.
v0.2.34 absorbs the fix and removes the Cargo.lock pin.
Side effect: lz4_flex is no longer in the dep tree. All
LZ4-using paths (including omnizip-blosc and omnizip-filters)
now use the in-house from-spec encoder.
11. v0.2.35: libdeflate dynamic-Huffman fix
omnizip-rs TODO 116: the broken package-merge in omnizip-libdeflate
was replaced with correct standard Huffman + zlib CPI length
limiting. Dynamic-Huffman path re-enabled. CODEC_LIBDEFLATE
(0x14) now produces 10–20% better ratio on text and binary inputs.
Round-trip safety verified by the existing cross_decodes_with_deflate
test against the miniz_oxide-backed CODEC_DEFLATE (0x05).
12. v0.2.36: Cross-file compress cache
Each rayon worker thread now carries a thread-local
HashMap<DropId, (codec_id, compressed_bytes)>. When two files
share a chunk — common in source trees with vendored dependencies,
container layer stacks, and successive build outputs — the second
file hits the cache and skips the tournament compress pass entirely.
Cache is bounded at 100K entries; output bytes are unchanged.
Where we are vs the field
Pure-ratio comparison on synthetic datasets (balanced profile, lower is better):
| Dataset | LimniFS | DwarFS | SquashFS | tar+zstd |
|---|---|---|---|---|
| csv-synthetic | 3.6% | 3.6% | 16.4% | 4.8% |
| fits-synthetic | 32.1% | 89.97% | 90.2% | 85.7% |
| wav-synthetic | 0.0% | 0.1% | 3.4% | 0.03% |
| repetitive | 0.2% | 0.4% | 0.5% | 0.4% |
| zeros | 0.0% | 0.4% | 0.0% | 0.0% |
We win or tie ratio on every synthetic dataset. Create speed is competitive with SquashFS on most workloads and substantially faster than DwarFS. Extract speed is dominated by decompression cost; we win on WAV (FLAC) and FITS (ricepp) by 5-50×.
What’s next
- omnizip-side work: ZSTD SIMD per-se, Brotli Phase C, FLAC FFT (other half), ricepp SIMD (other half). Each closed item directly improves LimniFS without code change here.
- Website polish: more docs, an interactive drop-explorer, a Docker image, package manager recipes.
- FUSE mount stability: production hardening of
limnifs-fuse. - ComposeFS path: kernel-mountable via composefs-style path.
Get started
git clone https://github.com/limnifs/limnifs
cd limnifs/limnifs
cargo build --release -p limni
# Your first .lim image
./target/release/limni limn ./my-dir -o my.lim
./target/release/limni verify my.lim
./target/release/limni ls my.lim
We’re on GitHub at limnifs/limnifs. Issues, PRs, and benchmark results from your own workloads all welcome.
— The LimniFS team, 2026-08-05