2424//! command layer. The folded-header metadata (below) is laid out as the FAM
2525//! version will pack it, so that follow-up is a representation change only.
2626
27- use crate :: encoding:: { Classified , EMBSTR_THRESHOLD , classify} ;
27+ use crate :: encoding:: { Classified , classify} ;
2828use crate :: scan_hash;
2929use bytes:: Bytes ;
3030use hashbrown:: HashMap ;
@@ -41,12 +41,6 @@ use ironcache_storage::{
4141} ;
4242use std:: collections:: { BTreeSet , VecDeque } ;
4343
44- /// The inline-value buffer capacity (embstr). Matches [`EMBSTR_THRESHOLD`]; a
45- /// value classified as embstr fits here without a separate allocation in the
46- /// eventual FAM layout. In the safe rep it is a fixed-size inline array plus a
47- /// length, so an embstr value adds no heap allocation beyond the `KvObj` itself.
48- pub const INLINE_CAP : usize = EMBSTR_THRESHOLD ;
49-
5044/// The packed per-key header (OBJECT_LAYOUT.md "packed header and metadata bits").
5145///
5246/// In the eventual FAM layout these fields are bit-packed into a few bytes (type +
@@ -101,66 +95,53 @@ impl Header {
10195 }
10296}
10397
104- /// A small inline string buffer ([`INLINE_CAP`] bytes plus a length), the safe-rep
105- /// stand-in for the FAM inline-value region. An embstr value lives here with no
106- /// extra heap allocation.
107- #[ derive( Debug , Clone ) ]
108- pub struct InlineBuf {
109- buf : [ u8 ; INLINE_CAP ] ,
110- len : u8 ,
111- }
112-
113- impl InlineBuf {
114- /// Build from bytes that are known to fit ([`INLINE_CAP`]). Panics if too long;
115- /// callers (the classifier path) only construct this for embstr-sized values.
116- #[ must_use]
117- pub fn from_bytes ( bytes : & [ u8 ] ) -> Self {
118- assert ! (
119- bytes. len( ) <= INLINE_CAP ,
120- "InlineBuf overflow: {} > {INLINE_CAP}" ,
121- bytes. len( )
122- ) ;
123- let mut buf = [ 0u8 ; INLINE_CAP ] ;
124- buf[ ..bytes. len ( ) ] . copy_from_slice ( bytes) ;
125- InlineBuf {
126- buf,
127- len : bytes. len ( ) as u8 ,
128- }
129- }
130-
131- /// The inline bytes.
132- #[ must_use]
133- pub fn as_bytes ( & self ) -> & [ u8 ] {
134- & self . buf [ ..self . len as usize ]
135- }
136- }
137-
13898/// The value representation inside a [`KvObj`] (ENCODINGS.md #112).
13999#[ derive( Debug , Clone ) ]
140100pub enum ValueRepr {
141101 /// An int-encoded value: the raw i64, NO value allocation (the decimal bytes
142102 /// are materialized on read). `OBJECT ENCODING` -> int.
143103 Int ( i64 ) ,
144- /// A short string stored inline. `OBJECT ENCODING` -> embstr.
145- Inline ( InlineBuf ) ,
104+ /// A short string (embstr). `OBJECT ENCODING` -> embstr.
105+ ///
106+ /// BOXED (memory Round 2): the bytes live behind a `Box<[u8]>` rather than a
107+ /// fixed inline buffer, so this variant is one pointer wide and the largest
108+ /// `ValueRepr` variant shrinks to a `Box<[u8]>`, cutting every per-key `KvObj`
109+ /// and the hashbrown table slot. The embstr-vs-raw distinction is the SAME (it is
110+ /// recorded in [`Header::encoding`], NOT by the variant): a value classified as
111+ /// embstr by [`crate::encoding::EMBSTR_THRESHOLD`] is `Inline`, a longer one is
112+ /// [`ValueRepr::Raw`],
113+ /// and `OBJECT ENCODING` reports `embstr` / `raw` exactly as before. Redis/Valkey
114+ /// also heap-allocate the object body, so this is allocation-parity with redis
115+ /// plus a smaller slot.
116+ Inline ( Box < [ u8 ] > ) ,
146117 /// A long string stored out-of-line. `OBJECT ENCODING` -> raw.
147118 Raw ( Box < [ u8 ] > ) ,
148119 /// A LIST value (PR-5). `OBJECT ENCODING` -> `listpack` while small, `quicklist`
149120 /// once over the threshold (a pure function of the active repr, #40).
150- List ( ListVal ) ,
121+ ///
122+ /// BOXED (memory Round 1): the four collection structs are the large `ValueRepr`
123+ /// variants (`ListVal` 40 / `HashVal` 40 / `SetVal` 48 / `ZSetVal` 64). Holding them
124+ /// behind a `Box` drops `ValueRepr` to the string-variant bound, which shrinks every
125+ /// per-key `KvObj` and the hashbrown table slot. The string/int hot path
126+ /// (`Int`/`Inline`/`Raw`) is UNBOXED so the embstr SSO is untouched; the collections
127+ /// already heap-allocate their contents, so the `Box` is a negligible extra
128+ /// indirection only on collection ops.
129+ List ( Box < ListVal > ) ,
151130 /// A HASH value (PR-6). `OBJECT ENCODING` -> `listpack` while small, `hashtable`
152131 /// once over the entry-count OR per-element-byte threshold (a pure function of the
153- /// active repr, #40).
154- Hash ( HashVal ) ,
132+ /// active repr, #40). BOXED (memory Round 1); see [`ValueRepr::List`].
133+ Hash ( Box < HashVal > ) ,
155134 /// A SET value (PR-7). `OBJECT ENCODING` -> `intset` while all-integer and small,
156135 /// `listpack` once a non-integer member is added (and still small), `hashtable` once
157136 /// over the entry-count OR per-member-byte threshold (a pure function of the active
158- /// repr, #40). The conversion is ONE-WAY (never demotes).
159- Set ( SetVal ) ,
137+ /// repr, #40). The conversion is ONE-WAY (never demotes). BOXED (memory Round 1);
138+ /// see [`ValueRepr::List`].
139+ Set ( Box < SetVal > ) ,
160140 /// A ZSET (sorted set) value (PR-8). `OBJECT ENCODING` -> `listpack` while small,
161141 /// `skiplist` once over the entry-count OR per-member-byte threshold (a pure function
162- /// of the active repr, #40). The conversion is ONE-WAY (never demotes).
163- ZSet ( ZSetVal ) ,
142+ /// of the active repr, #40). The conversion is ONE-WAY (never demotes). BOXED
143+ /// (memory Round 1); see [`ValueRepr::List`].
144+ ZSet ( Box < ZSetVal > ) ,
164145}
165146
166147impl ValueRepr {
@@ -187,8 +168,9 @@ impl ValueRepr {
187168 pub fn logical_len ( & self ) -> usize {
188169 match self {
189170 ValueRepr :: Int ( n) => int_decimal_len ( * n) ,
190- ValueRepr :: Inline ( b) => b. as_bytes ( ) . len ( ) ,
191- ValueRepr :: Raw ( b) => b. len ( ) ,
171+ // Embstr and raw both hold the value bytes behind a `Box<[u8]>`; the
172+ // embstr-vs-raw distinction lives in `Header.encoding`, not the variant.
173+ ValueRepr :: Inline ( b) | ValueRepr :: Raw ( b) => b. len ( ) ,
192174 ValueRepr :: List ( l) => l. element_bytes ( ) ,
193175 ValueRepr :: Hash ( h) => h. element_bytes ( ) ,
194176 ValueRepr :: Set ( s) => s. element_bytes ( ) ,
@@ -1714,7 +1696,7 @@ impl KvObj {
17141696 ) -> Self {
17151697 let value = match classified {
17161698 Classified :: Int ( n) => ValueRepr :: Int ( n) ,
1717- Classified :: EmbStr => ValueRepr :: Inline ( InlineBuf :: from_bytes ( bytes) ) ,
1699+ Classified :: EmbStr => ValueRepr :: Inline ( bytes. to_vec ( ) . into_boxed_slice ( ) ) ,
17181700 Classified :: Raw => ValueRepr :: Raw ( bytes. to_vec ( ) . into_boxed_slice ( ) ) ,
17191701 } ;
17201702 let header = Header :: new ( value. encoding ( ) , expire_at. is_some ( ) ) ;
@@ -1800,7 +1782,7 @@ impl KvObj {
18001782 KvObj {
18011783 header : Header :: with_type ( DataType :: List , encoding, expire_at. is_some ( ) ) ,
18021784 key : key. to_vec ( ) . into_boxed_slice ( ) ,
1803- value : ValueRepr :: List ( list) ,
1785+ value : ValueRepr :: List ( Box :: new ( list) ) ,
18041786 expire_at,
18051787 }
18061788 }
@@ -1814,7 +1796,7 @@ impl KvObj {
18141796 KvObj {
18151797 header : Header :: with_type ( DataType :: Hash , encoding, expire_at. is_some ( ) ) ,
18161798 key : key. to_vec ( ) . into_boxed_slice ( ) ,
1817- value : ValueRepr :: Hash ( hash) ,
1799+ value : ValueRepr :: Hash ( Box :: new ( hash) ) ,
18181800 expire_at,
18191801 }
18201802 }
@@ -1828,7 +1810,7 @@ impl KvObj {
18281810 KvObj {
18291811 header : Header :: with_type ( DataType :: Set , encoding, expire_at. is_some ( ) ) ,
18301812 key : key. to_vec ( ) . into_boxed_slice ( ) ,
1831- value : ValueRepr :: Set ( set) ,
1813+ value : ValueRepr :: Set ( Box :: new ( set) ) ,
18321814 expire_at,
18331815 }
18341816 }
@@ -1842,7 +1824,7 @@ impl KvObj {
18421824 KvObj {
18431825 header : Header :: with_type ( DataType :: ZSet , encoding, expire_at. is_some ( ) ) ,
18441826 key : key. to_vec ( ) . into_boxed_slice ( ) ,
1845- value : ValueRepr :: ZSet ( zset) ,
1827+ value : ValueRepr :: ZSet ( Box :: new ( zset) ) ,
18461828 expire_at,
18471829 }
18481830 }
@@ -1861,7 +1843,9 @@ impl KvObj {
18611843 /// yields `None` -> WRONGTYPE).
18621844 pub fn as_list_mut ( & mut self ) -> Option < & mut ListVal > {
18631845 match & mut self . value {
1864- ValueRepr :: List ( l) => Some ( l) ,
1846+ // Deref through the `Box` (memory Round 1) to the `&mut ListVal` the
1847+ // collection trait + in-place RMW path expect.
1848+ ValueRepr :: List ( l) => Some ( & mut * * l) ,
18651849 _ => None ,
18661850 }
18671851 }
@@ -1871,7 +1855,7 @@ impl KvObj {
18711855 /// `None` -> WRONGTYPE). The HASH analog of [`Self::as_list_mut`].
18721856 pub fn as_hash_mut ( & mut self ) -> Option < & mut HashVal > {
18731857 match & mut self . value {
1874- ValueRepr :: Hash ( h) => Some ( h) ,
1858+ ValueRepr :: Hash ( h) => Some ( & mut * * h) ,
18751859 _ => None ,
18761860 }
18771861 }
@@ -1881,7 +1865,7 @@ impl KvObj {
18811865 /// -> WRONGTYPE). The SET analog of [`Self::as_list_mut`]/[`Self::as_hash_mut`].
18821866 pub fn as_set_mut ( & mut self ) -> Option < & mut SetVal > {
18831867 match & mut self . value {
1884- ValueRepr :: Set ( s) => Some ( s) ,
1868+ ValueRepr :: Set ( s) => Some ( & mut * * s) ,
18851869 _ => None ,
18861870 }
18871871 }
@@ -1892,7 +1876,7 @@ impl KvObj {
18921876 /// [`Self::as_set_mut`].
18931877 pub fn as_zset_mut ( & mut self ) -> Option < & mut ZSetVal > {
18941878 match & mut self . value {
1895- ValueRepr :: ZSet ( z) => Some ( z) ,
1879+ ValueRepr :: ZSet ( z) => Some ( & mut * * z) ,
18961880 _ => None ,
18971881 }
18981882 }
@@ -1972,7 +1956,7 @@ impl KvObj {
19721956 pub fn set_value_bytes ( & mut self , bytes : & [ u8 ] ) {
19731957 self . value = match classify ( bytes) {
19741958 Classified :: Int ( n) => ValueRepr :: Int ( n) ,
1975- Classified :: EmbStr => ValueRepr :: Inline ( InlineBuf :: from_bytes ( bytes) ) ,
1959+ Classified :: EmbStr => ValueRepr :: Inline ( bytes. to_vec ( ) . into_boxed_slice ( ) ) ,
19761960 Classified :: Raw => ValueRepr :: Raw ( bytes. to_vec ( ) . into_boxed_slice ( ) ) ,
19771961 } ;
19781962 self . header . encoding = self . value . encoding ( ) ;
0 commit comments