Dependency Tracking
Dependency tracking is an advanced feature in RunCache that allows you to establish relationships between cache entries. When a cache entry changes or is invalidated, all dependent entries are automatically invalidated as well. This guide explains how to use dependency tracking effectively in your applications.
Understanding Dependency Tracking
Dependency tracking allows you to:
Define relationships between cache entries
Automatically invalidate dependent entries when a parent entry changes
Create complex dependency chains with multi-level relationships
Maintain data consistency across related cache entries
This is particularly useful for:
Derived data that depends on base data
Composite views that depend on multiple data sources
Hierarchical data structures
Ensuring consistency in complex caching scenarios
Setting Up Dependencies
To establish a dependency relationship, use the dependencies
parameter when setting a cache entry:
// Primary data
await RunCache.set({
key: 'user:1:profile',
value: JSON.stringify({ name: 'John Doe', email: 'john@example.com' })
});
// Dependent data
await RunCache.set({
key: 'user:1:dashboard',
value: JSON.stringify({ widgets: [...] }),
dependencies: ['user:1:profile'] // This entry depends on the profile
});
In this example:
user:1:dashboard
depends onuser:1:profile
If
user:1:profile
is updated or invalidated,user:1:dashboard
will be automatically invalidated
Multi-Level Dependencies
RunCache supports multi-level dependency chains:
// Primary data
await RunCache.set({
key: 'user:1:profile',
value: JSON.stringify({ name: 'John Doe' })
});
// First-level dependent data
await RunCache.set({
key: 'user:1:recommendations',
value: JSON.stringify([...]),
dependencies: ['user:1:profile']
});
// Second-level dependent data
await RunCache.set({
key: 'home:feed',
value: JSON.stringify([...]),
dependencies: ['user:1:recommendations']
});
In this example:
When
user:1:profile
changes,user:1:recommendations
is invalidatedWhen
user:1:recommendations
is invalidated (directly or via dependency),home:feed
is also invalidatedThis creates a cascading invalidation effect
Multiple Dependencies
A cache entry can depend on multiple other entries:
// Primary data sources
await RunCache.set({ key: 'products', value: JSON.stringify([...]) });
await RunCache.set({ key: 'categories', value: JSON.stringify([...]) });
await RunCache.set({ key: 'user:1:preferences', value: JSON.stringify({...}) });
// Dependent data that relies on multiple sources
await RunCache.set({
key: 'user:1:product-recommendations',
value: JSON.stringify([...]),
dependencies: ['products', 'categories', 'user:1:preferences']
});
In this example:
user:1:product-recommendations
depends on three different cache entriesIf any of those entries change, the recommendations will be invalidated
Checking Dependencies
You can check if one cache entry depends on another (directly or indirectly) using the isDependencyOf
method:
// Check if one entry depends on another
const isDependency = await RunCache.isDependencyOf('home:feed', 'user:1:profile');
console.log(isDependency); // true (because of the multi-level dependency)
// Check a non-dependent relationship
const isNotDependency = await RunCache.isDependencyOf('products', 'categories');
console.log(isNotDependency); // false (no dependency relationship)
Manual Dependency Invalidation
You can manually invalidate a cache entry and all its dependents using the invalidateByDependency
method:
// Invalidate an entry and all its dependents
RunCache.invalidateByDependency('user:1:profile');
This will:
Invalidate the specified entry (
user:1:profile
)Invalidate all entries that directly depend on it (
user:1:recommendations
)Invalidate all entries that depend on those entries (
home:feed
)And so on, following the dependency chain
Monitoring Dependency Invalidations
RunCache provides events to monitor dependency invalidations:
// Global dependency invalidation event
RunCache.onDependencyInvalidation((event) => {
console.log(`Cache entry ${event.key} was invalidated due to dependency on: ${event.dependencyKey}`);
});
// Specific key dependency invalidation
RunCache.onKeyDependencyInvalidation("user:1:dashboard", (event) => {
console.log(`Dashboard cache was invalidated due to dependency on: ${event.dependencyKey}`);
});
// Pattern-based dependency invalidation events
RunCache.onKeyDependencyInvalidation("home:*", (event) => {
console.log(`Home page component ${event.key} was invalidated due to dependency on: ${event.dependencyKey}`);
});
These events can be useful for:
Logging and monitoring
Triggering additional actions when dependencies are invalidated
Debugging complex dependency chains
Combining Dependencies with Source Functions
Dependencies work particularly well with source functions and automatic refetching:
// Primary data with source function
await RunCache.set({
key: 'user:1:profile',
sourceFn: async () => {
const response = await fetch('https://api.example.com/users/1');
const data = await response.json();
return JSON.stringify(data);
},
ttl: 300000, // 5 minutes
autoRefetch: true
});
// Dependent data with source function
await RunCache.set({
key: 'user:1:recommendations',
sourceFn: async () => {
// Get the user profile from cache
const profileJson = await RunCache.get('user:1:profile');
const profile = JSON.parse(profileJson);
// Use profile data to fetch recommendations
const response = await fetch(`https://api.example.com/recommendations?preferences=${profile.preferences.join(',')}`);
const recommendations = await response.json();
return JSON.stringify(recommendations);
},
dependencies: ['user:1:profile'],
ttl: 600000, // 10 minutes
autoRefetch: true
});
In this example:
When
user:1:profile
is refreshed (automatically or manually), its dependents are invalidatedWhen
user:1:recommendations
is next accessed, its source function will be calledThe source function will use the fresh profile data to generate new recommendations
This creates a powerful pattern for maintaining consistency while still benefiting from caching.
Dependency Best Practices
1. Design Dependency Hierarchies Carefully
Plan your dependency relationships to reflect logical data dependencies:
// Good - logical dependency hierarchy
await RunCache.set({ key: 'products', value: '...' });
await RunCache.set({ key: 'product:1:details', value: '...', dependencies: ['products'] });
await RunCache.set({ key: 'product:1:reviews', value: '...', dependencies: ['product:1:details'] });
// Avoid - circular dependencies
await RunCache.set({ key: 'user:1:friends', value: '...', dependencies: ['user:1:profile'] });
await RunCache.set({ key: 'user:1:profile', value: '...', dependencies: ['user:1:friends'] }); // Circular!
2. Avoid Deep Dependency Chains
Deep dependency chains can lead to widespread invalidations:
// Potentially problematic - deep dependency chain
A → B → C → D → E → F → G
// Better - flatter dependency structure
A → B, C, D
E → F, G
Consider using tags for broader grouping when appropriate.
3. Be Specific with Dependencies
Define dependencies as specifically as possible:
// Too broad - invalidates too much
await RunCache.set({
key: 'user:1:dashboard',
value: '...',
dependencies: ['global:data'] // Very broad dependency
});
// Better - more specific dependencies
await RunCache.set({
key: 'user:1:dashboard',
value: '...',
dependencies: ['user:1:profile', 'user:1:preferences'] // Only what's needed
});
4. Combine with Tags for Complex Scenarios
For complex invalidation needs, combine dependencies with tags:
// Set up with both dependencies and tags
await RunCache.set({
key: 'user:1:feed',
value: '...',
dependencies: ['user:1:profile', 'user:1:friends'],
tags: ['user:1', 'feed']
});
// Different invalidation options:
// 1. Invalidate when profile changes (automatic via dependency)
// 2. Invalidate all user:1 data (via tag)
RunCache.invalidateByTag('user:1');
// 3. Invalidate all feeds (via tag)
RunCache.invalidateByTag('feed');
5. Monitor Invalidation Patterns
Use events to monitor and understand how your dependencies behave:
// Track dependency invalidations
RunCache.onDependencyInvalidation((event) => {
console.log(`${event.key} invalidated due to ${event.dependencyKey}`);
// Track metrics about invalidation frequency
trackMetric('cache.dependency.invalidation', {
key: event.key,
dependency: event.dependencyKey
});
});
Advanced Dependency Patterns
1. Dynamic Dependencies
Create dependencies dynamically based on content:
async function cacheArticleWithRelatedContent(articleId) {
// Fetch and cache the article
const article = await fetchArticle(articleId);
await RunCache.set({
key: `article:${articleId}`,
value: JSON.stringify(article)
});
// Determine related content IDs from the article
const relatedIds = article.relatedArticles;
// Cache related content with dependencies
for (const relatedId of relatedIds) {
const relatedArticle = await fetchArticle(relatedId);
await RunCache.set({
key: `article:${relatedId}`,
value: JSON.stringify(relatedArticle),
dependencies: [`article:${articleId}`] // Dynamic dependency
});
}
}
2. Dependency-Aware Refresh
Implement smart refreshing that considers dependencies:
async function smartRefresh(key) {
// First, check if this key has dependencies
const allKeys = await RunCache.get('*'); // Get all cache keys
const dependencyKeys = [];
for (const cacheKey of allKeys) {
if (await RunCache.isDependencyOf(key, cacheKey)) {
dependencyKeys.push(cacheKey);
}
}
// Refresh dependencies first (from furthest to closest)
for (const depKey of dependencyKeys.reverse()) {
await RunCache.refetch(depKey);
}
// Then refresh the target key
await RunCache.refetch(key);
}
3. Conditional Dependencies
Implement conditional dependency relationships:
// Check if we should establish a dependency
const shouldDependOn = await checkCondition();
await RunCache.set({
key: 'conditional-data',
value: '...',
dependencies: shouldDependOn ? ['base-data'] : [] // Conditional dependency
});
Next Steps
Now that you understand dependency tracking, explore these related topics:
Tag-based Invalidation - Another way to group and invalidate related cache entries
Event System - Learn more about monitoring cache events
Source Functions - Understand how to generate cache values dynamically
Resource Management - Learn about memory management and cleanup
Last updated