Caché
La caché almacena el resultado de un trabajo costoso — una consulta pesada, un
fragmento renderizado, una llamada a una API de terceros — para que la siguiente
petición lo obtenga al instante en lugar de recalcularlo. Rustango te da un
único trait Cache con backends intercambiables (in-memory, Redis, base de
datos), un helper de cálculo-al-fallar (get_or_set) y helpers JSON tipados.
Cambia el backend sin tocar un solo sitio de llamada — como el framework de caché
de Django o la fachada Cache de Laravel.
¿Un término nuevo aquí? caché, TTL, clave, backend — ver el glosario.
Fuente:
rustango::cache(Cache,InMemoryCache,NullCache,get_or_set,get_json,set_json,BoxedCache,from_settings) — tras la featurecache(activa por defecto).RedisCacherequiere la featurecache-redis(desactivada por defecto).Versión ejecutable: cada fragmento está copiado de
cache_doc.rs(cargo test -p rustango --test cache_doc); el backend de base de datos se prueba con dogfooding sobre SQLite mediantecache_db_backend_sqlite_live.rs.
Tabla de contenidos
- Paso 1 — Elegir un backend
- Paso 2 — get / set / delete
- Paso 3 — get_or_set (cache-aside)
- Valores JSON tipados
- TTL y expiración
- Cambiar de backend
- Referencia
- Véase también
Paso 1 — Elegir un backend
Cada backend implementa el mismo trait Cache, así que tu código es idéntico sea
cual sea el que elijas. El código de la aplicación mantiene un BoxedCache
(Arc<dyn Cache>) y nunca nombra el tipo concreto:
use rustango::cache::{BoxedCache, InMemoryCache};
use std::sync::Arc;
let cache: BoxedCache = Arc::new(InMemoryCache::new());
| Backend | Feature | Usar para |
|---|---|---|
InMemoryCache | cache | dev, tests, un solo proceso (HashMap por proceso + TTL) |
RedisCache | cache-redis | producción; compartido entre réplicas |
DbCache | cache | producción sin Redis; una tabla rustango_cache |
NullCache | cache | deshabilitar la caché (cada lectura falla) — práctico en tests |
Paso 2 — get / set / delete
El núcleo del trait son cuatro métodos async. set toma un TTL opcional
(None = sin expiración); get retorna Option<String> (None en un fallo):
use rustango::cache::{Cache, InMemoryCache};
let cache = InMemoryCache::new();
assert_eq!(cache.get("greeting").await?, None); // miss
cache.set("greeting", "hello", None).await?; // store, no expiry
assert_eq!(cache.get("greeting").await?.as_deref(), Some("hello"));
assert!(cache.exists("greeting").await?);
cache.delete("greeting").await?; // gone
También hay variantes por lotes — get_many / set_many / delete_many.
Paso 3 — get_or_set (cache-aside)
Esta es la que usarás más. get_or_set retorna el valor cacheado, o — en un
fallo — ejecuta tu factory, almacena el resultado con un TTL y lo retorna. La
factory solo se ejecuta en un fallo:
use rustango::cache::get_or_set;
use std::time::Duration;
let stats: HomeStats = get_or_set(
&*cache, // &dyn Cache
"home:stats",
|| async { compute_home_stats(&pool).await }, // runs only on a miss
Some(Duration::from_secs(60)), // cache for 60s
).await?;
El test que lo respalda llama a get_or_set dos veces para la misma clave y
afirma que la factory se ejecutó exactamente una vez — la segunda llamada se
sirve desde la caché.
Invalida al escribir. Cache-aside significa datos obsoletos hasta que expira el TTL. Para datos que cambian, haz también
delete(key)cuando los escribes — p. ej. desde una señalpost_save— para que la siguiente lectura recalcule.
Valores JSON tipados
get_json / set_json serializan cualquier tipo Serialize/Deserialize a
JSON, de modo que cacheas structs y listas, no solo strings:
use rustango::cache::{get_json, set_json};
#[derive(serde::Serialize, serde::Deserialize)]
struct Profile { id: i64, name: String }
set_json(&*cache, "profile:7", &profile, None).await?;
let back: Option<Profile> = get_json(&*cache, "profile:7").await?; // None on a miss
(get_or_set los usa por debajo, por lo que su tipo de valor debe ser
Serialize + Deserialize.)
TTL y expiración
Pasa una Duration a set (o get_or_set) y la entrada desaparece después de
ella. Verificado: una entrada de 50 ms es legible de inmediato y desaparece tras
80 ms.
cache.set("flash", "x", Some(Duration::from_millis(50))).await?;
// ...50ms later...
assert_eq!(cache.get("flash").await?, None); // expired
InMemoryCache::with_default_ttl(d) fija un TTL por defecto que se aplica cuando
pasas None.
Cambiar de backend
Como todo es el trait Cache, cambiar de in-memory a Redis es un cambio de una
línea en el arranque — normalmente impulsado por la configuración para que
difiera según el entorno:
// Build the cache from `[cache]` settings (backend = "memory" | "redis" | "db" | "null").
let cache: BoxedCache = rustango::cache::from_settings(&settings.cache);
En producción, apúntalo a Redis (compartido entre todas tus réplicas):
use rustango::cache::RedisCache; // needs the `cache-redis` feature
let cache: BoxedCache = std::sync::Arc::new(RedisCache::new("redis://localhost").await?);
Las mismas llamadas get / set / get_or_set — solo cambió el constructor.
Referencia
Trait Cache: get · set(key, value, ttl) · delete · exists ·
get_many / set_many / delete_many · get_or(key, default).
Helpers libres: get_or_set(cache, key, factory, ttl) ·
get_json / set_json · from_settings(&CacheSettings).
Qué está construido sobre la caché: las sesiones del lado
del servidor, la limitación de tasa distribuida
(CacheRateLimitLayer), las claves de idempotencia y los feature flags toman
todos un BoxedCache — de modo que una sola instancia de Redis los respalda a
todos.
Véase también
- Trabajos en segundo plano — la otra mitad de mantener rápidas las peticiones (diferir el trabajo en lugar de cachear su resultado).
- Sesiones — un almacén del lado del servidor construido
sobre
Cache. - Middleware —
CacheRateLimitLayercomparte un contador entre réplicas vía la caché. - Cookbook del ORM — invalidar lecturas cacheadas desde una señal
post_save.
