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