Repository navigation
Releases
v0.5.7
Performance tuning and documentation
The default zstd compression level changes from 3 to 1 based on a full compression-level sweep — level 1 is ~2x faster to encode with negligible ratio loss (3.2x vs 3.1x on text payloads). For a throughput buffer where segments are short-lived, encode speed matters more than ratio.
⚠️ Behavioral change: compression default 3 → 1
Existing segments at any compression level still decode correctly (the level is encode-only). New segments will use level 1 unless explicitly overridden. If you need the previous default:
let config = SegmentConfig::builder()
.compression_level(3)
.build();Highlights
- Compression-level sweep: tested all 22 zstd levels across 4 payload kinds. Level 1 is the sweet spot for a throughput buffer — 2x faster than level 3, negligible ratio loss. Levels above 10 collapse throughput to single-digit K items/s for negligible ratio gain.
- Concurrent append benchmark:
append_allis 3.6x faster thanappendat 8 threads (7.1 vs 1.96 Melem/s) because it acquires the mutex once per batch. - LIMITATIONS.md: comprehensive design-limitations documentation now visible on docs.rs via the crate-level rustdoc.
- Scaling example: latency percentiles (p50/p95/p99), real-disk testing (
--dir), encrypted testing (--encrypted), and buffer retention (--keep).
Full changelog
https://github.com/LarsArtmann/segment-buffer/blob/v0.5.7/CHANGELOG.md
v0.5.6
API polish and test-coverage expansion
The SegmentStore trait is now sealed (feature-gated under loom; no impact on default-feature consumers), SegmentConfig gains PartialEq/Eq, FlushPolicy gains a reusable validate() method, and BufferStats/DurabilityPolicy/SegmentConfig gain human-readable Display impls. Test coverage expands with 2 loom tests, 2 property tests, 1 stress test, 1 fuzz target, and 2 criterion benchmarks.
No on-disk format change, no new dependency, no API break for default-feature consumers.
Added — API
PartialEq+EqforSegmentConfig(cipher compared byArc::ptr_eq— no key material in comparisons)FlushPolicy::validate()— reusable validation forBatchOrIntervalMinconstraints- Sealed
SegmentStoretrait — prevents downstream implementations (loom feature re-exports the seal marker) Displayimpls forDurabilityPolicy,BufferStats, andSegmentConfig(cipher masked as[set]/[none])#[must_use]onBufferStats#[doc(alias = "backlog")]onpending_count()
Added — test coverage
- 2 loom tests:
for_each_fromPhase 2 snapshot +iter_fromunder concurrent append (loom suite: 12 → 14) - 2 property tests:
publish_disk_statscorrectness (counters vs directory truth after sync + recover),delete_ackedidempotency (overlapping ack ranges + concurrent append) - 1 stress test:
segment_size_statsunder concurrent flush + delete - 1 fuzz target:
fuzz_for_each_from(7 fuzz targets total) - 2 benchmarks:
bench_segment_size_stats(100/1k/10k segments),bench_cipher(AES-256-GCM + XChaCha20-Poly1305 vs baseline)
Changed
- Internal deduplication:
seq_to_indexhelper,BufferInner::pending_count()/latest_sequence()/pending_start()const fns,compute_store_pressureextracted from inline formula BufferStatsDisplay now formats bytes in human-readable binary units (4.0KB,1.0MB)
Full changelog
v0.5.5
segment-buffer v0.5.5 — 2026-08-04
Non-breaking release: panic-free public API, live segment_count, segment_size_stats tuning primitive, scan-cache TOCTOU fix, strict Clippy lint architecture, and expanded concurrency-property coverage. No API break, no on-disk format change, no new dependency.
Highlights
- Panic-free public API —
for_each_fromno longer holds the buffer mutex across the user callback; the only panic path (assert_not_reentered) and theIterationGuardtype are gone. Re-entrant calls from inside a callback (append,stats,delete_acked, …) are now safe. - Fixed
iter_fromsequence-number bug with gaps —iter_fromnow delegates tofor_each_from, so returned(seq, item)pairs are correct when deleted segments leave gaps. - Live
segment_countinBufferStats— tracked incrementally alongsideapprox_disk_bytes, recalibrated bysync_disk_bytes. - New
segment_size_stats()— on-demand size distribution (count/min/max/mean/p50/p90) of on-disk segment files; the tuning primitive forFlushPolicy::Batch(N). - Scan-cache TOCTOU fix —
mtimeis now captured before the directory scan, so a concurrent rename cannot pair a stale post-rename mtime with a pre-rename segment list. - Strict Clippy lint architecture —
Cargo.tomldeniespedantic + nursery + restrictionlints; library code is fully clippy-clean under the strict set. - Expanded concurrency coverage — added
for_each_from/iter_fromproperty tests under concurrent delete/flush, a high-concurrencysegment_countstress test, and new loom tests for the scan-cache path.
Full changelog
See CHANGELOG.md.
v0.5.4
Non-breaking batch: a new FlushPolicy variant for tiny-segment suppression, a counting-allocator regression guard, expanded concurrency tests, and broader documentation. No API break, no on-disk format change, no new dependency.
Added
-
FlushPolicy::BatchOrIntervalMin— suppresses tiny segment files during low-throughput periods. Flushes immediately atbatch_size, atintervalonly if at leastmin_batchitems are pending, or unconditionally atmax_interval(safety valve). Builder convenience setter with debug-assert validation. Covered by pure decision tests, an integration test, and an exhaustive proptest. -
Allocation-count regression guard (
tests/alloc_guard.rs) — machine-independent heap-allocation budgets on hot paths (warm append, read_from, stats, append+flush). -
MPMC boundary stress tests — proves
read_fromnever returns corrupt data under concurrent delete/flush. -
Domain Language expansions — consistency model, tradeoffs matrix, schema evolution guide.
Changed
-
FlushPolicy time-based tests rewritten as pure decision tests —
should_flushis a pure function; tests now call it directly with syntheticDurationvalues instead ofthread::sleep. Eliminates all CI flakiness from wall-clock dependency. -
SegmentConfigexamples useDefault::default()+ field reassignment (works with#[non_exhaustive]).
Documentation
docs/PERFORMANCE.mdtuning guide:BatchOrIntervalMincallout for low-throughput producers.
Full changelog: https://github.com/LarsArtmann/segment-buffer/blob/v0.5.4/CHANGELOG.md
💘 Generated with Crush
v0.5.3
Dependency bump patch: chacha20poly1305 0.10 → 0.11
No API change, no on-disk format change.
Changed
- chacha20poly1305 0.10 → 0.11: migrated
XNonce::from_slice→XNonce::from/try_intoin the XChaCha20-Poly1305 cipher and thebring_your_own_cipherexample, mirroring the same migration already done foraes-gcm0.11. The on-disk format is unchanged.
Fixed
docs/CIPHERS.mdnonce snippet drifted from its runnable counterpart (examples/bring_your_own_cipher.rs) — the markdown still showed the deprecatedNonce::from_slicepattern after the 0.11 migration. Both encrypt and decrypt snippets now match the compiled code.
v0.5.2 — Performance, docs & CI hardening patch
No API break, no on-disk format change, no new dependency. Drop-in upgrade from v0.5.1.
cargo update -p segment-buffer
Performance
flush()now recycles theunflushedVec capacity across flushes. Previouslystd::mem::takeleft the field at zero capacity, forcing the next batch through ~log2(N) reallocs. The new path reserves the previous batch's capacity inside the same lock scope — one upfront allocation replaces incremental growth. No public API change, no change to the concurrency contract.- Performance tuning guide (
docs/PERFORMANCE.md§ "Tuning for your workload"). Surfaces the four config-only Tier 0 levers (DurabilityPolicy,FlushPolicy,compression_level,for_each_from) in impact order, each with a code snippet and a "when NOT to use" guardrail. examples/background_flush.rs— the recommended pattern for p99-sensitive producers:FlushPolicy::Manual+ a caller-owned timer thread with an atomic shutdown flag and a final synchronous flush before exit.
Documentation
- README expansion: Cargo features table (
default = [],encryption,loom,fuzz),append_allone-liner in Quickstart,iter_fromexample next to the drain loop,open_with_reportcrash-recovery example. SegmentBufferrustdoc: new# Concurrencysection (MPMC semantics, single-processflockinvariant, mutex-never-held-across-I/O rule, loom-provendelete_acked+appendinterleaving, re-entrancy panic). Added#[doc(alias = "queue" | "spool" | "wal" | "writeahead" | "log")]for search discoverability. Cross-linked allexamples/*.rsfrom the crate-root rustdoc.
CI / tooling
actionlintstep added toscripts/verify-gate.shand a matching job inci.yml.- lychee redirect URLs documented in
.github/lychee.toml(idiomatic docs.rs patterns that look like errors but aren't). - Historical doc annotations: 12 of 23
docs/{status,planning,perf}/2026-07-2*files annotated with non-destructive resolution notes (inline corrections for stale claims, end-of-file appendices for completed TODO items).
Verification
All gates green on commit 512d74a:
cargo fmt --all -- --checkcargo clippy --all-targets --features encryption -- -D warningscargo test --no-fail-fast --features encryptioncargo doc --no-deps --features encryptioncargo publish --dry-run --features encryption- CI + Nix workflows:
success(verified viagh run list)
v0.5.1 — crates.io description + keywords fix
Metadata-only patch. No API change, no on-disk format change, no migration.
v0.5.0 shipped the reframing ("high-throughput local buffer for cloud sync") in the README and AGENTS.md, but the three highest-visibility discovery surfaces still carried the old "durable bounded queue" framing. This release fixes all three.
What changed
Cargo.tomldescription (→ crates.io search results): now leads with "High-throughput local buffer for cloud sync" (was "Durable bounded queue"). This is the #1 discovery path for new users.Cargo.tomlkeywords:["queue", "cloud-sync", "spool", "segment", "buffer"](was["queue", "disk", "durable", "segment", "buffer"]). Dropped the generic "disk"/"durable" in favor of target-audience terms.- Crate-root rustdoc (
src/lib.rs//!): docs.rs module page and IDE hovers now show the cloud-sync positioning. SegmentBuffer<T>struct doc: same reframing.
Also fixed: the CHANGELOG [0.5.0] section header was missing (a multiedit silently failed during the v0.5.0 release session, leaving the v0.5.0 content under [Unreleased]). The header structure is now correct.
Upgrade
cargo update -p segment-buffer
Drop-in from v0.5.0 — no code changes required.
Verification
All gates green on the tagged commit (8994421):
cargo fmt,cargo clippy -- -D warnings,cargo test --features encryption(96 + 38 passed)RUSTDOCFLAGS="-D warnings" cargo doc --no-deps --features encryptioncargo publish --dry-run- CI (all 10 jobs) + Nix workflows:
success
v0.5.0 — cloud-sync throughput batch
The reframing release: segment-buffer is a single-process throughput buffer for cloud sync, with configurable durability, optional performant encryption, and at-least-once delivery.
Migration (breaking changes)
Four breaking changes, batched so you upgrade once. All are API-surface changes — the on-disk format is unchanged; existing segment files (including encrypted and legacy monitor365 files) read without migration.
1. SegmentConfig.cipher: Box → Arc
// before (v0.4.x)
config.cipher = Some(Box::new(cipher));
// after (v0.5.0)
use std::sync::Arc;
let config = SegmentConfig::builder().cipher(Arc::new(cipher)).build();Why: makes SegmentConfig and its builder Clone (the cipher Arc is shared between clones).
2. SegmentError::Io: path: Option<PathBuf> → site: IoSite
// before (v0.4.x)
match err {
SegmentError::Io { path, source } => { /* path: Option<PathBuf> */ }
}
// after (v0.5.0)
match err {
SegmentError::Io { site, source } => {
// site: IoSite — Dir | Segment(PathBuf) | Unknown
}
}Why: None was overloaded (directory-level failure vs no context attached). IoSite makes every case explicit. with_path still works; new with_dir tags directory-level failures.
3. SegmentStore::write_atomic signature (loom feature only)
// before (v0.4.x)
fn write_atomic(&self, range: SegmentRange, bytes: Vec<u8>) -> Result<()>;
// after (v0.5.0)
fn write_atomic(&self, range: SegmentRange, bytes: Vec<u8>, policy: DurabilityPolicy) -> Result<()>;Who is affected: only callers who implement SegmentStore externally (the trait is reachable only under the loom Cargo feature). The production RealStore is constructed internally by open().
4. SegmentConfig new field: durability: DurabilityPolicy
#[non_exhaustive] struct — direct struct-literal construction was already forbidden; builder callers are unaffected. Internal struct-literal callers must add durability: DurabilityPolicy::Segment (or Default::default()).
What's new
Configurable durability
use segment_buffer::{DurabilityPolicy, SegmentConfig};
// Cloud-sync: skip fsync (cloud is the durable layer)
let config = SegmentConfig::builder()
.durability(DurabilityPolicy::Throughput)
.build();
// Standalone queue: maximum crash safety
let config = SegmentConfig::builder()
.durability(DurabilityPolicy::Maximal)
.build();| Policy | Fsync file | Fsync dir after rename | Worst-case crash loss |
|---|---|---|---|
Maximal |
yes | yes | last in-flight flush only |
Segment (default) |
yes | no | rename window (~5–30s, kernel-dependent) |
Throughput |
no | no | entire OS dirty window (~30s); cloud is durable |
Throughput is ~12% faster than Segment, ~26% faster than Maximal on bench_durability_policy.
Single-process lock (flock)
open() now acquires an exclusive flock on <dir>/.segment-buffer.lock. If another process holds it, open() returns SegmentError::Locked { path } immediately (no block, no timeout). The lock is released on Drop.
XChaCha20-Poly1305 cipher
use segment_buffer::SegmentConfig;
// recommended_cipher(key) installs XChaCha20Poly1305Cipher
let config = SegmentConfig::builder()
.recommended_cipher(&key)
.build();24-byte extended nonce (no AES-GCM 2³²-message-per-key limit), constant-time in software (no AES-NI dependency). Legacy AES-GCM segments still decrypt through AesGcmCipher.
Owned-item iterator
for (seq, item) in buf.iter_from(start, 1000)? {
// standard Iterator combinators work
}SegmentIter<'_, T> yields (seq, item) pairs; for_each_from stays for the zero-copy lending path.
Performance
- Pooled write-side zstd
Compressor:append/batch_115.09 µs → 7.75 µs (2.07× faster). Net ~2.3× vs v0.1.0 at small batches. - Pooled read-side zstd
Decompressor: symmetric pooling forread_from/for_each_from. - Latency histogram stress test: p50/p90/p99/p99.9 per-append latency in CI.
Loom-proven concurrency
4 new loom tests exhaustively enumerate every two-thread schedule of the delete_acked + append interleaving, proving head_seq <= pending_start (the "honest backlog" invariant) across all schedules.
Three new examples
cloud_sync.rs— runnable at-least-once drain loop (happy + retry).cloud_sync_disk_full.rs— metrics-not-policy backpressure pattern.idempotent_server.rs— consumer-side(producer_id, seq)dedup pattern.
Full changelog
See CHANGELOG.md [0.5.0] for the complete per-item detail, including internal changes, dependency bumps, and process hardening.
Verification gate (all green on the tagged commit b7904d6):
cargo fmt --all -- --checkcargo clippy --all-targets {,--features encryption} -- -D warningscargo test --no-fail-fast --features encryption— 96 unit/property + 38 doctests passedRUSTDOCFLAGS="-D warnings" cargo doc --no-deps {,--features encryption}RUSTFLAGS="--cfg loom" cargo test --features loom --test loom --release— 9 passed- CI + Nix workflows:
success
v0.4.2 — process debt + semver-leak closure
The "process debt + semver-leak closure" release. All changes are additive or
internal (no breaking changes; drop-in upgrade from v0.4.1). Driven by the
brutally honest v0.4.1 self-review, which uncovered four critical gaps
(fuzz_hooks semver leak, no CI loom job, missing dual audit+deny gate,
missing domain-language docs).
Added
fuzzCargo feature — opt-in feature exposing thefuzz_hooksmodule
(parse_filename,wrap_envelope,unwrap_envelope,SegmentRange) for
out-of-tree fuzz targets. Items reachable through this feature are not
part of the semver contract and may change in any release without a
major bump. The in-tree fuzz crate enables this feature infuzz/Cargo.toml.- CI
loomjob —RUSTFLAGS="--cfg loom" cargo test --features loom --release --test loomruns on every push and PR. The#![cfg(loom)]test
file is invisible tocargo testby default and rotted silently between
v0.4.0 and v0.4.1 (the v0.4.0FlushPolicychange removed fields the loom
test still referenced); this job prevents that class of regression. - Fuzz target:
fuzz_append_all— fuzzesappend_allover arbitrary
iterator behavior (empty, single, large) with 4 invariants: never panics,
pending_countgrows by batch size,last_seqadvances correctly,
follow-up empty iterator is a no-op. 771k+ runs / 16s, zero crashes. - Property test:
append_all_assigns_contiguous_sequences_across_batches
— varies batch sizes (0–50) across multipleappend_allcalls and asserts
seqs stay contiguous at the batch boundary. Catches off-by-one regressions
in thenext_seqcounter. - Property test:
sync_disk_bytes_matches_actual_disk_usage— after
every mutation cycle (flush + append),sync_disk_bytes()must bring
stats().approx_disk_bytesinto exact agreement with the sum of.zst
file sizes on disk. Catches reconciliation drift. docs/DOMAIN_LANGUAGE.md— glossary for segment, head_seq, next_seq,
acked_seq, envelope, flush, recover. Codifies the ubiquitous vocabulary
for issues, doc comments, and commit messages.docs/CIPHERS.md— cipher internals + worked bring-your-own-AEAD
examples: ChaCha20-Poly1305 (via thechacha20poly1305crate), no-op
cipher (testing only), and an explanation of what the cipher does and
does not see (item boundaries, filename, envelope).docs/perf/2026-07-19_v0.4.1_stress_throughput.md— v0.4.1 stress
test baseline: ~397k events/sec under 8-writer × 2-reader contention with
FlushPolicy::Manual. Reproduction command + interpretation included.
Changed
fuzz_hooksis now#[cfg(any(test, feature = "fuzz"))]instead of
#[doc(hidden)] pub. This closes a v0.4.1-introduced semver leak.
#[doc(hidden)]hides items from rustdoc but does NOT remove them from
the public API surface; the cfg gate does both. SeeCONTRIBUTING.md→
"Internal hooks:#[cfg]over#[doc(hidden)]" for the rationale.Cargo.tomldescription rewritten for crates.io search clarity:
"Durable bounded queue: batch-spills to zstd+CBOR segment files with
ack-based deletion and filename-based crash recovery. No WAL, no metadata
db."- CI
supply-chainjob renamed tocargo audit + cargo denyfor
discoverability (no behavior change).
Fixed
- Broken
AesGcmCipherdoc link warning under default features (the
[AesGcmCipher]intradoc link failed to resolve when theencryption
feature was off). Replaced with a prose reference to "theAesGcmCipher
behind theencryptionfeature".RUSTDOCFLAGS="-D warnings" cargo doc
is now clean under all feature combinations.
Internal
- AGENTS.md verification discipline gained two new hard rules:
- Rule 5: "The supply-chain gate is BOTH
cargo auditANDcargo deny check." They pull from different advisory sources in edge cases. - Rule 6: "The loom gate is
RUSTFLAGS='--cfg loom' cargo test --features loom --test loom --release."#![cfg(loom)]files are invisible to
defaultcargo testand silently rot.
- Rule 5: "The supply-chain gate is BOTH
- CONTRIBUTING.md gained a new section: "Internal hooks:
#[cfg]over
#[doc(hidden)]" — codifies the lesson from the v0.4.1 semver leak so
the next agent doesn't repeat it.
v0.4.1 — safety + trust depth
The "safety + trust depth" release. All changes are additive (no breaking
changes). On-disk format, encryption contract, and API shapes are unchanged
from v0.4.0.
Added
for_each_fromre-entrancy guard — calling any&selfmethod on the
buffer from inside afor_each_fromcallback now panics with a clear message
({method}: cannot call from within a for_each_from callback) instead of
silently deadlocking. The guard is Drop-cleared for panic safety, so a
panicking callback does not brick the buffer. (Closes the v0.4.0 footgun.)append_all<I: IntoIterator<Item = T>>— batch append under a single
lock acquisition. Returns the last assigned sequence number. The whole batch
gets contiguous seqs atomically; flush is checked once at the end. Bench:
benches/bench_append_all.rsquantifies the lock-acquisition saving vs a
loop ofappend.SegmentBuffer::path()— returns&Pathto the segment directory.
Removes the need toDebug-parse the buffer to reach the directory.SegmentBuffer::config()— returns&SegmentConfigthe buffer was
opened with. Lets callers inspect the flush policy, compression level, and
cipher presence without re-deriving them.SegmentBuffer::sync_disk_bytes()— re-stats the segment directory and
stores the authoritative total. Corrects drift when an external process
(backup, compaction, manual cleanup) touches the directory.fuzz_hooksmodule (#[doc(hidden)]) — exposesparse_filename,
unwrap_envelope,wrap_envelope, andSegmentRangefor fuzz targets.
Not part of the public API.- Two new fuzz targets:
fuzz_parse_filename(17M+ runs / 16s, zero
crashes) andfuzz_envelope(15M+ runs / 16s, zero crashes; fuzzer
discovered theSBF1magic dictionary entry organically). - Property tests:
FlushPolicy::Manualnever auto-flushes (up to 499
appends);read_from(start, limit)⊆read_from(start, larger_limit);
delete_ackedpending_count is monotone non-increasing;for_each_from
visits the same items asread_from. - Throughput stress test: 8 writers × 2 readers × 80k events, reports
events/sec under contention. - Loom test:
append_allbatch atomicity under concurrentappend. - CI workflows: nightly cargo-fuzz (
fuzz.yml), weekly flake.lock update
(update-flake-lock.yml), cargo-audit + cargo-deny supply-chain job,
dependabot.yml for GitHub Actions + cargo. - Docs:
docs/PERFORMANCE.md(methodology),docs/RELEASE.md(runbook),
docs/MSRV.md(policy).
Changed
packages.defaultinflake.nixnow builds with Rust 1.85 (the declared
MSRV) viacraneLibMsrv, proving the package builds on its floor — not just
on whatever nixpkgs stable ships.dtolnay/rust-toolchainpinned to a commit hash in all CI workflows
(supply-chain hygiene).nix.ymlcachix-action guarded to the canonical repo + optional token,
so forks don't attempt uploads to a cache that doesn't exist.- README perf paragraph now carries the methodology caveat inline ("single-
run, single-machine; see docs/PERFORMANCE.md"). - README comparison table now carries a freshness disclaimer.
- AGENTS.md session-end checklist gains "release scope approval" and
"draft release notes before tagging" items (process guard against the
v0.4.0 failure).
Fixed
- Loom test was broken since v0.4.0 — referenced removed
max_batch_events
/flush_interval_secsfields and had an inner attribute inside a function
body. Now usesFlushPolicy::Manualvia the builder API. The breakage was
invisible because#![cfg(loom)]skips compilation unless--cfg loomis set.
Internal
SegmentRangefields are nowpub(werepub(crate)) to support the
fuzz_hooksre-export. Thesegmentmodule itself stays private; the fields
are only reachable through#[doc(hidden)] fuzz_hooks.