logo

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