Cache em Rust: Redis, Memcached e soluções embutidas
Cache é um dos mecanismos-chave para otimizar o desempenho de aplicações modernas. No ecossistema Rust, conhecido por sua velocidade e segurança de memória, escolher a ferramenta certa para cache pode impactar significativamente a eficiência do programa. Neste artigo, exploraremos três abordagens principais: sistemas externos Redis e Memcached, além de soluções embutidas disponíveis na biblioteca padrão e em crates populares.
Por que usar cache em Rust?
Rust já é muito rápido por si só, mas ao trabalhar com bancos de dados, APIs externas ou cálculos complexos, o cache permite:
- Reduzir a latência (latency) em requisições repetidas
- Diminuir a carga em bancos de dados e serviços externos
- Economizar recursos computacionais e tempo de CPU
1. Soluções embutidas: HashMap e cache LRU
A maneira mais simples de fazer cache em Rust é usar HashMap da biblioteca padrão. Para aplicações multithread, é necessário um wrapper Mutex<HashMap> ou RwLock<HashMap>. No entanto, essa abordagem não possui política de despejo (eviction policy), o que pode levar ao crescimento infinito da memória.
Exemplo de cache simples com 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 com o crate lru
Para limitar o tamanho do cache e despejar automaticamente entradas antigas, use o crate lru. Ele implementa o algoritmo Least Recently Used — remove os elementos menos usados recentemente.
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" será despejado
println!("{:?}", cache.get(&"apple")); // None println!("{:?}", cache.get(&"date")); // Some(40)}Prós: latência de rede zero, simplicidade, controle total. Contras: o cache vive apenas dentro de um único processo, sem persistência.
2. Cache com Redis usando redis-rs
Redis é um armazenamento in-memory do tipo chave-valor, frequentemente usado como cache distribuído. Em Rust, o crate redis (ou redis-rs) é usado para trabalhar com Redis.
Conexão e operações básicas
use redis::Commands;
fn main() -> redis::RedisResult<()> { let client = redis::Client::open("redis://127.0.0.1:6379/")?; let mut con = client.get_connection()?;
// Definir valor com TTL (tempo de vida) con.set_ex("user:42", "{\\"name\\": \\"Alice\\"}", 60)?;
// Leitura let value: String = con.get("user:42")?; println!("{}", value);
// Verificar existência let exists: bool = con.exists("user:42")?; println!("Exists: {}", exists);
Ok(())}Funcionalidades avançadas do Redis
- TTL (Time To Live): remoção automática de entradas expiradas
- Estruturas de dados: listas, conjuntos, hashes — para cenários complexos
- Pub/Sub: notificações sobre mudanças no cache
- Clusterização: escalabilidade horizontal
Prós: dis