armature-cache 0.3.0

Cache management for Armature framework with Redis and in-memory support
Documentation

armature-cache

Cache management for the Armature framework.

Features

  • Multiple backends — Redis (RedisCache), in-memory (InMemoryCache), and optional Memcached (MemcachedCache, behind the memcached feature)
  • TTL support — per-entry expiration, plus a configurable default TTL
  • Async API — non-blocking cache operations via the CacheStore trait
  • JSON values — the store works with JSON string payloads (get_json / set_json)
  • Batch primitivesget_many / set_many / delete_many, backed by native MGET / MSET / DEL on Redis
  • Multi-tier cachingTieredCache (L1 in-memory + L2 distributed) with live hit/miss/promotion stats
  • Tag-based invalidationTaggedCache invalidates groups of keys by tag

Installation

[dependencies]
armature-cache = "0.1"

The redis backend is enabled by default. Enable Memcached explicitly:

[dependencies]
armature-cache = { version = "0.1", features = ["memcached"] }

Quick Start (Redis)

use armature_cache::{CacheConfig, CacheStore, RedisCache};
use std::time::Duration;

#[tokio::main]
async fn main() -> Result<(), armature_cache::CacheError> {
    // Build a config, then connect.
    let config = CacheConfig::redis("redis://localhost:6379")?
        .with_key_prefix("myapp")
        .with_default_ttl(Duration::from_secs(300))
        .with_connection_timeout(Duration::from_secs(5))
        .with_operation_timeout(Duration::from_secs(3))
        .with_max_connections(10);
    let cache = RedisCache::new(config).await?;

    // Set a JSON value with an explicit TTL.
    cache
        .set_json("key", "\"value\"".to_string(), Some(Duration::from_secs(60)))
        .await?;

    // Get it back.
    let value: Option<String> = cache.get_json("key").await?;
    assert_eq!(value.as_deref(), Some("\"value\""));

    // Delete it.
    cache.delete("key").await?;

    Ok(())
}

connection_timeout, operation_timeout, and max_connections are applied to the Redis connection manager. An operation that exceeds operation_timeout fails with CacheError::Timeout.

In-Memory Cache

InMemoryCache implements the same CacheStore trait and is handy for tests or as the L1 tier. It is bounded (default DEFAULT_MAX_ENTRIES) and evicts expired entries lazily on read and eagerly when making room:

use armature_cache::{CacheStore, InMemoryCache};

# async fn example() -> Result<(), armature_cache::CacheError> {
let cache = InMemoryCache::new();
// or bound it explicitly: InMemoryCache::with_capacity(1_000)

cache.set_json("k", "\"v\"".to_string(), None).await?;
let v = cache.get_json("k").await?;
assert_eq!(v.as_deref(), Some("\"v\""));
# Ok(())
# }

Multi-Tier Caching

use armature_cache::{InMemoryCache, TieredCache};
use std::sync::Arc;

# async fn example() -> Result<(), armature_cache::CacheError> {
let l1 = Arc::new(InMemoryCache::new());
let l2 = Arc::new(InMemoryCache::new()); // typically a RedisCache
let tiered = TieredCache::new(l1, l2);

tiered.set("key", "\"value\"".to_string(), None).await?;
let value = tiered.get("key").await?; // L1, falling back to L2 (and promoting)

// Live counters, not just config echoes.
let stats = tiered.stats().await;
let _ = (stats.l1_hits, stats.l2_hits, stats.misses, stats.promotions);
# Ok(())
# }

Tag-Based Invalidation

use armature_cache::{InMemoryCache, TaggedCache};
use std::sync::Arc;

# async fn example() -> Result<(), armature_cache::CacheError> {
let tagged = TaggedCache::new(Arc::new(InMemoryCache::new()));

tagged
    .set_with_tags("user:123", "\"Alice\"".to_string(), &["users", "active"], None)
    .await?;

// Invalidate every key carrying any of these tags in a single batch delete.
tagged.invalidate_tags(&["users", "sessions"]).await?;
# Ok(())
# }

The tag index is persisted in the backing CacheStore itself (not a local, per-process map), so it is visible across every instance sharing that store. Only backends that override CacheStore::set_add/set_remove/set_members with a native set type (RedisCache, via SADD/SREM/SMEMBERS) update it atomically; other backends fall back to a non-atomic read-modify-write, which can race under concurrent tagging of the same tag from different instances.

Memcached (requires the memcached feature)

use armature_cache::{CacheConfig, CacheStore, MemcachedCache};

let config = CacheConfig::memcached("memcache://localhost:11211")?;
let cache = MemcachedCache::new(config).await?;
cache.set_json("k", "\"v\"".to_string(), None).await?;

Note: the memcached protocol exposes no way to read an item's remaining TTL, so MemcachedCache::ttl always returns Ok(None).

License

MIT OR Apache-2.0