Skip to main content

sz_orm_core/
l2_cache.rs

1//! L2 二级缓存(Level-2 Cache)
2//!
3//! 对应文档 6.8 节改进项 21(L2 二级缓存)。
4//!
5//! # 核心概念
6//!
7//! - **L2Cache**:跨 Session 共享的二级缓存(与 Hibernate L2 Cache / MyBatis 二级缓存对应)
8//! - **CacheKey**:统一缓存键构造(table + pk 或 table + query_hash)
9//! - **L2CacheStats**:命中率统计(hits/misses/evictions/sets)
10//! - **表级失效**:`invalidate_table(table)` 一次失效某表的所有缓存项
11//!
12//! 与 L1 缓存(Session 级别)的区别:
13//! - L1:单次 Session/请求 内有效,事务结束自动清空
14//! - L2:跨 Session 共享,进程级缓存,需显式失效
15//!
16//! # 设计灵感
17//!
18//! - Hibernate L2 Cache(`@Cache` / `@Cacheable`)
19//! - MyBatis 二级缓存(`<cache>` 标签)
20//! - Rails `Rails.cache`
21//! - Django cache framework
22//!
23//! # 使用示例
24//!
25//! ```no_run
26//! use sz_orm_core::l2_cache::{L2Cache, CacheKey};
27//! use sz_orm_core::Value;
28//!
29//! // 1. 创建 L2 缓存
30//! let cache = L2Cache::new();
31//!
32//! // 2. 缓存单行(pk 维度)
33//! let key = CacheKey::by_pk("users", 1);
34//! cache.put(&key, Value::String("Alice".to_string()), None);
35//!
36//! // 3. 读取
37//! let val = cache.get(&key);
38//! assert!(val.is_some());
39//!
40//! // 4. 表级失效(用户表更新后)
41//! cache.invalidate_table("users");
42//! assert!(cache.get(&key).is_none());
43//!
44//! // 5. 命中率统计
45//! let stats = cache.stats();
46//! println!("hit rate: {:.2}%", stats.hit_rate() * 100.0);
47//! ```
48
49use crate::value::Value;
50use std::collections::HashMap;
51use std::sync::RwLock;
52use std::time::{Duration, Instant};
53
54// ============================================================================
55// CacheKey — 统一缓存键
56// ============================================================================
57
58/// 统一缓存键
59///
60/// 通过 `table` + `kind` + `identifier` 三元组唯一标识一个缓存项:
61/// - `table`:表名(用于表级失效)
62/// - `kind`:缓存类型(ByPk / ByQuery / ByRelation)
63/// - `identifier`:具体标识(pk 值 / 查询哈希 / 关联键)
64#[derive(Debug, Clone, PartialEq, Eq, Hash)]
65pub struct CacheKey {
66    /// 表名
67    pub table: String,
68    /// 缓存类型
69    pub kind: CacheKeyKind,
70    /// 具体标识
71    pub identifier: String,
72}
73
74/// 缓存键类型
75#[derive(Debug, Clone, PartialEq, Eq, Hash)]
76pub enum CacheKeyKind {
77    /// 按主键缓存
78    ByPk,
79    /// 按查询条件缓存
80    ByQuery,
81    /// 按关联关系缓存
82    ByRelation,
83}
84
85impl CacheKey {
86    /// 构造主键维度的缓存键
87    pub fn by_pk(table: impl Into<String>, pk: impl std::fmt::Display) -> Self {
88        Self {
89            table: table.into(),
90            kind: CacheKeyKind::ByPk,
91            identifier: pk.to_string(),
92        }
93    }
94
95    /// 构造查询维度的缓存键(identifier 通常是 SQL + params 的哈希)
96    pub fn by_query(table: impl Into<String>, query_hash: impl std::fmt::Display) -> Self {
97        Self {
98            table: table.into(),
99            kind: CacheKeyKind::ByQuery,
100            identifier: query_hash.to_string(),
101        }
102    }
103
104    /// 构造关联维度的缓存键
105    pub fn by_relation(table: impl Into<String>, relation: impl std::fmt::Display) -> Self {
106        Self {
107            table: table.into(),
108            kind: CacheKeyKind::ByRelation,
109            identifier: relation.to_string(),
110        }
111    }
112
113    /// 序列化为字符串(用于底层存储键)
114    pub fn to_string_key(&self) -> String {
115        let kind_str = match self.kind {
116            CacheKeyKind::ByPk => "pk",
117            CacheKeyKind::ByQuery => "q",
118            CacheKeyKind::ByRelation => "rel",
119        };
120        format!("l2:{}:{}:{}", self.table, kind_str, self.identifier)
121    }
122}
123
124impl std::fmt::Display for CacheKey {
125    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
126        write!(f, "{}", self.to_string_key())
127    }
128}
129
130// ============================================================================
131// L2CacheStats — 命中率统计
132// ============================================================================
133
134/// L2 缓存命中率统计
135#[derive(Debug, Clone, Default)]
136pub struct L2CacheStats {
137    /// 命中次数
138    pub hits: u64,
139    /// 未命中次数
140    pub misses: u64,
141    /// 设置次数
142    pub sets: u64,
143    /// 失效次数(含单键和表级失效)
144    pub evictions: u64,
145    /// 当前缓存项数量
146    pub size: usize,
147}
148
149impl L2CacheStats {
150    /// 总查询次数(hits + misses)
151    pub fn total_lookups(&self) -> u64 {
152        self.hits + self.misses
153    }
154
155    /// 命中率(0.0 ~ 1.0)
156    pub fn hit_rate(&self) -> f64 {
157        let total = self.total_lookups();
158        if total == 0 {
159            0.0
160        } else {
161            self.hits as f64 / total as f64
162        }
163    }
164
165    /// 未命中率(0.0 ~ 1.0)
166    pub fn miss_rate(&self) -> f64 {
167        1.0 - self.hit_rate()
168    }
169
170    /// 合并两个统计(用于多分片汇总)
171    pub fn merge(&mut self, other: &L2CacheStats) {
172        self.hits += other.hits;
173        self.misses += other.misses;
174        self.sets += other.sets;
175        self.evictions += other.evictions;
176        self.size += other.size;
177    }
178}
179
180// ============================================================================
181// CacheEntry — 缓存项
182// ============================================================================
183
184/// 缓存项(值 + 过期时间)
185#[derive(Debug, Clone)]
186struct CacheEntry {
187    /// 缓存值
188    value: Value,
189    /// 过期时间(None 表示永不过期)
190    expires_at: Option<Instant>,
191}
192
193impl CacheEntry {
194    fn new(value: Value, ttl: Option<Duration>) -> Self {
195        // Duration::MAX 会导致 Instant::now() + Duration::MAX 溢出
196        // 将其视为永不过期(expires_at = None),与 None 语义一致
197        let expires_at = ttl.and_then(|d| {
198            if d == Duration::MAX {
199                None
200            } else {
201                Some(Instant::now() + d)
202            }
203        });
204        Self { value, expires_at }
205    }
206
207    fn is_expired(&self) -> bool {
208        self.expires_at
209            .map(|t| t <= Instant::now())
210            .unwrap_or(false)
211    }
212}
213
214// ============================================================================
215// L2Cache — 跨 Session 共享的二级缓存
216// ============================================================================
217
218/// L2 二级缓存 — 跨 Session 共享
219///
220/// 线程安全:内部使用 RwLock,可在多线程环境下共享。
221///
222/// # 示例
223///
224/// ```
225/// use sz_orm_core::l2_cache::{L2Cache, CacheKey};
226/// use sz_orm_core::Value;
227/// use std::time::Duration;
228///
229/// let cache = L2Cache::new();
230///
231/// // 缓存单行
232/// let key = CacheKey::by_pk("users", 1);
233/// cache.put(&key, Value::String("Alice".to_string()), None);
234///
235/// // 读取
236/// assert!(cache.get(&key).is_some());
237///
238/// // 表级失效
239/// cache.invalidate_table("users");
240/// assert!(cache.get(&key).is_none());
241/// ```
242pub struct L2Cache {
243    /// 缓存数据
244    data: RwLock<HashMap<String, CacheEntry>>,
245    /// 表名索引(用于表级失效)— table -> Vec<key_string>(去重)
246    table_index: RwLock<HashMap<String, Vec<String>>>,
247    /// LRU 访问顺序(尾部为最近访问,头部为最久未访问)
248    ///
249    /// # 锁顺序约定
250    ///
251    /// 跨字段持锁时遵循:`data` → `access_order` → `table_index` → `stats`,
252    /// 避免死锁。本字段不允许在持 `data` 写锁时获取其他写锁。
253    access_order: RwLock<Vec<String>>,
254    /// 统计信息
255    stats: RwLock<L2CacheStats>,
256    /// 默认 TTL(`put` 传 `None` 时使用,要"永不失效"请传 `Some(Duration::MAX)`)
257    default_ttl: Option<Duration>,
258    /// 最大容量(LRU 淘汰)
259    max_size: usize,
260}
261
262impl Default for L2Cache {
263    fn default() -> Self {
264        Self::new()
265    }
266}
267
268impl L2Cache {
269    /// 创建 L2 缓存(默认容量 10000,无 TTL)
270    pub fn new() -> Self {
271        Self {
272            data: RwLock::new(HashMap::new()),
273            table_index: RwLock::new(HashMap::new()),
274            access_order: RwLock::new(Vec::new()),
275            stats: RwLock::new(L2CacheStats::default()),
276            default_ttl: None,
277            max_size: 10_000,
278        }
279    }
280
281    /// 设置默认 TTL
282    pub fn with_default_ttl(mut self, ttl: Duration) -> Self {
283        self.default_ttl = Some(ttl);
284        self
285    }
286
287    /// 设置最大容量
288    pub fn with_max_size(mut self, max_size: usize) -> Self {
289        self.max_size = max_size;
290        self
291    }
292
293    /// 存入缓存项
294    ///
295    /// # TTL 语义
296    ///
297    /// - `ttl = Some(d)`:使用 `d` 作为过期时间
298    /// - `ttl = None`:使用 `default_ttl`(若未设置则永不过期)
299    /// - 要显式表示"永不失效",请传 `Some(Duration::MAX)`
300    pub fn put(&self, key: &CacheKey, value: Value, ttl: Option<Duration>) {
301        let actual_ttl = ttl.or(self.default_ttl);
302        let entry = CacheEntry::new(value, actual_ttl);
303        let key_str = key.to_string_key();
304
305        // 1. 写入数据 + LRU 淘汰
306        let is_new_key = {
307            let mut data = self.data.write().unwrap();
308            let exists = data.contains_key(&key_str);
309            if !exists && data.len() >= self.max_size {
310                // LRU 淘汰:优先淘汰已过期的 key,否则淘汰 access_order 头部
311                let victim = {
312                    // 不在持 data 写锁时获取 access_order 写锁,先读 access_order
313                    let order = self.access_order.read().unwrap();
314                    // 优先找已过期的 key
315                    order
316                        .iter()
317                        .find(|k| data.get(*k).map(|e| e.is_expired()).unwrap_or(false))
318                        .cloned()
319                        .or_else(|| order.first().cloned())
320                };
321                if let Some(victim) = victim {
322                    data.remove(&victim);
323                    // 同步清理 access_order
324                    let mut order = self.access_order.write().unwrap();
325                    order.retain(|k| k != &victim);
326                }
327            }
328            data.insert(key_str.clone(), entry);
329            !exists
330        };
331
332        // 2. 更新 LRU 访问顺序(key 移到尾部)
333        {
334            let mut order = self.access_order.write().unwrap();
335            if is_new_key {
336                order.push(key_str.clone());
337            } else {
338                // 已存在的 key:移到尾部
339                order.retain(|k| k != &key_str);
340                order.push(key_str.clone());
341            }
342        }
343
344        // 3. 更新表索引(去重,避免重复 push 导致 invalidate_table 统计错误)
345        {
346            let mut idx = self.table_index.write().unwrap();
347            let keys = idx.entry(key.table.clone()).or_default();
348            if !keys.contains(&key_str) {
349                keys.push(key_str);
350            }
351        }
352
353        // 4. 更新统计(不在此处读取 data.len(),避免锁顺序敏感)
354        {
355            let mut stats = self.stats.write().unwrap();
356            stats.sets += 1;
357        }
358    }
359
360    /// 读取缓存项(不存在或已过期返回 None)
361    ///
362    /// 命中时会更新 LRU 访问顺序(移到尾部)。
363    pub fn get(&self, key: &CacheKey) -> Option<Value> {
364        let key_str = key.to_string_key();
365        let result = {
366            let data = self.data.read().ok()?;
367            if let Some(entry) = data.get(&key_str) {
368                if entry.is_expired() {
369                    None
370                } else {
371                    Some(entry.value.clone())
372                }
373            } else {
374                None
375            }
376        };
377
378        // 命中时更新 LRU 顺序(移到尾部)
379        if result.is_some() {
380            let mut order = self.access_order.write().unwrap();
381            order.retain(|k| k != &key_str);
382            order.push(key_str);
383        }
384
385        // 更新统计
386        if let Ok(mut stats) = self.stats.write() {
387            if result.is_some() {
388                stats.hits += 1;
389            } else {
390                stats.misses += 1;
391            }
392        }
393
394        result
395    }
396
397    /// 失效单个缓存项
398    pub fn invalidate(&self, key: &CacheKey) {
399        let key_str = key.to_string_key();
400        let removed = {
401            let mut data = self.data.write().unwrap();
402            data.remove(&key_str).is_some()
403        };
404        if removed {
405            let mut order = self.access_order.write().unwrap();
406            order.retain(|k| k != &key_str);
407        }
408        if removed {
409            let mut stats = self.stats.write().unwrap();
410            stats.evictions += 1;
411        }
412    }
413
414    /// 失效整张表的所有缓存项
415    ///
416    /// 仅统计实际从缓存中删除的 key 数量,避免 evictions 偏大。
417    pub fn invalidate_table(&self, table: &str) {
418        let keys_to_remove: Vec<String> = {
419            let idx = match self.table_index.read() {
420                Ok(i) => i,
421                Err(_) => return,
422            };
423            idx.get(table).cloned().unwrap_or_default()
424        };
425
426        let mut actually_removed: usize = 0;
427        {
428            let mut data = self.data.write().unwrap();
429            for k in &keys_to_remove {
430                if data.remove(k).is_some() {
431                    actually_removed += 1;
432                }
433            }
434        }
435
436        if actually_removed > 0 {
437            let mut order = self.access_order.write().unwrap();
438            order.retain(|k| !keys_to_remove.contains(k));
439        }
440
441        if let Ok(mut idx) = self.table_index.write() {
442            idx.remove(table);
443        }
444        if actually_removed > 0 {
445            let mut stats = self.stats.write().unwrap();
446            stats.evictions += actually_removed as u64;
447        }
448    }
449
450    /// 清空所有缓存
451    pub fn clear(&self) {
452        let removed = {
453            let mut data = self.data.write().unwrap();
454            let n = data.len();
455            data.clear();
456            n
457        };
458        if let Ok(mut order) = self.access_order.write() {
459            order.clear();
460        }
461        if let Ok(mut idx) = self.table_index.write() {
462            idx.clear();
463        }
464        if removed > 0 {
465            let mut stats = self.stats.write().unwrap();
466            stats.evictions += removed as u64;
467            stats.size = 0;
468        }
469    }
470
471    /// 获取当前缓存项数量
472    pub fn size(&self) -> usize {
473        self.data.read().map(|d| d.len()).unwrap_or(0)
474    }
475
476    /// 获取统计信息
477    pub fn stats(&self) -> L2CacheStats {
478        let mut s = self.stats.read().map(|s| s.clone()).unwrap_or_default();
479        // 实时同步 size 字段(不写入 stats,避免持锁读 data)
480        s.size = self.size();
481        s
482    }
483
484    /// 重置统计信息
485    pub fn reset_stats(&self) {
486        if let Ok(mut stats) = self.stats.write() {
487            *stats = L2CacheStats::default();
488        }
489    }
490
491    /// 检查缓存项是否存在(不更新统计与 LRU 顺序)
492    pub fn contains(&self, key: &CacheKey) -> bool {
493        let key_str = key.to_string_key();
494        self.data
495            .read()
496            .map(|d| d.get(&key_str).map(|e| !e.is_expired()).unwrap_or(false))
497            .unwrap_or(false)
498    }
499
500    /// 手动清理所有过期项
501    pub fn evict_expired(&self) -> usize {
502        let expired_keys: Vec<String> = {
503            let data = self.data.read().unwrap();
504            data.iter()
505                .filter(|(_, e)| e.is_expired())
506                .map(|(k, _)| k.clone())
507                .collect()
508        };
509
510        let mut removed = 0;
511        if !expired_keys.is_empty() {
512            let mut data = self.data.write().unwrap();
513            for k in &expired_keys {
514                if data.remove(k).is_some() {
515                    removed += 1;
516                }
517            }
518        }
519
520        if removed > 0 {
521            let mut order = self.access_order.write().unwrap();
522            order.retain(|k| !expired_keys.contains(k));
523            let mut stats = self.stats.write().unwrap();
524            stats.evictions += removed as u64;
525        }
526        removed
527    }
528}
529
530// ============================================================================
531// 单元测试
532// ============================================================================
533
534#[cfg(test)]
535mod tests {
536    use super::*;
537    use crate::Value;
538    use std::thread;
539    use std::time::Duration;
540
541    // ===== CacheKey 测试 =====
542
543    #[test]
544    fn test_cache_key_by_pk() {
545        let key = CacheKey::by_pk("users", 1);
546        assert_eq!(key.table, "users");
547        assert_eq!(key.kind, CacheKeyKind::ByPk);
548        assert_eq!(key.identifier, "1");
549        assert_eq!(key.to_string_key(), "l2:users:pk:1");
550    }
551
552    #[test]
553    fn test_cache_key_by_query() {
554        let key = CacheKey::by_query("orders", "abc123");
555        assert_eq!(key.kind, CacheKeyKind::ByQuery);
556        assert_eq!(key.to_string_key(), "l2:orders:q:abc123");
557    }
558
559    #[test]
560    fn test_cache_key_by_relation() {
561        let key = CacheKey::by_relation("users", "posts:1");
562        assert_eq!(key.kind, CacheKeyKind::ByRelation);
563        assert_eq!(key.to_string_key(), "l2:users:rel:posts:1");
564    }
565
566    #[test]
567    fn test_cache_key_equality() {
568        let k1 = CacheKey::by_pk("users", 1);
569        let k2 = CacheKey::by_pk("users", 1);
570        let k3 = CacheKey::by_pk("users", 2);
571        assert_eq!(k1, k2);
572        assert_ne!(k1, k3);
573    }
574
575    #[test]
576    fn test_cache_key_display() {
577        let key = CacheKey::by_pk("users", 42);
578        assert_eq!(format!("{}", key), "l2:users:pk:42");
579    }
580
581    // ===== L2CacheStats 测试 =====
582
583    #[test]
584    fn test_stats_hit_rate_empty() {
585        let stats = L2CacheStats::default();
586        assert_eq!(stats.hit_rate(), 0.0);
587        assert_eq!(stats.total_lookups(), 0);
588    }
589
590    #[test]
591    fn test_stats_hit_rate_calculation() {
592        let stats = L2CacheStats {
593            hits: 80,
594            misses: 20,
595            ..Default::default()
596        };
597        assert_eq!(stats.total_lookups(), 100);
598        assert!((stats.hit_rate() - 0.8).abs() < 0.001);
599        assert!((stats.miss_rate() - 0.2).abs() < 0.001);
600    }
601
602    #[test]
603    fn test_stats_merge() {
604        let mut s1 = L2CacheStats {
605            hits: 10,
606            misses: 5,
607            sets: 15,
608            evictions: 2,
609            size: 100,
610        };
611        let s2 = L2CacheStats {
612            hits: 20,
613            misses: 10,
614            sets: 30,
615            evictions: 5,
616            size: 200,
617        };
618        s1.merge(&s2);
619        assert_eq!(s1.hits, 30);
620        assert_eq!(s1.misses, 15);
621        assert_eq!(s1.sets, 45);
622        assert_eq!(s1.evictions, 7);
623        assert_eq!(s1.size, 300);
624    }
625
626    // ===== L2Cache 基本操作 =====
627
628    #[test]
629    fn test_put_and_get() {
630        let cache = L2Cache::new();
631        let key = CacheKey::by_pk("users", 1);
632
633        cache.put(&key, Value::String("Alice".to_string()), None);
634        let val = cache.get(&key);
635        assert_eq!(val, Some(Value::String("Alice".to_string())));
636    }
637
638    #[test]
639    fn test_get_missing_returns_none() {
640        let cache = L2Cache::new();
641        let key = CacheKey::by_pk("users", 999);
642        assert_eq!(cache.get(&key), None);
643    }
644
645    #[test]
646    fn test_overwrite_existing_key() {
647        let cache = L2Cache::new();
648        let key = CacheKey::by_pk("users", 1);
649
650        cache.put(&key, Value::String("Alice".to_string()), None);
651        cache.put(&key, Value::String("Bob".to_string()), None);
652        assert_eq!(cache.get(&key), Some(Value::String("Bob".to_string())));
653    }
654
655    #[test]
656    fn test_invalidate_single_key() {
657        let cache = L2Cache::new();
658        let key = CacheKey::by_pk("users", 1);
659
660        cache.put(&key, Value::I64(42), None);
661        assert!(cache.get(&key).is_some());
662
663        cache.invalidate(&key);
664        assert!(cache.get(&key).is_none());
665    }
666
667    // ===== 表级失效 =====
668
669    #[test]
670    fn test_invalidate_table_removes_all_entries_for_table() {
671        let cache = L2Cache::new();
672
673        let k1 = CacheKey::by_pk("users", 1);
674        let k2 = CacheKey::by_pk("users", 2);
675        let k3 = CacheKey::by_query("users", "hash1");
676        let k4 = CacheKey::by_pk("orders", 1); // 不同表
677
678        cache.put(&k1, Value::I64(1), None);
679        cache.put(&k2, Value::I64(2), None);
680        cache.put(&k3, Value::I64(3), None);
681        cache.put(&k4, Value::I64(4), None);
682
683        cache.invalidate_table("users");
684
685        // users 表的所有缓存项应被失效
686        assert!(cache.get(&k1).is_none());
687        assert!(cache.get(&k2).is_none());
688        assert!(cache.get(&k3).is_none());
689        // orders 表的缓存项应保留
690        assert!(cache.get(&k4).is_some());
691    }
692
693    #[test]
694    fn test_invalidate_table_no_op_for_unknown_table() {
695        let cache = L2Cache::new();
696        let k1 = CacheKey::by_pk("users", 1);
697        cache.put(&k1, Value::I64(1), None);
698
699        cache.invalidate_table("nonexistent");
700        assert!(cache.get(&k1).is_some());
701    }
702
703    // ===== TTL 测试 =====
704
705    #[test]
706    fn test_ttl_expiration() {
707        let cache = L2Cache::new();
708        let key = CacheKey::by_pk("users", 1);
709
710        cache.put(&key, Value::I64(42), Some(Duration::from_millis(50)));
711        assert!(cache.get(&key).is_some());
712
713        // 等待 TTL 过期
714        thread::sleep(Duration::from_millis(100));
715        assert!(cache.get(&key).is_none());
716    }
717
718    #[test]
719    fn test_default_ttl_applied_when_no_explicit_ttl() {
720        let cache = L2Cache::new().with_default_ttl(Duration::from_millis(50));
721        let key = CacheKey::by_pk("users", 1);
722
723        cache.put(&key, Value::I64(42), None); // 不显式传 TTL
724        assert!(cache.get(&key).is_some());
725
726        thread::sleep(Duration::from_millis(100));
727        assert!(cache.get(&key).is_none());
728    }
729
730    #[test]
731    fn test_explicit_ttl_overrides_default() {
732        // 语义验证:ttl=Some(Duration::MAX) 表示永不失效,覆盖默认 TTL
733        let cache = L2Cache::new().with_default_ttl(Duration::from_millis(50));
734        let key = CacheKey::by_pk("users", 1);
735
736        // 显式传 Some(Duration::MAX) 覆盖默认 TTL(永不失效)
737        cache.put(&key, Value::I64(42), Some(Duration::MAX));
738
739        // 等待默认 TTL 已过期的时间
740        thread::sleep(Duration::from_millis(100));
741        // 由于显式传 Some(Duration::MAX),应仍然有效
742        assert!(cache.get(&key).is_some());
743    }
744
745    #[test]
746    fn test_none_ttl_uses_default_ttl() {
747        // 语义验证:ttl=None 时使用 default_ttl
748        let cache = L2Cache::new().with_default_ttl(Duration::from_millis(50));
749        let key = CacheKey::by_pk("users", 1);
750
751        cache.put(&key, Value::I64(42), None);
752        assert!(cache.get(&key).is_some());
753
754        thread::sleep(Duration::from_millis(100));
755        // None 使用了 default_ttl,应已过期
756        assert!(cache.get(&key).is_none());
757    }
758
759    // ===== 命中率统计 =====
760
761    #[test]
762    fn test_stats_tracks_hits_and_misses() {
763        let cache = L2Cache::new();
764
765        let k1 = CacheKey::by_pk("users", 1);
766        let k2 = CacheKey::by_pk("users", 2);
767
768        cache.put(&k1, Value::I64(1), None);
769
770        // 1 次命中
771        cache.get(&k1);
772        // 2 次未命中
773        cache.get(&k2);
774        cache.get(&k2);
775
776        let stats = cache.stats();
777        assert_eq!(stats.hits, 1);
778        assert_eq!(stats.misses, 2);
779        assert_eq!(stats.sets, 1);
780    }
781
782    #[test]
783    fn test_stats_tracks_evictions() {
784        let cache = L2Cache::new();
785        let k1 = CacheKey::by_pk("users", 1);
786        let k2 = CacheKey::by_pk("users", 2);
787
788        cache.put(&k1, Value::I64(1), None);
789        cache.put(&k2, Value::I64(2), None);
790
791        cache.invalidate(&k1); // evictions = 1
792        cache.invalidate_table("users"); // 仅 k2 实际被删除,evictions = 2
793
794        let stats = cache.stats();
795        // invalidate(k1) 删除 1 项;invalidate_table("users") 仅删除 k2(k1 已不存在)
796        assert_eq!(stats.evictions, 2);
797    }
798
799    #[test]
800    fn test_stats_reset() {
801        let cache = L2Cache::new();
802        let k1 = CacheKey::by_pk("users", 1);
803
804        cache.put(&k1, Value::I64(1), None);
805        cache.get(&k1);
806        cache.get(&k1);
807
808        let stats_before = cache.stats();
809        assert!(stats_before.hits > 0);
810
811        cache.reset_stats();
812        let stats_after = cache.stats();
813        assert_eq!(stats_after.hits, 0);
814        assert_eq!(stats_after.misses, 0);
815        assert_eq!(stats_after.sets, 0);
816    }
817
818    // ===== 容量管理 =====
819
820    #[test]
821    fn test_max_size_eviction() {
822        let cache = L2Cache::new().with_max_size(3);
823
824        for i in 0..5 {
825            let k = CacheKey::by_pk("users", i);
826            cache.put(&k, Value::I64(i), None);
827        }
828
829        // 真正的 LRU:容量严格不超过 max_size
830        let size = cache.size();
831        assert_eq!(
832            size, 3,
833            "size should be exactly max_size after LRU eviction, got {}",
834            size
835        );
836    }
837
838    #[test]
839    fn test_lru_eviction_order() {
840        // 验证 LRU 顺序:访问 k0 后,下次淘汰应跳过 k0 而淘汰 k1
841        let cache = L2Cache::new().with_max_size(3);
842
843        let k0 = CacheKey::by_pk("users", 0);
844        let k1 = CacheKey::by_pk("users", 1);
845        let k2 = CacheKey::by_pk("users", 2);
846        let k3 = CacheKey::by_pk("users", 3);
847
848        cache.put(&k0, Value::I64(0), None);
849        cache.put(&k1, Value::I64(1), None);
850        cache.put(&k2, Value::I64(2), None);
851
852        // 访问 k0,使其成为最近使用
853        let _ = cache.get(&k0);
854
855        // 插入 k3,应淘汰 k1(最久未访问)
856        cache.put(&k3, Value::I64(3), None);
857
858        assert!(
859            cache.get(&k0).is_some(),
860            "k0 should survive (recently accessed)"
861        );
862        assert!(
863            cache.get(&k1).is_none(),
864            "k1 should be evicted (LRU victim)"
865        );
866        assert!(cache.get(&k2).is_some(), "k2 should survive");
867        assert!(
868            cache.get(&k3).is_some(),
869            "k3 should survive (just inserted)"
870        );
871    }
872
873    #[test]
874    fn test_clear_all() {
875        let cache = L2Cache::new();
876        cache.put(&CacheKey::by_pk("users", 1), Value::I64(1), None);
877        cache.put(&CacheKey::by_pk("users", 2), Value::I64(2), None);
878        cache.put(&CacheKey::by_pk("orders", 1), Value::I64(3), None);
879
880        assert_eq!(cache.size(), 3);
881        cache.clear();
882        assert_eq!(cache.size(), 0);
883    }
884
885    // ===== contains(不更新统计)=====
886
887    #[test]
888    fn test_contains_does_not_update_stats() {
889        let cache = L2Cache::new();
890        let k1 = CacheKey::by_pk("users", 1);
891        cache.put(&k1, Value::I64(1), None);
892
893        let exists = cache.contains(&k1);
894        assert!(exists);
895
896        let stats = cache.stats();
897        assert_eq!(stats.hits, 0);
898        assert_eq!(stats.misses, 0);
899    }
900
901    #[test]
902    fn test_contains_returns_false_for_missing() {
903        let cache = L2Cache::new();
904        let k = CacheKey::by_pk("users", 999);
905        assert!(!cache.contains(&k));
906    }
907
908    #[test]
909    fn test_contains_returns_false_for_expired() {
910        let cache = L2Cache::new();
911        let k = CacheKey::by_pk("users", 1);
912        cache.put(&k, Value::I64(1), Some(Duration::from_millis(10)));
913
914        thread::sleep(Duration::from_millis(50));
915        assert!(!cache.contains(&k));
916    }
917
918    // ===== evict_expired 手动清理 =====
919
920    #[test]
921    fn test_evict_expired_removes_only_expired_entries() {
922        let cache = L2Cache::new();
923
924        let k1 = CacheKey::by_pk("users", 1);
925        let k2 = CacheKey::by_pk("users", 2);
926
927        cache.put(&k1, Value::I64(1), Some(Duration::from_millis(10)));
928        cache.put(&k2, Value::I64(2), None); // 永不过期
929
930        thread::sleep(Duration::from_millis(50));
931        let removed = cache.evict_expired();
932
933        assert_eq!(removed, 1);
934        assert!(cache.get(&k1).is_none());
935        assert!(cache.get(&k2).is_some());
936    }
937
938    #[test]
939    fn test_evict_expired_returns_zero_if_no_expired() {
940        let cache = L2Cache::new();
941        let k1 = CacheKey::by_pk("users", 1);
942        cache.put(&k1, Value::I64(1), None);
943
944        let removed = cache.evict_expired();
945        assert_eq!(removed, 0);
946    }
947
948    // ===== 多线程测试 =====
949
950    #[test]
951    fn test_concurrent_access() {
952        let cache = std::sync::Arc::new(L2Cache::new());
953        let mut handles = Vec::new();
954
955        // 多线程写入
956        for i in 0..4 {
957            let c = cache.clone();
958            handles.push(thread::spawn(move || {
959                for j in 0..10 {
960                    let k = CacheKey::by_pk("users", i * 10 + j);
961                    c.put(&k, Value::I64(i * 10 + j), None);
962                }
963            }));
964        }
965        for h in handles {
966            h.join().unwrap();
967        }
968
969        assert_eq!(cache.size(), 40);
970
971        // 多线程读取
972        let mut handles = Vec::new();
973        for i in 0..4 {
974            let c = cache.clone();
975            handles.push(thread::spawn(move || {
976                for j in 0..10 {
977                    let k = CacheKey::by_pk("users", i * 10 + j);
978                    let v = c.get(&k);
979                    assert!(v.is_some());
980                }
981            }));
982        }
983        for h in handles {
984            h.join().unwrap();
985        }
986
987        let stats = cache.stats();
988        assert_eq!(stats.hits, 40);
989    }
990
991    // ===== Default 测试 =====
992
993    #[test]
994    fn test_default() {
995        let cache = L2Cache::default();
996        assert_eq!(cache.size(), 0);
997    }
998
999    // ===== 综合场景 =====
1000
1001    #[test]
1002    fn test_realistic_scenario() {
1003        let cache = L2Cache::new();
1004
1005        // 1. 缓存用户表数据
1006        for i in 1..=5 {
1007            cache.put(
1008                &CacheKey::by_pk("users", i),
1009                Value::String(format!("user_{}", i)),
1010                None,
1011            );
1012        }
1013
1014        // 2. 缓存查询结果
1015        cache.put(
1016            &CacheKey::by_query("users", "active_users_hash"),
1017            Value::I64(5),
1018            None,
1019        );
1020
1021        // 3. 读取(部分命中、部分未命中)
1022        for i in 1..=10 {
1023            let _ = cache.get(&CacheKey::by_pk("users", i));
1024        }
1025
1026        let stats = cache.stats();
1027        assert_eq!(stats.hits, 5); // 1-5 命中
1028        assert_eq!(stats.misses, 5); // 6-10 未命中
1029        assert_eq!(stats.sets, 6); // 5 pk + 1 query
1030
1031        // 4. 用户表更新,失效所有缓存
1032        cache.invalidate_table("users");
1033
1034        // 5. 再次读取应全部未命中
1035        cache.reset_stats();
1036        for i in 1..=5 {
1037            let _ = cache.get(&CacheKey::by_pk("users", i));
1038        }
1039        let stats2 = cache.stats();
1040        assert_eq!(stats2.hits, 0);
1041        assert_eq!(stats2.misses, 5);
1042    }
1043}