Mise en cache en Rust : Redis, Memcached et solutions intégrées
La mise en cache est l’un des mécanismes clés pour optimiser les performances des applications modernes. Dans l’écosystème Rust, réputé pour sa vitesse et sa sécurité mémoire, choisir le bon outil de mise en cache peut considérablement influencer l’efficacité d’un programme. Dans cet article, nous examinerons trois approches principales : les systèmes externes Redis et Memcached, ainsi que les solutions intégrées disponibles dans la bibliothèque standard et les crates populaires.
Pourquoi mettre en cache en Rust ?
Rust est déjà très rapide en soi, mais lorsqu’il s’agit de travailler avec des bases de données, des API externes ou des calculs complexes, la mise en cache permet de :
- Réduire les latences (latency) lors de requêtes répétées
- Diminuer la charge sur les bases de données et les services externes
- Économiser des ressources de calcul et du temps CPU
1. Solutions intégrées : HashMap et cache LRU
La façon la plus simple de mettre en cache en Rust est d’utiliser HashMap de la bibliothèque standard. Pour les applications multithreadées, il faudra une enveloppe Mutex<HashMap> ou RwLock<HashMap>. Cependant, cette approche n’a pas de politique d’éviction (eviction policy), ce qui peut entraîner une croissance infinie de la mémoire.
Exemple d’un cache simple avec HashMap
use std::collections::HashMap;use std::sync::Mutex;
struct SimpleCache { store: Mutex<HashMap<String, String>>,}
impl SimpleCache { fn new() -> Self { SimpleCache { store: Mutex::new(HashMap::new()), } }
fn get(&self, key: &str) -> Option<String> { self.store.lock().unwrap().get(key).cloned() }
fn set(&self, key: String, value: String) { self.store.lock().unwrap().insert(key, value); }}Cache LRU via la crate lru
Pour limiter la taille du cache et évincer automatiquement les anciennes entrées, utilisez la crate lru. Elle implémente l’algorithme Least Recently Used — elle supprime les éléments les moins fréquemment utilisés.
use lru::LruCache;use std::num::NonZeroUsize;
fn main() { let mut cache = LruCache::new(NonZeroUsize::new(3).unwrap()); cache.put("apple", 10); cache.put("banana", 20); cache.put("cherry", 30); cache.put("date", 40); // "apple" sera évincé
println!("{:?}", cache.get(&"apple")); // None println!("{:?}", cache.get(&"date")); // Some(40)}Avantages : latence réseau nulle, simplicité, contrôle total. Inconvénients : le cache ne vit que dans un seul processus, pas de persistance.
2. Mise en cache avec Redis via redis-rs
Redis est un stockage clé-valeur en mémoire (in-memory) souvent utilisé comme cache distribué. En Rust, on utilise la crate redis (ou redis-rs) pour interagir avec Redis.
Connexion et opérations de base
use redis::Commands;
fn main() -> redis::RedisResult<()> { let client = redis::Client::open("redis://127.0.0.1:6379/")?; let mut con = client.get_connection()?;
// Définir une valeur avec TTL (durée de vie) con.set_ex("user:42", "{\\"name\\": \\"Alice\\"}", 60)?;
// Lecture let value: String = con.get("user:42")?; println!("{}", value);
// Vérification de l'existence let exists: bool = con.exists("user:42")?; println!("Exists: {}", exists);
Ok(())}Fonctionnalités avancées de Redis
- TTL (Time To Live) : suppression automatique des entrées obsolètes
- Structures de données : listes, ensembles, hachages — pour des scénarios complexes
- Pub/Sub : notifications de modifications du cache
- Clustering : mise à l’échelle horizontale
Avantages : dist