Multi-Tenant Cache Invalidation
Use prefix and wildcard deletion for efficient cache invalidation in multi-tenant applications.
Basic Pattern
import { MemoryCache } from '@humanspeak/memory-cache'
const cache = new MemoryCache<unknown>({
maxSize: 10000,
ttl: 10 * 60 * 1000 // 10 minutes
})
// Cache keys follow pattern: tenant:{tenantId}:{resource}:{id}
function cacheForTenant<T>(tenantId: string, resource: string, id: string, value: T) {
cache.set(`tenant:${tenantId}:${resource}:${id}`, value)
}
function getForTenant<T>(tenantId: string, resource: string, id: string): T | undefined {
return cache.get(`tenant:${tenantId}:${resource}:${id}`) as T | undefined
}
// Invalidate all cache for a specific tenant
function invalidateTenantCache(tenantId: string): number {
return cache.deleteByPrefix(`tenant:${tenantId}:`)
}
// Invalidate specific resource type for a tenant
function invalidateTenantResource(tenantId: string, resource: string): number {
return cache.deleteByPrefix(`tenant:${tenantId}:${resource}:`)
}
// Invalidate all caches for a resource across tenants
function invalidateResourceGlobally(resource: string): number {
return cache.deleteByMagicString(`tenant:*:${resource}:*`)
}import { MemoryCache } from '@humanspeak/memory-cache'
const cache = new MemoryCache<unknown>({
maxSize: 10000,
ttl: 10 * 60 * 1000 // 10 minutes
})
// Cache keys follow pattern: tenant:{tenantId}:{resource}:{id}
function cacheForTenant<T>(tenantId: string, resource: string, id: string, value: T) {
cache.set(`tenant:${tenantId}:${resource}:${id}`, value)
}
function getForTenant<T>(tenantId: string, resource: string, id: string): T | undefined {
return cache.get(`tenant:${tenantId}:${resource}:${id}`) as T | undefined
}
// Invalidate all cache for a specific tenant
function invalidateTenantCache(tenantId: string): number {
return cache.deleteByPrefix(`tenant:${tenantId}:`)
}
// Invalidate specific resource type for a tenant
function invalidateTenantResource(tenantId: string, resource: string): number {
return cache.deleteByPrefix(`tenant:${tenantId}:${resource}:`)
}
// Invalidate all caches for a resource across tenants
function invalidateResourceGlobally(resource: string): number {
return cache.deleteByMagicString(`tenant:*:${resource}:*`)
}Usage Example
// Store data
cacheForTenant('acme', 'users', '123', { name: 'John' })
cacheForTenant('acme', 'users', '456', { name: 'Jane' })
cacheForTenant('acme', 'products', '789', { name: 'Widget' })
cacheForTenant('globex', 'users', '123', { name: 'Homer' })
// Invalidate all ACME user caches
invalidateTenantResource('acme', 'users')
// Invalidate all user caches globally
invalidateResourceGlobally('users')// Store data
cacheForTenant('acme', 'users', '123', { name: 'John' })
cacheForTenant('acme', 'users', '456', { name: 'Jane' })
cacheForTenant('acme', 'products', '789', { name: 'Widget' })
cacheForTenant('globex', 'users', '123', { name: 'Homer' })
// Invalidate all ACME user caches
invalidateTenantResource('acme', 'users')
// Invalidate all user caches globally
invalidateResourceGlobally('users')With Invalidation Logging
import { MemoryCache } from '@humanspeak/memory-cache'
const cache = new MemoryCache<unknown>({
maxSize: 10000,
ttl: 10 * 60 * 1000,
hooks: {
onDelete: ({ key, source }) => {
// Parse tenant from key
const match = key.match(/^tenant:([^:]+):/)
const tenantId = match?.[1] ?? 'unknown'
auditLog.info('Cache invalidated', {
key,
tenantId,
source
})
}
}
})import { MemoryCache } from '@humanspeak/memory-cache'
const cache = new MemoryCache<unknown>({
maxSize: 10000,
ttl: 10 * 60 * 1000,
hooks: {
onDelete: ({ key, source }) => {
// Parse tenant from key
const match = key.match(/^tenant:([^:]+):/)
const tenantId = match?.[1] ?? 'unknown'
auditLog.info('Cache invalidated', {
key,
tenantId,
source
})
}
}
})Key Considerations
- Key Pattern: Use consistent, hierarchical patterns
- Prefix vs Wildcard: Prefix is faster; use wildcards for complex patterns
- Batch Size: Large deletions may briefly block; consider chunking