Introducción: ¿Por qué usar caché en Rust?
\nLa velocidad es la principal ventaja de Rust. Pero incluso el código más rápido puede verse limitado por la entrada/salida (E/S) lenta: consultas a bases de datos, APIs externas o cálculos de datos complejos. El caché resuelve este problema almacenando los resultados de operaciones costosas en un almacenamiento rápido. Rust ofrece herramientas flexibles para el caché: desde estructuras de datos integradas simples hasta potentes sistemas distribuidos como Redis y Memcached.
\nEn este artículo, analizaremos todos los niveles de caché en Rust: desde un simple HashMap hasta soluciones profesionales con TTL (tiempo de vida) y sincronización. Aprenderás a elegir la herramienta adecuada para tu proyecto y evitar errores comunes.
1. Caché integrado: HashMap y sus variantes
\nLa forma más simple de caché en Rust es usar HashMap de la biblioteca estándar. Sin embargo, en entornos multihilo se necesitan mecanismos de sincronización adicionales.
1.1. HashMap simple (monohilo)
\nPara aplicaciones monohilo, es adecuado un HashMap normal. Ejemplo:
use std::collections::HashMap;\n\nfn main() {\n let mut cache: HashMap<String, String> = HashMap::new();\n cache.insert("user:1".to_string(), "Alice".to_string());\n \n if let Some(name) = cache.get("user:1") {\n println!("Valor en caché: {}", name);\n }\n}\nVentajas: código mínimo, sin dependencias.
\nDesventajas: sin TTL, no es seguro para hilos, memoria ilimitada.
1.2. Caché seguro para hilos con Mutex y Arc
\nPara aplicaciones multihilo, usa Arc<Mutex<HashMap>>:
use std::collections::HashMap;\nuse std::sync::{Arc, Mutex};\nuse std::thread;\n\nstruct Cache {\n store: Arc<Mutex<HashMap<String, String>>>,\n}\n\nimpl Cache {\n fn new() -> Self {\n Cache { store: Arc::new(Mutex::new(HashMap::new())) }\n }\n \n fn get(&self, key: &str) -> Option<String> {\n self.store.lock().ok()?.get(key).cloned()\n }\n \n fn set(&self, key: String, value: String) {\n self.store.lock().unwrap().insert(key, value);\n }\n}\n\nfn main() {\n let cache = Cache::new();\n let c1 = cache.clone();\n thread::spawn(move || {\n c1.set("key".to_string(), "value".to_string());\n });\n println!("{:?}", cache.get("key"));\n}\nDesventaja: el bloqueo de Mutex puede reducir el rendimiento en alta concurrencia.
\n\n1.3. Bibliotecas avanzadas: lru y cached
\nPara código en producción, es mejor usar crates ya preparados:
\n- \n
lru— implementa el algoritmo de expulsión LRU (Menos Usado Recientemente). Adecuado para caché limitado. \n cached— proporciona macros para el caché automático de funciones con soporte de TTL y LRU. \n
Ejemplo con cached:
use cached::proc_macro::cached;\n\n#[cached(size = 100, time = 300)]\nfn expensive_computation(x: u32) -> u32 {\n std::thread::sleep(std::time::Duration::from_secs(2));\n x * x\n}\n\nfn main() {\n let result = expensive_computation(5); // tarda 2 seg\n let cached_result = expensive_computation(5); // instantáneo\n println!("{}", cached_result);\n}\n\n2. Redis: caché distribuido para sistemas escalables
\nRedis es un almacén de datos en memoria, ideal para caché distribuido. En Rust, se usa el crate redis para trabajar con Redis.
2.1. Conexión y operaciones básicas
\nuse redis::Commands;\n\nfn main() -> redis::RedisResult<()> {\n let client = redis::Client::open("redis://127.0.0.1:6379/")?;\n let mut con = client.get_connection()?;\n \n // Establecer valor con TTL (tiempo de vida)\n con.set_ex("user:1", "Alice", 3600)?; //