html_translation_lib/storage/
smart_cache.rs1use crate::config::TranslationConfig;
6use crate::error::{TranslationError, TranslationResult};
7use std::collections::{HashMap, BTreeSet};
8use std::hash::{Hash, Hasher};
9use std::time::{Duration, SystemTime, UNIX_EPOCH};
10use std::sync::atomic::{AtomicU64, Ordering};
11
12pub struct SmartCacheManager {
14 l1_cache: LRUCache<u64, CachedTranslation>,
16
17 l2_cache: FrequencyWeightedCache<u64, CachedTranslation>,
19
20 stats: CacheStats,
22
23 config: CacheConfig,
25
26 predictor: CachePredictor,
28}
29
30#[derive(Debug, Clone)]
32pub struct CacheConfig {
33 pub l1_size: usize,
35 pub l2_size: usize,
37 pub ttl: Duration,
39 pub enable_prediction: bool,
41 pub max_memory_bytes: usize,
43}
44
45impl Default for CacheConfig {
46 fn default() -> Self {
47 Self {
48 l1_size: 1000,
49 l2_size: 5000,
50 ttl: Duration::from_secs(3600),
51 enable_prediction: true,
52 max_memory_bytes: 100 * 1024 * 1024, }
54 }
55}
56
57#[derive(Debug, Clone)]
59pub struct CachedTranslation {
60 pub translation: String,
62 pub created_at: SystemTime,
64 pub access_count: u32,
66 pub last_accessed: SystemTime,
68 pub content_hash: u64,
70 pub memory_size: usize,
72}
73
74impl CachedTranslation {
75 pub fn new(translation: String, original_text: &str) -> Self {
77 let now = SystemTime::now();
78 let memory_size = translation.len() * 2 + 64; let mut hasher = std::collections::hash_map::DefaultHasher::new();
81 original_text.hash(&mut hasher);
82 let content_hash = hasher.finish();
83
84 Self {
85 translation,
86 created_at: now,
87 access_count: 1,
88 last_accessed: now,
89 content_hash,
90 memory_size,
91 }
92 }
93
94 pub fn is_expired(&self, ttl: Duration) -> bool {
96 self.created_at.elapsed().unwrap_or(Duration::ZERO) > ttl
97 }
98
99 pub fn mark_accessed(&mut self) {
101 self.access_count += 1;
102 self.last_accessed = SystemTime::now();
103 }
104
105 pub fn access_frequency(&self) -> f64 {
107 let elapsed = self.created_at.elapsed().unwrap_or(Duration::from_secs(1));
108 let minutes = elapsed.as_secs_f64() / 60.0;
109 self.access_count as f64 / minutes.max(1.0)
110 }
111}
112
113pub struct LRUCache<K, V> {
115 map: HashMap<K, V>,
116 order: BTreeSet<(SystemTime, K)>,
117 capacity: usize,
118 memory_usage: AtomicU64,
119}
120
121impl<K: Hash + Eq + Clone + Ord, V: Clone> LRUCache<K, V> {
122 pub fn new(capacity: usize) -> Self {
123 Self {
124 map: HashMap::with_capacity(capacity),
125 order: BTreeSet::new(),
126 capacity,
127 memory_usage: AtomicU64::new(0),
128 }
129 }
130
131 pub fn get(&mut self, key: &K) -> Option<&mut V> {
132 if let Some(value) = self.map.get_mut(key) {
133 let now = SystemTime::now();
135 self.order.insert((now, key.clone()));
136 Some(value)
137 } else {
138 None
139 }
140 }
141
142 pub fn insert(&mut self, key: K, value: V) -> Option<V>
143 where
144 V: AsRef<CachedTranslation>,
145 {
146 let now = SystemTime::now();
147
148 if self.map.len() >= self.capacity {
150 if let Some(&(_, ref oldest_key)) = self.order.iter().next() {
151 let oldest_key = oldest_key.clone();
152 self.remove(&oldest_key);
153 }
154 }
155
156 self.order.insert((now, key.clone()));
157 let old_value = self.map.insert(key, value);
158
159 old_value
160 }
161
162 pub fn remove(&mut self, key: &K) -> Option<V> {
163 if let Some(value) = self.map.remove(key) {
164 self.order.retain(|(_, k)| k != key);
166 Some(value)
167 } else {
168 None
169 }
170 }
171
172 pub fn len(&self) -> usize {
173 self.map.len()
174 }
175
176 pub fn is_empty(&self) -> bool {
177 self.map.is_empty()
178 }
179}
180
181pub struct FrequencyWeightedCache<K, V> {
183 map: HashMap<K, V>,
184 frequency_scores: HashMap<K, f64>,
185 capacity: usize,
186}
187
188impl<K: Hash + Eq + Clone, V: Clone> FrequencyWeightedCache<K, V> {
189 pub fn new(capacity: usize) -> Self {
190 Self {
191 map: HashMap::with_capacity(capacity),
192 frequency_scores: HashMap::with_capacity(capacity),
193 capacity,
194 }
195 }
196
197 pub fn get(&mut self, key: &K) -> Option<&mut V>
198 where
199 V: AsMut<CachedTranslation>,
200 {
201 if let Some(value) = self.map.get_mut(key) {
202 let score = self.frequency_scores.entry(key.clone()).or_insert(1.0);
204 *score *= 1.1; value.as_mut().mark_accessed();
208
209 Some(value)
210 } else {
211 None
212 }
213 }
214
215 pub fn insert(&mut self, key: K, value: V) -> Option<V>
216 where
217 V: AsRef<CachedTranslation>,
218 {
219 if self.map.len() >= self.capacity {
221 if let Some((lowest_key, _)) = self.frequency_scores
222 .iter()
223 .min_by(|(_, a), (_, b)| a.partial_cmp(b).unwrap())
224 .map(|(k, v)| (k.clone(), *v))
225 {
226 self.remove(&lowest_key);
227 }
228 }
229
230 self.frequency_scores.insert(key.clone(), 1.0);
231 self.map.insert(key, value)
232 }
233
234 pub fn remove(&mut self, key: &K) -> Option<V> {
235 self.frequency_scores.remove(key);
236 self.map.remove(key)
237 }
238
239 pub fn len(&self) -> usize {
240 self.map.len()
241 }
242}
243
244pub struct CachePredictor {
246 access_patterns: HashMap<String, Vec<SystemTime>>,
248 co_occurrence: HashMap<String, HashMap<String, u32>>,
250}
251
252impl CachePredictor {
253 pub fn new() -> Self {
254 Self {
255 access_patterns: HashMap::new(),
256 co_occurrence: HashMap::new(),
257 }
258 }
259
260 pub fn record_access(&mut self, text: &str) {
262 let pattern = self.access_patterns.entry(text.to_string()).or_default();
263 pattern.push(SystemTime::now());
264
265 if pattern.len() > 50 {
267 pattern.drain(0..pattern.len() - 50);
268 }
269 }
270
271 pub fn record_co_occurrence(&mut self, texts: &[String]) {
273 for (i, text1) in texts.iter().enumerate() {
274 for text2 in texts.iter().skip(i + 1) {
275 let entry = self.co_occurrence
276 .entry(text1.clone())
277 .or_default()
278 .entry(text2.clone())
279 .or_insert(0);
280 *entry += 1;
281 }
282 }
283 }
284
285 pub fn predict_next(&self, recent_texts: &[String]) -> Vec<String> {
287 let mut predictions = HashMap::new();
288
289 for text in recent_texts {
290 if let Some(co_occurs) = self.co_occurrence.get(text) {
291 for (co_text, &count) in co_occurs {
292 *predictions.entry(co_text.clone()).or_insert(0) += count;
293 }
294 }
295 }
296
297 let mut sorted: Vec<_> = predictions.into_iter().collect();
298 sorted.sort_by_key(|(_, count)| *count);
299 sorted.reverse();
300
301 sorted.into_iter()
302 .take(10) .map(|(text, _)| text)
304 .collect()
305 }
306}
307
308impl SmartCacheManager {
309 pub async fn new(translation_config: &TranslationConfig) -> TranslationResult<Self> {
311 let config = CacheConfig {
312 l1_size: 1000,
313 l2_size: translation_config.max_cache_entries.saturating_sub(1000),
314 ttl: translation_config.cache_ttl,
315 enable_prediction: true,
316 max_memory_bytes: 100 * 1024 * 1024,
317 };
318
319 Ok(Self {
320 l1_cache: LRUCache::new(config.l1_size),
321 l2_cache: FrequencyWeightedCache::new(config.l2_size),
322 stats: CacheStats::default(),
323 predictor: CachePredictor::new(),
324 config,
325 })
326 }
327
328 pub async fn get(&mut self, text: &str) -> TranslationResult<Option<String>> {
330 let key = self.hash_text(text);
331
332 if self.config.enable_prediction {
334 self.predictor.record_access(text);
335 }
336
337 if let Some(cached) = self.l1_cache.get(&key) {
339 if !cached.is_expired(self.config.ttl) {
340 cached.mark_accessed();
341 self.stats.l1_hits += 1;
342 return Ok(Some(cached.translation.clone()));
343 } else {
344 self.l1_cache.remove(&key);
346 }
347 }
348
349 if let Some(cached) = self.l2_cache.get(&key) {
351 if !cached.as_ref().is_expired(self.config.ttl) {
352 self.stats.l2_hits += 1;
353 let translation = cached.as_ref().translation.clone();
355 if cached.as_ref().access_frequency() > 1.0 { let cached_clone = cached.as_ref().clone();
357 self.l1_cache.insert(key, cached_clone);
358 }
359 return Ok(Some(translation));
360 } else {
361 self.l2_cache.remove(&key);
363 }
364 }
365
366 self.stats.misses += 1;
367 Ok(None)
368 }
369
370 pub async fn set(&mut self, text: &str, translation: String) -> TranslationResult<()> {
372 let key = self.hash_text(text);
373 let cached = CachedTranslation::new(translation, text);
374
375 self.check_memory_usage().await?;
377
378 if self.should_cache_in_l1(text) {
380 self.l1_cache.insert(key, cached);
381 } else {
382 self.l2_cache.insert(key, cached);
383 }
384
385 Ok(())
386 }
387
388 pub async fn preload(&mut self, translations: Vec<(String, String)>) -> TranslationResult<()> {
390 for (text, translation) in translations {
391 self.set(&text, translation).await?;
392 }
393 Ok(())
394 }
395
396 pub fn stats(&self) -> &CacheStats {
398 &self.stats
399 }
400
401 pub async fn cleanup_expired(&mut self) -> TranslationResult<()> {
403 let now = SystemTime::now();
404 let ttl = self.config.ttl;
405
406 self.stats.cleanups_performed += 1;
409
410 Ok(())
411 }
412
413 fn hash_text(&self, text: &str) -> u64 {
415 use std::collections::hash_map::DefaultHasher;
416 let mut hasher = DefaultHasher::new();
417 text.hash(&mut hasher);
418 hasher.finish()
419 }
420
421 fn should_cache_in_l1(&self, text: &str) -> bool {
423 text.len() < 100 ||
425 text.contains("button") || text.contains("click") || text.contains("submit")
427 }
428
429 async fn check_memory_usage(&mut self) -> TranslationResult<()> {
431 let estimated_memory = (self.l1_cache.len() + self.l2_cache.len()) * 100; if estimated_memory > self.config.max_memory_bytes {
435 self.cleanup_expired().await?;
437 }
438
439 Ok(())
440 }
441}
442
443#[derive(Debug, Default, Clone)]
445pub struct CacheStats {
446 pub l1_hits: u64,
448 pub l2_hits: u64,
450 pub misses: u64,
452 pub cleanups_performed: u64,
454 pub predictions_successful: u64,
456}
457
458impl CacheStats {
459 pub fn hit_rate(&self) -> f64 {
461 let total_requests = self.l1_hits + self.l2_hits + self.misses;
462 if total_requests > 0 {
463 (self.l1_hits + self.l2_hits) as f64 / total_requests as f64
464 } else {
465 0.0
466 }
467 }
468
469 pub fn l1_hit_rate(&self) -> f64 {
471 let total_requests = self.l1_hits + self.l2_hits + self.misses;
472 if total_requests > 0 {
473 self.l1_hits as f64 / total_requests as f64
474 } else {
475 0.0
476 }
477 }
478}
479
480impl AsRef<CachedTranslation> for CachedTranslation {
482 fn as_ref(&self) -> &CachedTranslation {
483 self
484 }
485}
486
487impl AsMut<CachedTranslation> for CachedTranslation {
488 fn as_mut(&mut self) -> &mut CachedTranslation {
489 self
490 }
491}
492
493impl Default for CachePredictor {
494 fn default() -> Self {
495 Self::new()
496 }
497}
498
499#[cfg(test)]
500mod tests {
501 use super::*;
502
503 #[test]
504 fn test_lru_cache() {
505 let mut cache = LRUCache::new(2);
506 let item1 = CachedTranslation::new("translation1".to_string(), "text1");
507 let item2 = CachedTranslation::new("translation2".to_string(), "text2");
508 let item3 = CachedTranslation::new("translation3".to_string(), "text3");
509
510 cache.insert("key1", item1);
511 cache.insert("key2", item2);
512
513 assert_eq!(cache.len(), 2);
514
515 cache.insert("key3", item3);
517 assert_eq!(cache.len(), 2);
518 assert!(cache.get(&"key1").is_none()); }
520
521 #[test]
522 fn test_cached_translation() {
523 let cached = CachedTranslation::new("hello".to_string(), "原文");
524 assert!(!cached.is_expired(Duration::from_secs(3600)));
525 assert!(cached.is_expired(Duration::from_nanos(1)));
526 }
527
528 #[tokio::test]
529 async fn test_smart_cache_manager() {
530 let config = TranslationConfig::default();
531 let mut cache = SmartCacheManager::new(&config).await.unwrap();
532
533 let result = cache.get("test").await.unwrap();
535 assert!(result.is_none());
536
537 cache.set("test", "测试".to_string()).await.unwrap();
539
540 let result = cache.get("test").await.unwrap();
542 assert_eq!(result.unwrap(), "测试");
543
544 assert_eq!(cache.stats().misses, 1);
546 assert_eq!(cache.stats().l1_hits, 1);
547 }
548}