- Public `(len, stream)` shape: `Store::get` returns
`Option<(u64, Blob)>`; `Blob` (src/facade.rs) is a bounded, re-entrant
windowed reader over the new crate-internal `AnyCursor` tier dispatch
(mem materializing / local pread body families) — engine cursors
never leak onto the surface; no chunk-iterator machinery.
- Verification-on-get pinned against ADR-006: kv-tier materialized
reads whole-blob hash-check at get time (mismatch = Err(Verification),
never a further fall-through); the large tier's streaming open is
metadata + handle only (put-path verification + slice digests are its
story); `read_range` carries slice digests only, on both tiers.
- `stat` rides the trait's `size` probe (never a content load) into
`EntryMeta { len, algorithm }`; `has` is the pure existence probe;
both fall through kv -> large like every key-addressed op (ADR-012
§6.1) — consult order, not routing (never consults `tier_for`).
- Miss shapes enforced (the error module's behavioral note): both-tier
misses are Ok(None) on get/stat, false on has, Err(Missing) on
read_range; `src/error.rs` wave-2 deferral notes updated to enforced
state.
- `list` decided Vec<Key>: materialized, sorted total-order union of
both tiers (the sweep consumes the engines' enumeration directly);
malformed pool bytes are fatal `Err(KeyInvalid)`, never a silent
skip.
- `KeyInvalid` at the facade boundary: ops take opaque key bytes and
convert via `Key::from_bytes` once (`boundary_key`); engines never
see typed keys.
- Twelve read-path tests: virgin-store shapes per mode (dual-tier,
mem-only, kv-only), the `(len, stream)` windows, fall-through,
kv-first (decoy content in the large tier), both verification
postures, list union/sort/pool-fatality, and malformed-key
rejection on every op.
Task: tasks/store-read-paths.md (design points recorded in Notes for
the wave-2 review gate).
Verification: cargo test (default), cargo test --features mem, cargo
test --all-features green; cargo clippy --all-targets -- -D warnings
green in all three feature shapes; cargo fmt --check green; cargo doc
--no-deps clean beyond the pre-existing private-intra-doc-links class.
124 lines
5.1 KiB
Rust
124 lines
5.1 KiB
Rust
//! Size-threshold dispatch between the kv and large tiers (ADR-003):
|
||
//! routing as a pure function of content length.
|
||
//!
|
||
//! **The routing seam the facade programs through.** [`route`] is the
|
||
//! pure function — `route(len, threshold) → Tier`, **at-or-below the
|
||
//! threshold stays on the kv tier** (the boundary's pinned reading:
|
||
//! store-put-paths' "≤ threshold → kv; > threshold → large") — and
|
||
//! [`DEFAULT_DISPATCH_THRESHOLD`] is the shipped default, 128 KiB, the
|
||
//! conservative edge of the measured ~128–256 KiB crossover zone (POC #3
|
||
//! A4). Purity is the ADR-003 invariant: same content ⇒ same length ⇒
|
||
//! same tier, re-puts are deterministic, no content ever migrates.
|
||
//!
|
||
//! The threshold is **constructor-tunable** (a per-deployment media
|
||
//! re-tuning, never an API change): the facade's mode constructors take
|
||
//! it explicitly and default their docs to this constant. Read-side
|
||
//! consult order — kv first, then large — is the facade's fall-through
|
||
//! general rule (ADR-012 §6.1), carried on the `Store` facade type
|
||
//! ([`crate::store`]); mode-specific behavior (kv-only's put-time
|
||
//! rejection, mem-only's no-threshold posture) lives on the facade's
|
||
//! mode machinery, not here: this module routes lengths, it does not
|
||
//! own modes.
|
||
//!
|
||
//! The `#![allow(dead_code)]` below marks the seam's scope: `route` is
|
||
//! driven by the facade's write-side tier selection (`Store::tier_for`),
|
||
//! whose own caller is the put seam (store-put-paths); the allow shrinks
|
||
//! as those land. Read-side ops do not consult `route` — the
|
||
//! fall-through consult order is the facade's (kv first, then large;
|
||
//! ADR-012 §6.1).
|
||
|
||
#![allow(dead_code)]
|
||
|
||
/// The default dispatch threshold in bytes: 128 KiB — the conservative
|
||
/// edge of the measured ~128–256 KiB crossover zone (ADR-003; POC #3
|
||
/// finding A4's first-party benchmark). Constructor-tunable per
|
||
/// deployment media; retuning is a constructor parameter, never an API
|
||
/// change.
|
||
pub const DEFAULT_DISPATCH_THRESHOLD: u64 = 128 * 1024;
|
||
|
||
/// The two tiers a content length routes between. `pub(crate)`: the
|
||
/// enum is the routing *answer*, not the engine — tier names the
|
||
/// routing class (ADR-011 §1), and which engine backs the tier is the
|
||
/// constructor's choice, never this type's business.
|
||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||
pub(crate) enum Tier {
|
||
/// The kv tier — at-or-below-threshold content.
|
||
Kv,
|
||
/// The large tier — over-threshold content.
|
||
Large,
|
||
}
|
||
|
||
/// The pure routing function: over the threshold goes to the large
|
||
/// tier; **at or below** (exactly-at-threshold included) stays on the
|
||
/// kv tier. The boundary reading is pinned here and boundary-tested
|
||
/// below — `store-put-paths` and the read paths consume this exact
|
||
/// reading through the facade's seam.
|
||
///
|
||
/// Total purity discipline (ADR-003): a pure function of `len` — no
|
||
/// engine input, no namespace, no state. Same length is the same tier
|
||
/// forever, across mixed put paths, re-puts, and restarts.
|
||
pub(crate) fn route(len: u64, threshold: u64) -> Tier {
|
||
if len > threshold {
|
||
Tier::Large
|
||
} else {
|
||
Tier::Kv
|
||
}
|
||
}
|
||
|
||
#[cfg(test)]
|
||
mod tests {
|
||
use super::*;
|
||
|
||
/// The shipped default is 128 KiB — pinned numerically (131072).
|
||
#[test]
|
||
fn default_threshold_is_128_kib() {
|
||
assert_eq!(DEFAULT_DISPATCH_THRESHOLD, 131_072);
|
||
assert_eq!(DEFAULT_DISPATCH_THRESHOLD, 128 * 1024);
|
||
}
|
||
|
||
/// The pinned boundary reading: exactly-at-threshold routes to the
|
||
/// kv tier (≤ threshold → kv); one over routes large.
|
||
#[test]
|
||
fn exactly_at_threshold_routes_kv_one_over_routes_large() {
|
||
assert_eq!(route(4096, 4096), Tier::Kv);
|
||
assert_eq!(route(4095, 4096), Tier::Kv);
|
||
assert_eq!(route(4097, 4096), Tier::Large);
|
||
}
|
||
|
||
/// Boundary values across the domain: zero length, the default
|
||
/// threshold, and the degenerate tunings a constructor may pass —
|
||
/// all route deterministically.
|
||
#[test]
|
||
fn boundary_values_route_deterministically() {
|
||
assert_eq!(route(0, DEFAULT_DISPATCH_THRESHOLD), Tier::Kv);
|
||
assert_eq!(route(131_071, DEFAULT_DISPATCH_THRESHOLD), Tier::Kv);
|
||
assert_eq!(route(131_072, DEFAULT_DISPATCH_THRESHOLD), Tier::Kv);
|
||
assert_eq!(route(131_073, DEFAULT_DISPATCH_THRESHOLD), Tier::Large);
|
||
|
||
assert_eq!(route(0, 0), Tier::Kv, "zero-length content always kv");
|
||
assert_eq!(
|
||
route(1, 0),
|
||
Tier::Large,
|
||
"threshold 0: everything else large"
|
||
);
|
||
|
||
assert_eq!(route(0, u64::MAX), Tier::Kv, "max threshold: nothing large");
|
||
assert_eq!(
|
||
route(u64::MAX, u64::MAX),
|
||
Tier::Kv,
|
||
"at-threshold is kv even at MAX"
|
||
);
|
||
}
|
||
|
||
/// Purity (the ADR-003 invariant): the same length is the same tier
|
||
/// on every call — routing derives from inputs alone.
|
||
#[test]
|
||
fn routing_is_a_pure_function_of_length() {
|
||
let len = 103_487;
|
||
let first = route(len, DEFAULT_DISPATCH_THRESHOLD);
|
||
for _ in 0..32 {
|
||
assert_eq!(route(len, DEFAULT_DISPATCH_THRESHOLD), first);
|
||
}
|
||
}
|
||
}
|