InvalidationEngine
The InvalidationEngine provides tag-based cache invalidation: associate keys with tags, then invalidate all keys sharing a tag in a single operation.
Overview
TTL-based expiration handles routine staleness, but sometimes you need to invalidate cache entries immediately -- when a model is updated, a user's data changes, or a document is re-indexed. The InvalidationEngine solves this by letting you associate cache keys with arbitrary string tags and then bulk-remove all keys for a given tag.
The engine maintains a separate tag-to-key mapping in its own CacheBackend (typically an InMemoryBackend), keeping the tag index isolated from the primary cache namespace. This design means:
- Tag metadata does not pollute the primary cache.
- The tag store can use a different backend or TTL than the primary store.
- Tag lookups are fast key-value reads, not scans.
Usage
Standalone Usage
from omnicache_ai.backends.memory_backend import InMemoryBackend
from omnicache_ai.core.invalidation import InvalidationEngine
# Create a tag store and the engine
tag_store = InMemoryBackend()
engine = InvalidationEngine(tag_store=tag_store)
# Create a primary cache backend
cache = InMemoryBackend()
# Store some values in the primary cache
cache.set("resp:a1b2", "Answer about Python")
cache.set("resp:c3d4", "Answer about Java")
cache.set("resp:e5f6", "Answer about Rust")
# Register tags
engine.register("resp:a1b2", ["lang:python", "model:gpt-4"])
engine.register("resp:c3d4", ["lang:java", "model:gpt-4"])
engine.register("resp:e5f6", ["lang:rust", "model:claude-3"])
Invalidating by Tag
# Remove all entries tagged "model:gpt-4"
removed = engine.invalidate_tag("model:gpt-4", backend=cache)
print(f"Removed {removed} keys") # 2
# Verify
print(cache.exists("resp:a1b2")) # False
print(cache.exists("resp:c3d4")) # False
print(cache.exists("resp:e5f6")) # True (different tag)
Invalidating a Single Key
# Remove one specific key
engine.invalidate_key("resp:e5f6", backend=cache)
print(cache.exists("resp:e5f6")) # False
Through CacheManager
In practice, you rarely use InvalidationEngine directly. The CacheManager exposes tag registration via set() and invalidation via invalidate():
from omnicache_ai import CacheManager, OmnicacheSettings
manager = CacheManager.from_settings(OmnicacheSettings())
# Tags are registered automatically during set()
manager.set("key1", "value1", tags=["user:alice", "session:xyz"])
manager.set("key2", "value2", tags=["user:alice", "session:abc"])
manager.set("key3", "value3", tags=["user:bob", "session:xyz"])
# Invalidate all of Alice's cached data
removed = manager.invalidate("user:alice")
print(f"Removed {removed} entries") # 2
Use structured tags to organize your invalidation strategy:
user:{id}-- Invalidate when user data changesmodel:{name}-- Invalidate when switching or updating modelsdoc:{id}-- Invalidate when a source document is updatedsession:{id}-- Clear session-scoped cachesversion:{n}-- Invalidate across version bumps
How It Works
The engine stores tag-to-key mappings as JSON arrays in the tag store under keys prefixed with __tag__:.
__tag__:model:gpt-4 --> ["resp:a1b2", "resp:c3d4"]
__tag__:user:alice --> ["key1", "key2"]
When register() is called:
- The engine reads the existing key list for each tag.
- Appends the new key if it is not already present.
- Writes the updated list back to the tag store.
When invalidate_tag() is called:
- The engine reads the key list for the tag.
- Deletes each key from the primary cache backend.
- Removes the tag entry from the tag store.
- Returns the count of deleted keys.
The invalidate_key() method only deletes the key from the primary backend. It does not remove the key from any tag mappings. This is a lightweight operation for cases where you know the exact key to remove.
API Reference
Constructor
InvalidationEngine(tag_store: CacheBackend)
| Parameter | Type | Default | Description |
|---|---|---|---|
tag_store | CacheBackend | required | Backend used to persist tag-to-key mappings |
Methods
| Method | Signature | Returns | Description |
|---|---|---|---|
register | register(key, tags) | None | Associate a cache key with one or more tags |
invalidate_tag | invalidate_tag(tag, backend) | int | Remove all keys registered under a tag |
invalidate_key | invalidate_key(key, backend) | None | Remove a specific key from the primary backend |
Method Details
register(key, tags)
Associate a cache key with one or more string tags. If the key is already registered under a tag, it is not duplicated.
| Parameter | Type | Description |
|---|---|---|
key | str | The cache key to register |
tags | list[str] | List of tags to associate with the key |
engine.register("resp:abc123", ["user:alice", "model:gpt-4", "topic:science"])
Tags are additive. Calling register() multiple times for the same key with different tags adds to the set of tags; it does not replace them.
invalidate_tag(tag, backend)
Remove all cache keys that were registered under the given tag. Deletes the keys from the primary backend and cleans up the tag mapping.
| Parameter | Type | Description |
|---|---|---|
tag | str | The tag to invalidate |
backend | CacheBackend | The primary cache backend to delete keys from |
Returns: int -- the number of keys removed.
removed = engine.invalidate_tag("model:gpt-4", backend=cache)
If the tag does not exist or has no associated keys, the method returns 0 without error.
invalidate_key(key, backend)
Remove a specific key from the primary backend. This is a convenience method that delegates to backend.delete(key).
| Parameter | Type | Description |
|---|---|---|
key | str | The cache key to remove |
backend | CacheBackend | The primary cache backend |
engine.invalidate_key("resp:abc123", backend=cache)
Next Steps
- CacheManager -- Exposes invalidation through
set(tags=...)andinvalidate(tag) - Policies -- TTL-based expiration as a complement to manual invalidation
- Settings -- Configure the system via environment variables