pub enum Value {
Show 18 variants
Str(SmallBytes),
Int(i64),
ArcBulk(Arc<Box<[u8]>>),
Hash(Arc<KevyMap<SmallBytes, SmallBytes>>),
SegHash(Arc<SegMap<SmallBytes>>),
List(Arc<VecDeque<Vec<u8>>>),
SegList(Arc<SegListData>),
Set(Arc<KevySet<SmallBytes>>),
SegSet(Arc<SegMap<()>>),
ZSet(Arc<ZSetData>),
SegZSet(Arc<SegZSetData>),
Stream(Arc<StreamData>),
SmallSetInline(SmallSetData),
SmallHashInline(SmallHashData),
PackedRow(PackedRow),
SmallListInline(SmallListData),
SmallZSetInline(SmallZSetData),
Cold(ColdRef),
}Expand description
A stored value. One variant per Redis type.
The collection variants live behind a shared pointer (Arc) so the
enum is only as big as Str (24 B) + tag = 32 B, not the 56 B ZSetData
— every Entry (incl. the common string case) is then ~48 B instead of
~80 B, so the bucket array is ~40% denser/smaller (fewer cache misses on
a large keyspace, less RSS). The extra pointer-chase lands only on
collection ops, not the hot string GET path.
Arc (same 8 B as the previous Box) is what makes O(short-pause)
persistence possible: crate::Store::collect_snapshot bumps each
collection’s refcount instead of serializing it, and a background thread
walks the frozen payloads at leisure. Mutations go through
std::sync::Arc::make_mut — a single uniqueness check (the steady
state, no snapshot in flight) or a copy-on-write deep clone when a
snapshot still holds the payload.
Str holds a SmallBytes (24 B, same size as Vec<u8>) so byte strings
up to 22 bytes live inline inside the bucket, killing the second cache
miss the value pointer-chase used to cost on large-keyspace GETs.
Clone is the snapshot-collect primitive: Str copies its bytes
(inline = 24 B memcpy; heap = one allocation), collections bump a
refcount. See crate::Store::collect_snapshot.
Variants§
Str(SmallBytes)
A byte string, inline up to 22 bytes — see the type doc above for why that boundary is where it is.
Int(i64)
Following valkey’s OBJ_ENCODING_INT: when a SET
stores a clean canonical i64 ASCII string (parses round-trip), we
keep the integer as i64 rather than as 22 B of inline bytes.
Wins on INCR (in-place += delta, no parse / no format / no
SmallBytes wrap) and on memory (8 B vs 24 B). GET formats it via
a per-Store scratch buffer.
ArcBulk(Arc<Box<[u8]>>)
Values larger
than BULK_THRESHOLD bytes get stored behind an
Arc<Box<[u8]>> instead of a heap-backed SmallBytes. The Arc
lets the io_uring reactor’s reply path borrow the bytes across
the SQE→CQE window safely (Arc clone keeps them alive even if
the keyspace mutates) — the prerequisite for the writev
zero-copy bulk reply path, which skips the per-GET memcpy from
value storage into the per-conn output buffer.
Why Arc<Box<[u8]>> and not Arc<[u8]>: Arc<[u8]> is a
DST-backed ArcInner<[u8]> = { strong, weak, [u8; N] } whose
data slot sits past the refcount words. Arc::from(Vec<u8>)
allocates a fresh ArcInner and copy_from_slices the bytes
— a hard mandatory 64 KiB memcpy on every big SET. With
Arc<Box<[u8]>>, the Box<[u8]> wrapper occupies the Arc’s
data slot (16 B), pointing AT an unchanged heap buffer; so
Arc::new(vec.into_boxed_slice()) is truly zero-copy
(the boxed slice’s allocation stays put — only the 32-byte
ArcInner is freshly malloced). Per-GET cost: one extra
pointer dereference (&**arc to get &[u8]), measured to be
negligible vs the per-SET memcpy savings. The Arc<[u8]>
mandatory copy was confirmed with perf-record before switching
to Arc<Box<[u8]>>.
Small values stay on Str(SmallBytes) because the inline
cache-line storage beats an Arc indirection for the common case.
Hash(Arc<KevyMap<SmallBytes, SmallBytes>>)
A hash below [HS_PROMOTE] elements: one map behind one Arc, so
a snapshot pins it whole and the first write during that window
deep-clones it. Past that size it becomes SegHash.
SegHash(Arc<SegMap<SmallBytes>>)
A hash past seg_map::HS_PROMOTE fields: an extendible-hash
directory of Arc-shared buckets — a COW write under a live
snapshot view clones one bucket, not the whole value.
List(Arc<VecDeque<Vec<u8>>>)
A list below [SEG_PROMOTE] elements: one deque behind one Arc,
with the same whole-value copy-on-write. Past that size it becomes
SegList.
SegList(Arc<SegListData>)
A list past crate::list_seg::SEG_PROMOTE elements: a deque of
Arc-shared segments so a COW write under a live snapshot view
clones one segment, not the whole (possibly multi-GB) value. See
list_seg.rs for the promotion contract.
Set(Arc<KevySet<SmallBytes>>)
A set below [HS_PROMOTE] elements, on the same terms as Hash.
SegSet(Arc<SegMap<()>>)
A set past seg_map::HS_PROMOTE members — the set door of the
same bucket-sharded COW as Value::SegHash.
ZSet(Arc<ZSetData>)
A sorted set: members with scores, plus the order-statistic tree that makes rank queries a lookup rather than a scan.
SegZSet(Arc<SegZSetData>)
A zset past zset_seg::Z_PROMOTE members — sharded member map
- ordered segments; COW writes clone one bucket + one segment.
Stream(Arc<StreamData>)
A stream: entries, consumer groups and their pending lists. Never segmented — a stream trims from the front instead of growing without bound.
SmallSetInline(SmallSetData)
Valkey-orthodox encoding switch: tiny sets (1-N
short members) live inline in 24 bytes instead of behind
Arc<SetData> — matches valkey’s OBJ_ENCODING_LISTPACK for
sets, which is what redis-benchmark -t sadd default -r 0
(cardinality stays at 1 forever, single 20-byte literal member)
measures. On overflow (crate::small_set::SmallSetData::try_add
returns NoRoom) the set is promoted to Value::Set(Arc<SetData>)
— the Swiss-table path that wins for larger cardinalities.
SmallHashInline(SmallHashData)
Tiny hashes
(1-2 short field-value pairs) live inline in 24 bytes; promoted
to Value::Hash(Arc<HashData>) on overflow. Mirrors valkey’s
OBJ_ENCODING_LISTPACK for hashes.
PackedRow(PackedRow)
A declared table’s row: the columns in declared order, in one payload buffer, with no field names and no per-row table.
Reachable only for a key under a declared prefix — an undeclared hash
keeps Value::Hash.
SmallListInline(SmallListData)
Tiny lists inline encoding; promoted to
Value::List(Arc<ListData>) on overflow.
SmallZSetInline(SmallZSetData)
Tiny sorted sets inline encoding; promoted to
Value::ZSet(Arc<ZSetData>) on overflow.
Cold(ColdRef)
A demoted (tiered-to-disk) value’s in-map stub. The two-stage
funnel (tier module) resolves this before any typed match sees
it: stage 1 answers existence/TYPE/TTL from the stub with zero
IO; stage 2 materializes (serve or promote) only on a type
match. Cloning a Cold clones the STUB, not the record — paths
that duplicate values (COPY, cross-shard ship) materialize
first so two stubs never alias one vlog record.
Implementations§
Source§impl Value
impl Value
Sourcepub fn weight(&self) -> u64
pub fn weight(&self) -> u64
Approximate heap bytes the value owns. Excludes the inline Entry /
bucket slot — that’s a separate per-entry constant accounted by the
store. Walks collections, so prefer the cached Entry::weight for
hot-path accounting and only call this when bootstrapping or after a
load-from-snapshot.
Sourcepub fn is_heap_heavy(&self) -> bool
pub fn is_heap_heavy(&self) -> bool
Whether this value’s Drop is heavy enough to deserve being
shipped to the bio thread instead of freed inline. Fast: every
variant decides off a sub-field cheap to inspect (no recursive
walk), so it’s safe to call on every overwrite-SET on the hot
path. The threshold is intentionally conservative — small Arcs
and every short string stay on inline-drop where jemalloc small-
class is sub-µs and a cross-thread hand-off would lose.