Rustango docs
← Anleitungen

Caching

Caching speichert das Ergebnis teurer Arbeit — eine schwere Query, ein gerendertes Fragment, einen Drittanbieter-API-Aufruf — sodass der nächste Request es sofort erhält, statt es neu zu berechnen. Rustango gibt dir einen Cache-Trait mit austauschbaren Backends (In-Memory, Redis, Datenbank), einen Compute-on-Miss-Helfer (get_or_set) und typisierte JSON-Helfer. Tausche das Backend, ohne eine einzige Aufrufstelle anzufassen — wie Djangos Cache-Framework oder Laravels Cache-Fassade.

Caching in Rustango: get_or_set checks the cache, runs the factory only on a miss, stores the result with a TTL, and serves hits instantly; the same Cache trait backs InMemory, Redis, and DB

Ein Begriff hier neu für dich? Cache, TTL, Key, Backend — siehe das Glossar.

Quelle: rustango::cache (Cache, InMemoryCache, NullCache, get_or_set, get_json, set_json, BoxedCache, from_settings) — hinter der cache-Feature (standardmäßig aktiv). RedisCache benötigt die cache-redis-Feature (standardmäßig aus).

Ausführbare Version: jeder Codeausschnitt ist aus cache_doc.rs kopiert (cargo test -p rustango --test cache_doc); das Datenbank-Backend wird per Dogfooding auf SQLite durch cache_db_backend_sqlite_live.rs erprobt.

Inhaltsverzeichnis


Schritt 1 — Ein Backend wählen

Jedes Backend implementiert denselben Cache-Trait, dein Code ist also identisch, welches du auch wählst. Der App-Code hält einen BoxedCache (Arc<dyn Cache>) und nennt nie den konkreten Typ:

use rustango::cache::{BoxedCache, InMemoryCache};
use std::sync::Arc;

let cache: BoxedCache = Arc::new(InMemoryCache::new());
BackendFeatureVerwenden für
InMemoryCachecacheDev, Tests, Einzelprozess (HashMap pro Prozess + TTL)
RedisCachecache-redisProduktion; über Replicas geteilt
DbCachecacheProduktion ohne Redis; eine rustango_cache-Tabelle
NullCachecacheCaching deaktivieren (jeder Read verfehlt) — praktisch in Tests

Schritt 2 — get / set / delete

Der Kern des Traits sind vier async-Methoden. set nimmt ein optionales TTL (None = kein Ablauf); get gibt Option<String> zurück (None bei einem Miss):

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

Es gibt auch Batch-Varianten — get_many / set_many / delete_many.


Schritt 3 — get_or_set (Cache-Aside)

Das ist die, zu der du am häufigsten greifst. get_or_set gibt den gecachten Wert zurück, oder — bei einem Miss — führt deine Factory aus, speichert das Ergebnis mit einem TTL und gibt es zurück. Die Factory läuft nur bei einem Miss:

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?;

Der zugrunde liegende Test ruft get_or_set zweimal für denselben Key auf und stellt sicher, dass die Factory genau einmal lief — der zweite Aufruf wird aus dem Cache bedient.

Bei Schreibvorgängen invalidieren. Cache-Aside bedeutet veraltete Daten, bis das TTL abläuft. Für Daten, die sich ändern, mache auch delete(key), wenn du sie schreibst — z. B. aus einem post_save-Signal — damit der nächste Read neu berechnet.


Typisierte JSON-Werte

get_json / set_json serialisieren jeden Serialize/Deserialize-Typ nach JSON, sodass du Structs und Listen cachst, nicht nur 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 verwendet diese unter der Haube, weshalb sein Werttyp Serialize + Deserialize sein muss.)


TTL und Ablauf

Übergib eine Duration an set (oder get_or_set) und der Eintrag verschwindet danach. Verifiziert: ein 50-ms-Eintrag ist sofort lesbar und nach 80 ms weg.

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) setzt ein Standard-TTL, das angewendet wird, wenn du None übergibst.


Backends tauschen

Weil alles der Cache-Trait ist, ist der Wechsel von In-Memory zu Redis eine einzeilige Änderung beim Start — meist von der Konfiguration gesteuert, sodass sie sich pro Umgebung unterscheidet:

// Build the cache from `[cache]` settings (backend = "memory" | "redis" | "db" | "null").
let cache: BoxedCache = rustango::cache::from_settings(&settings.cache);

In der Produktion richtest du es auf Redis (über alle deine Replicas geteilt):

use rustango::cache::RedisCache;   // needs the `cache-redis` feature
let cache: BoxedCache = std::sync::Arc::new(RedisCache::new("redis://localhost").await?);

Dieselben get / set / get_or_set-Aufrufe — nur der Konstruktor hat sich geändert.


Referenz

Cache-Trait: get · set(key, value, ttl) · delete · exists · get_many / set_many / delete_many · get_or(key, default).

Freie Helfer: get_or_set(cache, key, factory, ttl) · get_json / set_json · from_settings(&CacheSettings).

Was auf dem Cache aufbaut: serverseitige Sessions, verteiltes Rate Limiting (CacheRateLimitLayer), Idempotenz-Schlüssel und Feature-Flags nehmen alle einen BoxedCache — sodass eine einzige Redis-Instanz sie alle stützt.


Siehe auch

  • Hintergrund-Jobs — die andere Hälfte, um Requests schnell zu halten (Arbeit aufschieben, statt ihr Ergebnis zu cachen).
  • Sessions — ein serverseitiger Store, der auf Cache aufbaut.
  • MiddlewareCacheRateLimitLayer teilt einen Zähler über Replicas hinweg via Cache.
  • ORM-Cookbook — gecachte Reads aus einem post_save-Signal invalidieren.