Skip to main content

TaggedCache

Struct TaggedCache 

Source
pub struct TaggedCache<C: CacheStore> { /* private fields */ }
Expand description

Cache with tag-based invalidation support.

The tag -> member-key index (and its reverse, key -> tags) is persisted in the backing CacheStore itself under reserved keys — it is NOT kept in a local, per-process map. That means the index is visible to every instance sharing the same backing store (e.g. every app process pointed at the same Redis), so a key tagged by one instance can be looked up and invalidated by another.

§Atomicity caveat (concurrent race)

Index updates go through CacheStore::set_add / CacheStore::set_remove / CacheStore::set_members. Only backends that override these with a native set type — RedisCache does, via SADD/SREM/SMEMBERS — update the index atomically (see CacheStore::supports_atomic_sets). Other backends (e.g. InMemoryCache, MemcachedCache) fall back to the trait’s default, non-atomic read-modify-write, so concurrent set_with_tags/invalidate_tag calls against the SAME tag from different instances can race and lose an update. For distributed deployments, wrap a RedisCache (or another backend that overrides the set primitives) if you need that guarantee. Self::new logs a warning once, at construction time, when the backing store doesn’t support atomic sets.

§Partial-failure caveat (sequential, non-transactional updates)

Separately from the concurrent-race caveat above: Self::set_with_tags, Self::delete, and Self::invalidate_tags each issue several independent set_add/set_remove/delete calls in sequence. If an early call in one of these sequences succeeds and a later one fails, the tag index (and its reverse index) can end up partially updated — inconsistent with the cached value, or inconsistent with itself (e.g. a key’s reverse-index tag set can end up not matching the forward tag -> keys sets it’s actually a member of). There is currently no automatic rollback or reconciliation for this case: a failed call may need to be retried or the affected tag(s)/key(s) reconciled manually. A best-effort warning is logged when this happens (see the calls guarded by warn_on_partial_failure in the implementation) so operators at least get a signal, but the index itself is not repaired automatically.

§Reserved key namespace

The tag index’s bookkeeping keys (tag_set_key/key_tags_set_key/ TAG_INDEX_KEY) all begin with the reserved prefix "__armature_" and live in the SAME keyspace as caller-supplied keys — both go through the same backing CacheStore, with only key_prefix (from CacheConfig) applied identically to both. A caller-supplied key that happens to start with "__armature_" would therefore collide with this reserved bookkeeping keyspace (e.g. writing to __armature_tag__:users would clobber the “users” tag’s member set). This prefix is forbidden for caller-supplied keys: Self::set_with_tags, Self::get, and Self::delete all reject a key starting with "__armature_" with a CacheError::Config, rather than silently allowing the collision.

Implementations§

Source§

impl<C: CacheStore> TaggedCache<C>

Source

pub fn new(cache: Arc<C>) -> Self

Create new tagged cache

Checks CacheStore::supports_atomic_sets on cache and logs a warning once, here at construction time, when the backing store does NOT support atomic sets — see the struct-level “Atomicity caveat” docs for what that means for concurrent tag-index updates.

§Examples
use armature_cache::*;

let cache = RedisCache::new(config).await?;
let tagged = TaggedCache::new(Arc::new(cache));
Source

pub async fn set_with_tags( &self, key: &str, value: String, tags: &[&str], ttl: Option<Duration>, ) -> CacheResult<()>

Set a value with tags

A repeated call for the same key REPLACES its tag membership with tags (it does not union with whatever tags the key carried before).

§TTL and the tag index

ttl applies to the value, and is mirrored onto the key’s reverse index (key -> tags) so that bookkeeping disappears along with the value instead of outliving it forever.

The forward index (tag -> keys) cannot carry the same TTL: one tag set holds many keys with independent lifetimes, so expiring the set would drop the memberships of keys that are still alive. An expired key therefore remains a member of its tags until it is noticed, and Self::get_keys_by_tag reconciles that on read — it filters out members whose value is gone and prunes them from the tag set, so tag sets do not grow monotonically and stale keys are never returned.

