1use subms_block_cache::BlockCache;
18
19fn main() {
20 base_page_cache();
21
22 #[cfg(feature = "arc")]
23 arc_scan_resistance();
24
25 #[cfg(feature = "tinylfu")]
26 tinylfu_admission();
27
28 #[cfg(feature = "weighted")]
29 weighted_byte_budget();
30
31 #[cfg(feature = "concurrent-shards")]
32 sharded_parallel_readers();
33
34 #[cfg(feature = "metrics")]
35 metrics_hit_ratio();
36}
37
38fn read_block(id: u64) -> String {
40 format!("page:{id}")
41}
42
43fn base_page_cache() {
47 println!("== base: block cache in front of a cold columnar store ==");
48 const CAP: usize = 4;
49 let mut cache: BlockCache<u64, String> = BlockCache::with_capacity(CAP);
50
51 let hot = [100u64, 101, 102, 103];
52 let mut cold_reads = 0;
53 for &id in &hot {
54 assert!(cache.get(&id).is_none(), "cold on first touch");
55 cache.put(id, read_block(id));
56 cold_reads += 1;
57 }
58 println!(" warmed {} pages, {cold_reads} cold reads", cache.len());
59 assert_eq!(cache.len(), CAP);
60
61 let mut served = 0;
62 for &id in &hot {
63 assert!(cache.get(&id).is_some(), "resident page must never miss");
64 served += 1;
65 }
66 println!(" re-read the hot set: {served} served, 0 cold reads");
67 assert_eq!(served, CAP);
68
69 let (victim, _) = cache
70 .put(200, read_block(200))
71 .expect("full cache must evict to admit a new page");
72 println!(" admitted page 200, evicted page {victim}");
73 assert_eq!(cache.len(), CAP, "capacity is a hard bound");
74
75 assert!(cache.remove(&200).is_some(), "stale page must be dropped");
78 println!(" invalidated page 200, {} pages resident", cache.len());
79 cache.clear();
80 assert!(cache.is_empty(), "clear drops the whole segment");
81 println!(" segment dropped, cache empty, capacity still {CAP}");
82}
83
84#[cfg(feature = "arc")]
88fn arc_scan_resistance() {
89 use subms_block_cache::ArcCache;
90 println!("\n== arc: scan-resistant page cache ==");
91 let mut cache: ArcCache<u64, String> = ArcCache::with_capacity(8);
92 for id in 0u64..4 {
93 cache.put(id, read_block(id));
94 let _ = cache.get(&id); }
96 println!(" frequent set in T2: {} pages", cache.t2_len());
97
98 for id in 1000u64..1200 {
99 cache.put(id, read_block(id));
100 }
101 let survivors = (0u64..4).filter(|id| cache.get(id).is_some()).count();
102 println!(" hot pages surviving a 200-page scan: {survivors}/4");
103 assert_eq!(
104 survivors, 4,
105 "ARC must hold the frequent set through a scan"
106 );
107}
108
109#[cfg(feature = "tinylfu")]
113fn tinylfu_admission() {
114 use subms_block_cache::TinyLfuCache;
115 println!("\n== tinylfu: frequency-gated admission ==");
116 let mut cache: TinyLfuCache<u64, String> = TinyLfuCache::with_capacity(64);
117 for _ in 0..50 {
118 for id in 0u64..16 {
119 let _ = cache.get(&id);
120 }
121 }
122 for id in 0u64..16 {
123 cache.put(id, read_block(id));
124 }
125 for _ in 0..50 {
126 for id in 0u64..16 {
127 let _ = cache.get(&id);
128 }
129 }
130
131 let rej_before = cache.rejections();
132 for id in 1000u64..3000 {
133 cache.put(id, read_block(id));
134 }
135 let rejected = cache.rejections() - rej_before;
136 println!(
137 " admissions {}, scan pages rejected {rejected}",
138 cache.admissions()
139 );
140 assert!(
141 rejected > 0,
142 "admission filter must reject some one-shot scan pages"
143 );
144}
145
146#[cfg(feature = "weighted")]
150fn weighted_byte_budget() {
151 use subms_block_cache::WeightedCache;
152 println!("\n== weighted: byte-budgeted page cache ==");
153 let mut cache: WeightedCache<u64, Vec<u8>> =
154 WeightedCache::with_capacity_bytes(4096, |page: &Vec<u8>| page.len());
155
156 let mut evicted_total = 0;
157 for id in 0u64..64 {
158 let size = 128 + (id as usize % 8) * 128;
159 evicted_total += cache.put(id, vec![0u8; size]).len();
160 }
161 println!(
162 " used {} / 4096 bytes, {} pages, {evicted_total} evicted",
163 cache.used_bytes(),
164 cache.len()
165 );
166 assert!(cache.used_bytes() <= 4096, "byte budget is a hard bound");
167
168 let too_big = cache.put(999, vec![0u8; 8192]);
169 assert_eq!(too_big.len(), 1, "oversized page is rejected, not admitted");
170 assert!(cache.get(&999).is_none());
171}
172
173#[cfg(feature = "concurrent-shards")]
177fn sharded_parallel_readers() {
178 use std::sync::Arc;
179 use std::thread;
180 use subms_block_cache::ShardedCache;
181 println!("\n== concurrent-shards: parallel query threads ==");
182 let cache: Arc<ShardedCache<u64, u64>> = Arc::new(ShardedCache::with_capacity(1024, 8));
183 println!(" {} shards", cache.num_shards());
184 for id in 0u64..256 {
185 cache.put(id, id);
186 }
187
188 let mut handles = Vec::new();
189 for _ in 0..4 {
190 let c = Arc::clone(&cache);
191 handles.push(thread::spawn(move || {
192 for id in 0u64..4000 {
193 let _ = c.get(&(id % 256));
194 }
195 }));
196 }
197 for t in 0u64..2 {
198 let c = Arc::clone(&cache);
199 handles.push(thread::spawn(move || {
200 for i in 0u64..2000 {
201 c.put(t * 10_000 + i, i);
202 }
203 }));
204 }
205 for h in handles {
206 h.join().unwrap();
207 }
208 println!(" survived concurrent load, {} pages resident", cache.len());
209 assert!(cache.len() <= 1024, "capacity holds across all shards");
210}
211
212#[cfg(feature = "metrics")]
216fn metrics_hit_ratio() {
217 use subms_block_cache::MetricsCache;
218 println!("\n== metrics: hit-ratio observability ==");
219 let mut cache: MetricsCache<u64, String> = MetricsCache::with_capacity(4);
220 for id in 0u64..4 {
221 cache.put(id, read_block(id));
222 }
223 let _ = cache.get(&0);
224 let _ = cache.get(&1);
225 let _ = cache.get(&2);
226 let _ = cache.get(&99);
227 let m = cache.metrics();
228 println!(
229 " hits {}, misses {}, hit ratio {:.2}",
230 m.hits(),
231 m.misses(),
232 m.hit_ratio()
233 );
234 assert_eq!(m.hits(), 3);
235 assert_eq!(m.misses(), 1);
236 assert!((m.hit_ratio() - 0.75).abs() < 1e-9);
237}