Tagging¶
The tagging system in Nicolas Cache provides a powerful way to organize and manage cache entries. Tags allow you to group related cache items and perform bulk operations on them.
Basic Tag Operations¶
Retrieving by Tag¶
# Get all entries with a specific tag
all_users = cache.get_by_tag("users")
# Returns: {"user:1": {...}, "user:2": {...}}
# Process tagged entries
for key, value in cache.get_by_tag("active").items():
print(f"Active user {key}: {value['name']}")
Deleting by Tag¶
# Delete all entries with a tag
deleted_count = cache.delete_by_tag("temporary")
print(f"Deleted {deleted_count} temporary entries")
# Clear all user sessions
cache.delete_by_tag("sessions")
Tag Naming Strategies¶
Advanced Patterns¶
Cache Invalidation Strategies¶
class ProductCache:
def __init__(self, cache):
self.cache = cache
def set_product(self, product):
"""Cache product with comprehensive tagging."""
self.cache.set(
f"product:{product['id']}",
product,
tags=[
"products",
f"category:{product['category_id']}",
f"brand:{product['brand_id']}",
f"price_range:{self._get_price_range(product['price'])}",
"available" if product['stock'] > 0 else "out_of_stock"
]
)
def invalidate_category(self, category_id):
"""Invalidate all products in a category."""
return self.cache.delete_by_tag(f"category:{category_id}")
def invalidate_brand(self, brand_id):
"""Invalidate all products from a brand."""
return self.cache.delete_by_tag(f"brand:{brand_id}")
def _get_price_range(self, price):
if price < 100:
return "budget"
elif price < 500:
return "mid"
else:
return "premium"
Dependency Tracking¶
class DependencyCache:
def __init__(self, cache):
self.cache = cache
def cache_with_dependencies(self, key, value, depends_on):
"""Cache with dependency tracking."""
tags = ["cached_values"]
# Add dependency tags
for dep in depends_on:
tags.append(f"depends:{dep}")
self.cache.set(key, value, tags=tags)
def invalidate_dependents(self, dependency):
"""Invalidate all entries depending on something."""
return self.cache.delete_by_tag(f"depends:{dependency}")
# Usage
dep_cache = DependencyCache(cache)
# Cache computed value that depends on user and config
dep_cache.cache_with_dependencies(
"computed:result",
compute_result(),
depends_on=["user:123", "config:main"]
)
# When user changes, invalidate dependent caches
dep_cache.invalidate_dependents("user:123")
Multi-Tenant Caching¶
class TenantCache:
def __init__(self, cache, tenant_id):
self.cache = cache
self.tenant_id = tenant_id
def set(self, key, value, tags=None):
"""Set with tenant isolation."""
tags = tags or []
tags.append(f"tenant:{self.tenant_id}")
full_key = f"tenant:{self.tenant_id}:{key}"
self.cache.set(full_key, value, tags=tags)
def get(self, key):
"""Get with tenant isolation."""
full_key = f"tenant:{self.tenant_id}:{key}"
return self.cache.get(full_key)
def clear_tenant_cache(self):
"""Clear all cache for this tenant."""
return self.cache.delete_by_tag(f"tenant:{self.tenant_id}")
# Usage
tenant_cache = TenantCache(cache, tenant_id="acme_corp")
tenant_cache.set("config", config_data)
tenant_cache.clear_tenant_cache() # Clear all Acme Corp cache
Tag Performance¶
Performance Considerations¶
Number of Tags: More tags per entry means more bookkeeping
Tag Cardinality: Unique tags for every entry defeats the purpose
Tag Names: Shorter tag names use less memory
# Good - reasonable number of meaningful tags
cache.set("user:123", data, tags=["users", "active", "premium"])
# Bad - too many unique tags
cache.set("user:123", data, tags=[
f"user:{id}",
f"timestamp:{time.time()}", # Unique for every entry!
f"random:{uuid.uuid4()}" # Defeats grouping purpose
])
Memory Overhead¶
Each tag maintains a set of keys in Redis:
# Redis memory usage example
cache:tag:users -> {user:1, user:2, ..., user:1000} # ~8KB for 1000 users
cache:key_tags:user:1 -> {users, active, premium} # ~100 bytes per key
Best Practices¶
Use Consistent Naming
# Good - consistent format
tags = [
"type:user",
"status:active",
"role:admin"
]
# Bad - inconsistent
tags = [
"user-type",
"active_status",
"AdminRole"
]
Plan Your Tag Hierarchy
# Well-planned hierarchy
tags = [
"content",
"content:articles",
"content:articles:published",
"author:john",
"category:tech"
]
Avoid Over-Tagging
# Appropriate tagging
cache.set("session:123", session, tags=["sessions", f"user:{user_id}"])
# Over-tagging
cache.set("session:123", session, tags=[
"sessions", "active_sessions", "user_sessions",
"temporary", "data", "cache", "storage" # Too generic
])
Document Your Tagging Schema
"""
Tagging Schema:
- users: All user-related data
- user:{id}: Specific user's data
- org:{id}: Organization-specific data
- temp: Temporary data (cleared daily)
- config: Configuration data
- api: API response cache
"""
Regular Cleanup
# Scheduled cleanup of temporary data
def daily_cleanup():
cache.delete_by_tag("temp")
cache.delete_by_tag(f"date:{yesterday}")
Tag Implementation Details¶
The tagging system is implemented differently in each backend:
Memory Backend:
Tags stored in Python dictionaries
O(1) tag addition/removal
O(n) retrieval by tag
Redis Backend:
Tags stored as Redis sets
Atomic tag operations
Efficient set operations for retrieval
Data Structure:
# Conceptual structure
cache_entries = {
"user:1": {value: {...}, tags: {"users", "active"}},
"user:2": {value: {...}, tags: {"users", "inactive"}}
}
tag_index = {
"users": {"user:1", "user:2"},
"active": {"user:1"},
"inactive": {"user:2"}
}