rainier-cache 1.0.1

A cache port for Rainier, with memory, Redis, Redis Cluster and Memcached drivers.
Documentation
rainier-cache-1.0.1 has been yanked.

rainier-cache

A [Cache] port and its drivers — Laravel's Cache, with the same shape and the same drivers you would expect.

use rainier_cache::{Cache, CacheExt, MemoryCache};
use std::time::Duration;

# #[tokio::main] async fn main() -> rainier_support::Result<()> {
let cache = MemoryCache::new();

cache.put_string("greeting", "hello", Some(Duration::from_secs(60))).await?;
assert_eq!(cache.get_string("greeting").await?.as_deref(), Some("hello"));

// A counter, incremented atomically.
assert_eq!(cache.increment("hits", 1).await?, 1);
# Ok(()) }

Drivers

Driver Feature Shared between instances
[MemoryCache] — no
RedisCache redis-driver yes
RedisCache on a cluster redis-cluster yes, sharded
MemcachedCache memcached yes
DynamoDbCache dynamodb yes, and no server to run

The transports live in rainier-drivers rather than here, because Redis is wanted by the queue too and an application should configure it once.

Needs the matching feature, so this one is not compiled as a doctest:

// One server…
let cache = RedisCache::connect(RedisConnector::open("redis://127.0.0.1/")?).await?;

// …or a sharded cluster, with no change to anything downstream.
let cache = RedisCache::connect(
    RedisConnector::open_cluster(["redis://10.0.0.1:6379", "redis://10.0.0.2:6379"])?,
).await?;

A miss is not a failure

get returns Ok(None) for an absent key and Err only when the cache could not be reached. Conflating the two is how a cache outage becomes an application outage — and a cache is the one dependency an application should be able to lose.

Driver errors are ServiceUnavailable, so they land in the right bucket on a dashboard rather than looking like bugs.

Namespace a shared cache

Two applications caching user:1 on one Redis database read each other's values, and the symptom is a user seeing another application's data. [PrefixedCache] fixes it, and makes "flush mine" refuse rather than empty the server:

# use rainier_cache::{Cache, MemoryCache, PrefixedCache};
# use std::sync::Arc;
let shared: Arc<dyn Cache> = Arc::new(MemoryCache::new());
let mine = PrefixedCache::new(shared, "billing");

What not to cache in memory

A [MemoryCache] is per-process, so anything cached for correctness rather than for speed is wrong in it: a rate-limit counter becomes N × limit across N instances, and a lock is not a lock.

[Cache::add] and [Cache::forget_if] are atomic on every driver, so a [Lock] over a shared one is a real lock. Over a MemoryCache it is a real lock within this process and nothing at all between two — [LockManager::is_shared] is the check worth making at boot.