§Examples
tagged.set_with_tags(
    "user:123",
    user_json,
    &["users", "active-users"],
    Some(Duration::from_secs(3600)),
).await?;
Source

pub async fn get(&self, key: &str) -> CacheResult<Option<String>>

Get value from cache

Source

pub async fn delete(&self, key: &str) -> CacheResult<()>

Delete a specific key

Source

pub async fn invalidate_tag(&self, tag: &str) -> CacheResult<()>

Invalidate all keys with a specific tag

§Examples
// Invalidate all user-related cache entries
tagged.invalidate_tag("users").await?;
Source

pub async fn invalidate_tags(&self, tags: &[&str]) -> CacheResult<()>

Invalidate all keys with any of the specified tags

§Examples
// Invalidate all user and session data
tagged.invalidate_tags(&["users", "sessions"]).await?;
Source

pub async fn get_keys_by_tag(&self, tag: &str) -> CacheResult<Vec<String>>

Get all keys currently tagged with tag.

Members whose value is no longer present — most commonly because the value’s TTL elapsed — are reconciled away rather than returned: the forward index (tag -> keys) cannot carry the value’s TTL (one tag set spans many keys with independent lifetimes), so an expired key would otherwise stay a member of every one of its tags forever, growing the tag sets monotonically and handing callers keys that no longer exist.

Reconciliation is best-effort: if pruning the stale members fails, the live keys are still returned and the prune is retried on the next read.

Source

pub async fn get_tags_for_key(&self, key: &str) -> CacheResult<Vec<String>>

Get all tags for a specific key

Source

pub async fn list_tags(&self) -> CacheResult<Vec<String>>

Get all registered tags (tags currently carrying at least one member).

Trait Implementations§

Source§

impl<C: CacheStore> Clone for TaggedCache<C>

Source§

fn clone(&self) -> Self

Returns a duplicate of the value. Read more
1.0.0 (const: unstable) · Source§

fn clone_from(&mut self, source: &Self)

Performs copy-assignment from source. Read more

Auto Trait Implementations§

§

impl<C> Freeze for TaggedCache<C>

§

impl<C> RefUnwindSafe for TaggedCache<C>
where C: RefUnwindSafe,

§

impl<C> Send for TaggedCache<C>

§

impl<C> Sync for TaggedCache<C>

§

impl<C> Unpin for TaggedCache<C>

§

impl<C> UnsafeUnpin for TaggedCache<C>

§

impl<C> UnwindSafe for TaggedCache<C>
where C: RefUnwindSafe,

Blanket Implementations§

Source§

impl<T> Any for T
where T: 'static + ?Sized,

Source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
Source§

impl<T> Borrow<T> for T
where T: ?Sized,

Source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
Source§

impl<T> BorrowMut<T> for T
where T: ?Sized,

Source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
Source§

impl<T> CloneToUninit for T
where T: Clone,

Source§

unsafe fn clone_to_uninit(&self, dest: *mut u8)

🔬This is a nightly-only experimental API. (clone_to_uninit)
Performs copy-assignment from self to dest. Read more
Source§

impl<T> From<T> for T

Source§

fn from(t: T) -> T

Returns the argument unchanged.

Source§

impl<T, U> Into<U> for T
where U: From<T>,

Source§

fn into(self) -> U

Calls U::from(self).

That is, this conversion is whatever the implementation of From<T> for U chooses to do.

Source§

impl<T> ToOwned for T
where T: Clone,

Source§

type Owned = T

The resulting type after obtaining ownership.
Source§

fn to_owned(&self) -> T

Creates owned data from borrowed data, usually by cloning. Read more
Source§

fn clone_into(&self, target: &mut T)

Uses borrowed data to replace owned data, usually by cloning. Read more
Source§

impl<T, U> TryFrom<U> for T
where U: Into<T>,

Source§

type Error = Infallible

The type returned in the event of a conversion error.
Source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
Source§

impl<T, U> TryInto<U> for T
where U: TryFrom<T>,

Source§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
Source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.