Tag-based invalidation is a powerful feature in RunCache that allows you to group related cache entries and invalidate them together. This guide explains how to use tags effectively in your applications.
Understanding Tag-based Invalidation
Tag-based invalidation allows you to:
Group related cache entries under common tags
Invalidate multiple cache entries at once using a single tag
Create flexible grouping schemes independent of key naming
Implement complex invalidation strategies
Basic Usage
Adding Tags to Cache Entries
When setting a cache entry, you can assign one or more tags to it:
// Set a cache entry with tagsawaitRunCache.set({ key:'user:1:profile', value:JSON.stringify({ name:'John Doe', email:'john@example.com' }), tags: ['user:1','profile']});// Another entry with some overlapping tagsawaitRunCache.set({ key:'user:1:settings', value:JSON.stringify({ theme:'dark', notifications:true }), tags: ['user:1','settings']});// Entry for a different userawaitRunCache.set({ key:'user:2:profile', value:JSON.stringify({ name:'Jane Smith', email:'jane@example.com' }), tags: ['user:2','profile']});
In this example:
Both user:1:profile and user:1:settings are tagged with user:1
Both user:1:profile and user:2:profile are tagged with profile
user:1:settings is uniquely tagged with settings
Invalidating by Tag
To invalidate all cache entries with a specific tag:
// Invalidate all entries tagged with 'user:1'
RunCache.invalidateByTag('user:1');
This would invalidate both user:1:profile and user:1:settings, but not user:2:profile.
Similarly:
// Invalidate all profile entries
RunCache.invalidateByTag('profile');
This would invalidate both user:1:profile and user:2:profile, but not user:1:settings.
Tag Design Strategies
Hierarchical Tags
You can create hierarchical relationships using tag naming conventions:
// Invalidate all product data
RunCache.invalidateByTag('product');
// Invalidate all data for product 1
RunCache.invalidateByTag('product:1');
// Invalidate details for all products
RunCache.invalidateByTag('product:details');
// Invalidate all admin views when admin settings change
RunCache.invalidateByTag('admin-view');
// Invalidate all dashboards
RunCache.invalidateByTag('dashboard');
// Invalidate all cache entries from the previous version
RunCache.invalidateByTag('v1.2.3');
// Set new cache entries with the new version tag
await RunCache.set({
key: 'app:config',
value: JSON.stringify({ theme: 'dark', features: [...] }),
tags: ['config', 'v1.3.0']
});
Monitoring Tag Invalidations
RunCache provides events to monitor tag invalidations:
// Global tag invalidation event
RunCache.onTagInvalidation((event) => {
console.log(`Cache key ${event.key} was invalidated by tag: ${event.tag}`);
});
// Specific key tag invalidation
RunCache.onKeyTagInvalidation("user:1:profile", (event) => {
console.log(`User profile was invalidated by tag: ${event.tag}`);
});
// Pattern-based tag invalidation events
RunCache.onKeyTagInvalidation("user:*:profile", (event) => {
console.log(`User profile ${event.key} was invalidated by tag: ${event.tag}`);
});
Combining Tags with Other Features
Tags with TTL
Combine tags with TTL for time-based invalidation:
// Set cache entry with tags and TTL
await RunCache.set({
key: 'weather:nyc',
value: JSON.stringify({ temp: 72, conditions: 'sunny' }),
tags: ['weather', 'location:nyc'],
ttl: 3600000 // 1 hour
});
// Set cache entry with tags and TTL
await RunCache.set({
key: 'weather:la',
value: JSON.stringify({ temp: 85, conditions: 'clear' }),
tags: ['weather', 'location:la'],
ttl: 3600000 // 1 hour
});
This allows for both time-based expiration and manual invalidation:
// Invalidate all weather data regardless of TTL
RunCache.invalidateByTag('weather');
// Invalidate just NYC weather data
RunCache.invalidateByTag('location:nyc');
Tags with Source Functions
Combine tags with source functions for automatic refreshing:
// Option 1: Invalidate by dependency (cascades to dependents)
RunCache.invalidateByDependency('products');
// Option 2: Invalidate by tag (direct invalidation)
RunCache.invalidateByTag('product-data');
// Option 3: Invalidate just personalized content
RunCache.invalidateByTag('personalized');
Advanced Tag Patterns
Dynamic Tag Generation
Generate tags dynamically based on data or context:
function generateUserTags(user) {
const tags = [`user:${user.id}`];
if (user.role) {
tags.push(`role:${user.role}`);
}
if (user.subscriptionLevel) {
tags.push(`subscription:${user.subscriptionLevel}`);
}
if (user.region) {
tags.push(`region:${user.region}`);
}
return tags;
}
// Usage
const user = await fetchUser(1);
await RunCache.set({
key: `user:${user.id}:profile`,
value: JSON.stringify(user),
tags: generateUserTags(user)
});
Tag-based Prefetching
Use tags to implement prefetching strategies:
// Listen for tag invalidations
RunCache.onTagInvalidation((event) => {
if (event.tag === 'product-data') {
// Prefetch related data that might be needed soon
prefetchRelatedData();
}
});
async function prefetchRelatedData() {
// Prefetch in the background
const relatedData = await fetchRelatedData();
await RunCache.set({
key: 'related-products',
value: JSON.stringify(relatedData),
tags: ['product-data', 'related']
});
}
Conditional Tag Invalidation
Implement conditional tag invalidation logic:
async function conditionalInvalidate(tag, condition) {
if (await condition()) {
console.log(`Invalidating tag: ${tag}`);
RunCache.invalidateByTag(tag);
return true;
}
console.log(`Condition not met, tag ${tag} not invalidated`);
return false;
}
// Usage
await conditionalInvalidate('user-data', async () => {
const lastUpdate = await getLastUpdateTimestamp('user-data');
const threshold = Date.now() - (60 * 60 * 1000); // 1 hour ago
return lastUpdate < threshold;
});
// Good - focused, relevant tags
tags: ['user:1', 'profile', 'settings']
// Avoid - too many tags
tags: ['user:1', 'profile', 'settings', 'john', 'doe', 'admin', 'active', 'verified', 'english', 'premium']
3. Document Tag Schemas
Document your tag naming conventions and schemas:
/**
* Tag Schema:
* - entity:{entityType} - The type of entity (user, product, etc.)
* - {entityType}:{id} - Specific entity instance (user:1, product:xyz)
* - type:{dataType} - The type of data (profile, settings, etc.)
* - role:{roleName} - User role (admin, customer, etc.)
* - region:{regionCode} - Geographic region (us, eu, etc.)
* - version:{versionNumber} - Application version
*/
4. Avoid Overly Generic Tags
Be specific enough with tags to avoid unintended invalidations:
// Too generic - affects too many entries
tags: ['data']
// Better - more specific
tags: ['user-data']
// Best - most specific for the use case
tags: ['user:1:profile-data']
5. Consider Tag Cardinality
Be mindful of how many cache entries a tag might apply to:
// High cardinality - unique to one entry
tags: [`unique:${uuid()}`] // Avoid unless needed
// Medium cardinality - applies to a reasonable group
tags: [`user:${userId}`] // Good for user-specific data
// Low cardinality - applies to many entries
tags: ['global'] // Use carefully
Common Tag-based Invalidation Use Cases
1. User Data Invalidation
Cache and invalidate user-specific data:
// Store user data with tags
await RunCache.set({
key: `user:${userId}:profile`,
value: JSON.stringify(profile),
tags: [`user:${userId}`]
});
await RunCache.set({
key: `user:${userId}:preferences`,
value: JSON.stringify(preferences),
tags: [`user:${userId}`]
});
await RunCache.set({
key: `user:${userId}:activity`,
value: JSON.stringify(activity),
tags: [`user:${userId}`]
});
// When user data changes
function handleUserDataChange(userId) {
// Invalidate all data for this user
RunCache.invalidateByTag(`user:${userId}`);
}
2. Content Management
Cache and invalidate content by category:
// Store content with category tags
await RunCache.set({
key: `article:${articleId}`,
value: JSON.stringify(article),
tags: ['content', `category:${article.category}`, `author:${article.authorId}`]
});
// Invalidation scenarios
function handleCategoryUpdate(category) {
// Invalidate all content in this category
RunCache.invalidateByTag(`category:${category}`);
}
function handleAuthorUpdate(authorId) {
// Invalidate all content by this author
RunCache.invalidateByTag(`author:${authorId}`);
}
function handleGlobalContentUpdate() {
// Invalidate all content
RunCache.invalidateByTag('content');
}
3. API Response Caching
Cache API responses with appropriate tags:
async function cachedApiRequest(endpoint, params = {}, options = {}) {
// Generate a cache key based on the endpoint and params
const key = `api:${endpoint}:${JSON.stringify(params)}`;
// Define tags based on the endpoint and params
const tags = ['api'];
tags.push(`api:${endpoint}`);
if (params.userId) {
tags.push(`user:${params.userId}`);
}
if (params.category) {
tags.push(`category:${params.category}`);
}
// Check cache first
const cachedResponse = await RunCache.get(key);
if (cachedResponse) {
return JSON.parse(cachedResponse);
}
// If not in cache, make the API request
const response = await fetch(`https://api.example.com/${endpoint}`, {
method: 'POST',
body: JSON.stringify(params),
headers: { 'Content-Type': 'application/json' }
});
const data = await response.json();
// Cache the response with appropriate tags and TTL
await RunCache.set({
key,
value: JSON.stringify(data),
tags,
ttl: options.ttl || 300000 // 5 minutes default
});
return data;
}
// Usage
const userData = await cachedApiRequest('users/profile', { userId: 123 });
// When user data changes
function invalidateUserApiCache(userId) {
RunCache.invalidateByTag(`user:${userId}`);
}