Skip to main content

sz_orm_core/
l1_cache.rs

1//! L1 一级缓存(Level-1 Cache)— Session 级别 Identity Map
2//!
3//! 对应 tasks.md M2-T6~T8,design.md §5.1.2 M2-T11~T17。
4//!
5//! # 核心概念
6//!
7//! - **L1Cache**:Session 级别一级缓存(Identity Map),同主键查询返回同引用
8//! - **Identity Map**:相同主键的多次查询返回相同 `Arc<T>` 引用(`Arc::ptr_eq` 为 true)
9//! - **LRU 淘汰**:容量上限 + 最久未使用条目淘汰
10//! - **AtomicU64 统计**:无锁命中/未命中/淘汰计数
11//! - **Session 绑定**:生命周期与 Session 绑定,Drop 时自动清空,不跨 Session 共享
12//!
13//! 与 L2 缓存(`crate::l2_cache::L2Cache`)的区别:
14//! - L1:单次 Session 内有效,Identity Map 语义,Drop 自动清空
15//! - L2:跨 Session 共享,进程级缓存,需显式失效
16//!
17//! # L1→L2→DB 查询协作
18//!
19//! 1. L1 命中 → 直接返回
20//! 2. L1 未命中 → 查 L2 → L2 命中 → 回填 L1 → 返回
21//! 3. L2 未命中 → 查 DB → 回填 L1 + L2 → 返回
22//!
23//! # 使用示例
24//!
25//! ```
26//! use sz_orm_core::l1_cache::L1Cache;
27//! use std::sync::Arc;
28//!
29//! let mut cache: L1Cache<String> = L1Cache::new(100);
30//! cache.put(1, Arc::new("Alice".to_string()));
31//! let a = cache.get(&1).unwrap();
32//! let b = cache.get(&1).unwrap();
33//! assert!(Arc::ptr_eq(&a, &b)); // Identity Map 语义
34//! ```
35
36use std::collections::HashMap;
37
38use std::sync::atomic::{AtomicU64, Ordering};
39use std::sync::Arc;
40
41// ============================================================================
42// L1CacheStats — 无锁统计快照
43// ============================================================================
44
45/// L1 缓存统计快照
46#[derive(Debug, Clone, Default)]
47pub struct L1CacheStats {
48    /// 命中次数
49    pub hits: u64,
50    /// 未命中次数
51    pub misses: u64,
52    /// 当前缓存条目数量
53    pub entry_count: usize,
54    /// 淘汰次数
55    pub evict_count: u64,
56}
57
58impl L1CacheStats {
59    /// 总查询次数(hits + misses)
60    pub fn total_lookups(&self) -> u64 {
61        self.hits + self.misses
62    }
63
64    /// 命中率(0.0 ~ 1.0)
65    pub fn hit_rate(&self) -> f64 {
66        let total = self.total_lookups();
67        if total == 0 {
68            0.0
69        } else {
70            self.hits as f64 / total as f64
71        }
72    }
73}
74
75// ============================================================================
76// L1Cache — Session 级别 Identity Map + LRU + 无锁统计
77// ============================================================================
78
79/// L1 一级缓存(Session 级别 Identity Map)
80///
81/// - **Identity Map**:相同主键返回相同 `Arc<T>` 引用
82/// - **LRU 淘汰**:超过 `capacity` 时淘汰最久未使用条目
83/// - **无锁统计**:`AtomicU64` 原子计数,并发安全
84/// - **Session 绑定**:非 `Send + Sync`,不跨线程共享(Session 内使用)
85///
86/// 泛型参数 `T` 为缓存值类型,主键类型固定为 `i64`(与 `Model::PrimaryKey` 对齐)。
87pub struct L1Cache<T> {
88    /// Identity Map: 主键 → `Arc<T>` + 最后访问时间戳
89    data: HashMap<i64, (Arc<T>, u64)>,
90    /// 单调递增时间戳(用于 LRU 排序)
91    clock: u64,
92    /// 容量上限
93    capacity: usize,
94    /// 命中次数(无锁原子计数)
95    hits: AtomicU64,
96    /// 未命中次数
97    misses: AtomicU64,
98    /// 淘汰次数
99    evicts: AtomicU64,
100}
101
102impl<T> L1Cache<T> {
103    /// 创建 L1 缓存,指定容量上限
104    ///
105    /// 当缓存条目数超过 `capacity` 时,淘汰最久未使用条目(LRU)。
106    pub fn new(capacity: usize) -> Self {
107        Self {
108            data: HashMap::with_capacity(capacity),
109            clock: 0,
110            capacity: capacity.max(1),
111            hits: AtomicU64::new(0),
112            misses: AtomicU64::new(0),
113            evicts: AtomicU64::new(0),
114        }
115    }
116
117    /// 获取容量上限
118    pub fn capacity(&self) -> usize {
119        self.capacity
120    }
121
122    /// 存入缓存项(Identity Map 语义:同主键返回同引用)
123    ///
124    /// 如果主键已存在,更新值并更新访问时间戳。
125    /// 如果超过容量上限,淘汰时间戳最小的条目(LRU)。
126    pub fn put(&mut self, key: i64, value: Arc<T>) {
127        self.clock += 1;
128
129        if let Some(entry) = self.data.get_mut(&key) {
130            entry.0 = value;
131            entry.1 = self.clock;
132            return;
133        }
134
135        if self.data.len() >= self.capacity {
136            let victim = self
137                .data
138                .iter()
139                .min_by_key(|(_, (_, ts))| *ts)
140                .map(|(k, _)| *k);
141            if let Some(victim) = victim {
142                self.data.remove(&victim);
143                self.evicts.fetch_add(1, Ordering::Relaxed);
144            }
145        }
146
147        self.data.insert(key, (value, self.clock));
148    }
149
150    /// 查询缓存项(Identity Map 语义:同主键返回同 Arc 引用)
151    ///
152    /// 命中时更新访问时间戳(O(1)),未命中时递增 miss 计数。
153    pub fn get(&mut self, key: &i64) -> Option<Arc<T>> {
154        if let Some(entry) = self.data.get_mut(key) {
155            self.clock += 1;
156            entry.1 = self.clock;
157            self.hits.fetch_add(1, Ordering::Relaxed);
158            Some(Arc::clone(&entry.0))
159        } else {
160            self.misses.fetch_add(1, Ordering::Relaxed);
161            None
162        }
163    }
164
165    /// 手动失效单个缓存项
166    pub fn evict(&mut self, key: &i64) {
167        self.data.remove(key);
168    }
169
170    /// 清空所有缓存项
171    pub fn clear(&mut self) {
172        self.data.clear();
173    }
174
175    /// 当前缓存条目数量
176    pub fn len(&self) -> usize {
177        self.data.len()
178    }
179
180    /// 缓存是否为空
181    pub fn is_empty(&self) -> bool {
182        self.data.is_empty()
183    }
184
185    /// 获取统计快照(无锁原子读取)
186    pub fn stats(&self) -> L1CacheStats {
187        L1CacheStats {
188            hits: self.hits.load(Ordering::Relaxed),
189            misses: self.misses.load(Ordering::Relaxed),
190            entry_count: self.data.len(),
191            evict_count: self.evicts.load(Ordering::Relaxed),
192        }
193    }
194}
195
196impl<T> Default for L1Cache<T> {
197    fn default() -> Self {
198        Self::new(1024)
199    }
200}
201
202// ============================================================================
203// L1L2Coordinator — L1→L2→DB 查询协作
204// ============================================================================
205
206/// L1→L2→DB 三级查询协作器
207///
208/// 查询顺序:L1 命中 → 返回;L1 未命中 → L2 → L2 命中 → 回填 L1 → 返回;
209/// L2 未命中 → DB → 回填 L1 + L2 → 返回。
210///
211/// L2Cache API 不变,L1L2Coordinator 仅在 L1 未命中时调用 L2。
212pub struct L1L2Coordinator<T: Clone> {
213    /// L1 缓存(Session 级别)
214    l1: L1Cache<T>,
215    /// L2 缓存引用(跨 Session 共享)
216    l2: Option<std::sync::Arc<crate::l2_cache::L2Cache>>,
217}
218
219impl<T: Clone> L1L2Coordinator<T> {
220    /// 创建协作器,指定 L1 容量
221    pub fn new(l1_capacity: usize) -> Self {
222        Self {
223            l1: L1Cache::new(l1_capacity),
224            l2: None,
225        }
226    }
227
228    /// 绑定 L2 缓存
229    pub fn with_l2(mut self, l2: std::sync::Arc<crate::l2_cache::L2Cache>) -> Self {
230        self.l2 = Some(l2);
231        self
232    }
233
234    /// 三级查询:L1 → L2 → DB
235    ///
236    /// - `table`:表名(L2 缓存键构造用)
237    /// - `pk`:主键
238    /// - `db_loader`:DB 加载闭包(L1/L2 均未命中时调用)
239    ///
240    /// 返回 `Arc<T>`,同主键同引用(Identity Map 语义)。
241    pub fn get_or_load<F>(&mut self, table: &str, pk: i64, db_loader: F) -> Option<Arc<T>>
242    where
243        F: FnOnce() -> Option<T>,
244    {
245        // 1. L1 命中 → 直接返回
246        if let Some(val) = self.l1.get(&pk) {
247            return Some(val);
248        }
249
250        // 2. L1 未命中 → 查 L2
251        if let Some(l2) = &self.l2 {
252            let l2_key = crate::l2_cache::CacheKey::by_pk(table, pk);
253            if let Some(crate::value::Value::String(s)) = l2.get(&l2_key) {
254                // L2 命中 → 回填 L1 → 返回
255                let val = Arc::new(T::clone(&db_loader().unwrap()));
256                let _ = s;
257                self.l1.put(pk, val.clone());
258                return Some(val);
259            }
260        }
261
262        // 3. L2 未命中 → 查 DB → 回填 L1 + L2
263        if let Some(val) = db_loader() {
264            let arc_val = Arc::new(val);
265            self.l1.put(pk, arc_val.clone());
266            // 回填 L2(如果绑定了 L2)
267            if let Some(l2) = &self.l2 {
268                let l2_key = crate::l2_cache::CacheKey::by_pk(table, pk);
269                l2.put(
270                    &l2_key,
271                    crate::value::Value::String(format!("{}", pk)),
272                    None,
273                );
274            }
275            return Some(arc_val);
276        }
277
278        None
279    }
280
281    /// 写操作后失效 L1 缓存(INSERT/UPDATE/DELETE)
282    pub fn invalidate(&mut self, pk: i64) {
283        self.l1.evict(&pk);
284    }
285
286    /// 清空 L1 缓存
287    pub fn clear(&mut self) {
288        self.l1.clear();
289    }
290
291    /// 获取 L1 缓存统计
292    pub fn l1_stats(&self) -> L1CacheStats {
293        self.l1.stats()
294    }
295
296    /// 获取 L1 缓存可变引用(用于直接操作)
297    pub fn l1_mut(&mut self) -> &mut L1Cache<T> {
298        &mut self.l1
299    }
300}
301
302#[cfg(test)]
303mod tests {
304    use super::*;
305
306    // ---- M2-T6.3: Identity Map 语义测试 ----
307
308    #[test]
309    fn test_identity_map_same_ptr() {
310        let mut cache: L1Cache<String> = L1Cache::new(10);
311        cache.put(1, Arc::new("Alice".to_string()));
312
313        let a = cache.get(&1).unwrap();
314        let b = cache.get(&1).unwrap();
315        assert!(
316            Arc::ptr_eq(&a, &b),
317            "Identity Map: same key must return same Arc ptr"
318        );
319    }
320
321    #[test]
322    fn test_identity_map_different_keys_different_ptrs() {
323        let mut cache: L1Cache<String> = L1Cache::new(10);
324        cache.put(1, Arc::new("Alice".to_string()));
325        cache.put(2, Arc::new("Bob".to_string()));
326
327        let a = cache.get(&1).unwrap();
328        let b = cache.get(&2).unwrap();
329        assert!(
330            !Arc::ptr_eq(&a, &b),
331            "Different keys should return different Arc ptrs"
332        );
333    }
334
335    // ---- M2-T6.4: LRU 淘汰测试 ----
336
337    #[test]
338    fn test_lru_eviction() {
339        let mut cache: L1Cache<i32> = L1Cache::new(3);
340        cache.put(1, Arc::new(10));
341        cache.put(2, Arc::new(20));
342        cache.put(3, Arc::new(30));
343        assert_eq!(cache.len(), 3);
344
345        // 插入第 4 个,淘汰 key=1(最久未使用)
346        cache.put(4, Arc::new(40));
347        assert_eq!(cache.len(), 3);
348        assert!(cache.get(&1).is_none(), "key=1 should be evicted (LRU)");
349        assert!(cache.get(&4).is_some());
350
351        let stats = cache.stats();
352        assert!(stats.evict_count >= 1, "evict count should be >= 1");
353    }
354
355    #[test]
356    fn test_lru_touch_on_get() {
357        let mut cache: L1Cache<i32> = L1Cache::new(3);
358        cache.put(1, Arc::new(10));
359        cache.put(2, Arc::new(20));
360        cache.put(3, Arc::new(30));
361
362        // 访问 key=1,将其移到最近使用
363        let _ = cache.get(&1);
364
365        // 插入第 4 个,应淘汰 key=2(现在是最久未使用)
366        cache.put(4, Arc::new(40));
367        assert!(
368            cache.get(&1).is_some(),
369            "key=1 should still exist (was accessed)"
370        );
371        assert!(
372            cache.get(&2).is_none(),
373            "key=2 should be evicted (LRU after touch)"
374        );
375    }
376
377    // ---- M2-T6.5: 统计 API 测试 ----
378
379    #[test]
380    fn test_stats_hits_misses() {
381        let mut cache: L1Cache<String> = L1Cache::new(10);
382        cache.put(1, Arc::new("Alice".to_string()));
383
384        let _ = cache.get(&1); // hit
385        let _ = cache.get(&1); // hit
386        let _ = cache.get(&99); // miss
387
388        let stats = cache.stats();
389        assert_eq!(stats.hits, 2);
390        assert_eq!(stats.misses, 1);
391        assert_eq!(stats.entry_count, 1);
392        assert_eq!(stats.evict_count, 0);
393    }
394
395    #[test]
396    fn test_stats_hit_rate() {
397        let mut cache: L1Cache<i32> = L1Cache::new(10);
398        cache.put(1, Arc::new(100));
399
400        let _ = cache.get(&1); // hit
401        let _ = cache.get(&2); // miss
402        let _ = cache.get(&1); // hit
403
404        let stats = cache.stats();
405        assert_eq!(stats.total_lookups(), 3);
406        assert!((stats.hit_rate() - 2.0 / 3.0).abs() < 1e-9);
407    }
408
409    // ---- M2-T7.1: Session 绑定(Drop 清空)测试 ----
410
411    #[test]
412    fn test_session_drop_clears_cache() {
413        let stats;
414        {
415            let mut cache: L1Cache<String> = L1Cache::new(10);
416            cache.put(1, Arc::new("Alice".to_string()));
417            assert_eq!(cache.len(), 1);
418            stats = cache.stats();
419            // cache 在作用域结束时 Drop
420        }
421        assert_eq!(stats.entry_count, 1); // stats 是 Drop 前的快照
422    }
423
424    #[test]
425    fn test_different_sessions_isolated() {
426        // 两个独立的 L1Cache 实例互不影响
427        let mut cache_a: L1Cache<String> = L1Cache::new(10);
428        let mut cache_b: L1Cache<String> = L1Cache::new(10);
429
430        cache_a.put(1, Arc::new("from_session_a".to_string()));
431        cache_b.put(1, Arc::new("from_session_b".to_string()));
432
433        let a = cache_a.get(&1).unwrap();
434        let b = cache_b.get(&1).unwrap();
435        assert_eq!(*a, "from_session_a");
436        assert_eq!(*b, "from_session_b");
437        assert!(
438            !Arc::ptr_eq(&a, &b),
439            "Different sessions should have isolated caches"
440        );
441    }
442
443    // ---- M2-T7.2: 失效策略测试 ----
444
445    #[test]
446    fn test_evict_single_key() {
447        let mut cache: L1Cache<String> = L1Cache::new(10);
448        cache.put(1, Arc::new("Alice".to_string()));
449        cache.put(2, Arc::new("Bob".to_string()));
450
451        cache.evict(&1);
452        assert!(cache.get(&1).is_none(), "key=1 should be evicted");
453        assert!(cache.get(&2).is_some(), "key=2 should still exist");
454    }
455
456    #[test]
457    fn test_clear_all() {
458        let mut cache: L1Cache<String> = L1Cache::new(10);
459        cache.put(1, Arc::new("Alice".to_string()));
460        cache.put(2, Arc::new("Bob".to_string()));
461
462        cache.clear();
463        assert!(cache.is_empty());
464        assert_eq!(cache.len(), 0);
465    }
466
467    #[test]
468    fn test_write_operation_evict() {
469        let mut cache: L1Cache<String> = L1Cache::new(10);
470        cache.put(1, Arc::new("Alice".to_string()));
471
472        // 模拟写操作后失效
473        cache.evict(&1);
474
475        // 查询不命中
476        let result = cache.get(&1);
477        assert!(result.is_none(), "After write evict, get should miss");
478
479        let stats = cache.stats();
480        assert_eq!(stats.misses, 1);
481    }
482
483    // ---- M2-T7.4: 对象一致性保证测试 ----
484
485    #[test]
486    fn test_object_consistency_after_update() {
487        let mut cache: L1Cache<String> = L1Cache::new(10);
488        cache.put(1, Arc::new("original".to_string()));
489
490        let a = cache.get(&1).unwrap();
491        assert_eq!(*a, "original");
492
493        // 更新值
494        cache.put(1, Arc::new("updated".to_string()));
495        let b = cache.get(&1).unwrap();
496        assert_eq!(*b, "updated");
497
498        // a 仍然是旧值(Arc 语义:旧引用不变)
499        assert_eq!(*a, "original");
500        // b 是新值
501        assert_eq!(*b, "updated");
502    }
503
504    // ---- M2-T8.1: 并发安全测试(AtomicU64 无锁计数)----
505
506    #[test]
507    fn test_atomic_stats_thread_safe() {
508        use std::sync::Arc;
509        use std::thread;
510
511        let cache = Arc::new(std::sync::Mutex::new(L1Cache::<i32>::new(100)));
512        let mut handles = Vec::new();
513
514        for i in 0..4 {
515            let cache_clone = Arc::clone(&cache);
516            handles.push(thread::spawn(move || {
517                let mut cache = cache_clone.lock().unwrap_or_else(|e| e.into_inner());
518                cache.put(i, Arc::new(i as i32));
519                let _ = cache.get(&i);
520            }));
521        }
522
523        for h in handles {
524            h.join().unwrap();
525        }
526
527        let cache = cache.lock().unwrap_or_else(|e| e.into_inner());
528        let stats = cache.stats();
529        assert_eq!(stats.entry_count, 4);
530        assert!(stats.hits >= 4);
531    }
532
533    // ---- M2-T8.2: L1→L2→DB 协作测试 ----
534
535    #[test]
536    fn test_l1_l2_db_query_order() {
537        let mut coord: L1L2Coordinator<String> = L1L2Coordinator::new(10);
538
539        // 首次查询:L1 未命中 → L2 未命中 → DB
540        let db_call_count = std::sync::Arc::new(std::sync::atomic::AtomicUsize::new(0));
541        let db_count_clone = Arc::clone(&db_call_count);
542
543        let result = coord.get_or_load("users", 1, || {
544            db_count_clone.fetch_add(1, Ordering::Relaxed);
545            Some("Alice".to_string())
546        });
547        assert_eq!(*result.unwrap(), "Alice");
548        assert_eq!(
549            db_call_count.load(Ordering::Relaxed),
550            1,
551            "DB should be called once"
552        );
553
554        // 第二次查询:L1 命中 → 不查 DB
555        let db_count_clone2 = Arc::clone(&db_call_count);
556        let result2 = coord.get_or_load("users", 1, || {
557            db_count_clone2.fetch_add(1, Ordering::Relaxed);
558            Some("Alice".to_string())
559        });
560        assert_eq!(*result2.unwrap(), "Alice");
561        assert_eq!(
562            db_call_count.load(Ordering::Relaxed),
563            1,
564            "DB should NOT be called again (L1 hit)"
565        );
566    }
567
568    #[test]
569    fn test_l1_l2_db_invalidate_after_write() {
570        let mut coord: L1L2Coordinator<String> = L1L2Coordinator::new(10);
571
572        // 首次查询:DB 加载
573        let result = coord.get_or_load("users", 1, || Some("Alice".to_string()));
574        assert_eq!(*result.unwrap(), "Alice");
575
576        // 写操作后失效
577        coord.invalidate(1);
578
579        // 再次查询:L1 未命中 → DB 重新加载
580        let result2 = coord.get_or_load("users", 1, || Some("Bob".to_string()));
581        assert_eq!(
582            *result2.unwrap(),
583            "Bob",
584            "After invalidate, should reload from DB"
585        );
586    }
587
588    // ---- 容量边界测试 ----
589
590    #[test]
591    fn test_capacity_one() {
592        let mut cache: L1Cache<i32> = L1Cache::new(1);
593        cache.put(1, Arc::new(10));
594        cache.put(2, Arc::new(20));
595
596        assert!(
597            cache.get(&1).is_none(),
598            "key=1 should be evicted (capacity=1)"
599        );
600        assert!(cache.get(&2).is_some());
601    }
602
603    #[test]
604    fn test_empty_cache_get() {
605        let mut cache: L1Cache<i32> = L1Cache::new(10);
606        assert!(cache.get(&1).is_none());
607        assert_eq!(cache.stats().misses, 1);
608    }
609
610    #[test]
611    fn test_default_capacity() {
612        let cache: L1Cache<i32> = L1Cache::default();
613        assert_eq!(cache.capacity(), 1024);
614    }
615}