Skip to content

Two-Tier Caching

NestJS RedisX implements a two-tier cache architecture that balances speed, consistency, and memory usage.

Architecture Overview

LayerLocationLatencyCapacityConsistency
L1Process memory~0.01msLimited (MB)Per-instance
L2Redis~1msLarge (GB)Shared

When to Use Each Layer

L1 Only (In-Memory)

Best for:

  • Extremely hot data (>100 reads/sec per key)
  • Small, stable datasets (config, feature flags)
  • Data where slight staleness is acceptable
typescript
new CachePlugin({
  l1: { maxSize: 1000, ttl: 60000 },
  l2: { enabled: false },
})

L2 Only (Redis)

Best for:

  • Large datasets that don't fit in memory
  • Data requiring cross-instance consistency
  • Session data, user profiles
typescript
new CachePlugin({
  l1: { enabled: false },
  l2: { defaultTtl: 3600 },
})

Best for:

  • General-purpose caching
  • Balancing speed and consistency
  • Most production workloads
typescript
new CachePlugin({
  l1: { maxSize: 1000, ttl: 30000 },
  l2: { defaultTtl: 3600 },
})

Decision Matrix

FactorUse L1Use L2Use Both
Read frequencyVery highLow-mediumMedium-high
Data sizeSmallLargeAny
Staleness toleranceHighLowMedium
Cross-instance consistencyNot neededRequiredEventually consistent
Memory constraintsPlentyLimitedBalanced

Consistency Model

With two-tier caching, consistency is eventual:

Time window for stale reads: Pub/sub propagation delay (~1-10ms)

L1 Invalidation Strategies

Cross-instance invalidation via Redis pub/sub:

typescript
new CachePlugin({
  l1: {
    maxSize: 1000,
    ttl: 30000,
    invalidation: 'pubsub', // Default
  },
})

TTL Only

No cross-instance invalidation. L1 expires naturally:

typescript
new CachePlugin({
  l1: {
    maxSize: 1000,
    ttl: 5000, // Short TTL for freshness
    invalidation: 'none',
  },
})

Disable L1

When consistency is critical:

typescript
new CachePlugin({
  l1: { enabled: false },
})

Memory Management

L1 Eviction

When L1 reaches maxSize, oldest entries are evicted (LRU):

typescript
l1: {
  maxSize: 1000,    // Max entries
  maxMemory: '50mb', // Or max memory
}

Sizing Guidelines

Application SizeL1 maxSizeL1 TTL
Small (<10 instances)1000-500030-60s
Medium (10-50 instances)500-200015-30s
Large (>50 instances)100-5005-15s

Memory Calculation

Estimate: entries × avg_value_size × 1.5 (overhead)

Example: 1000 entries × 1KB × 1.5 = ~1.5MB per instance

When NOT to Cache

ScenarioReason
User-specific data with high cardinalityL1 thrashing
Real-time data (stock prices)Staleness unacceptable
Write-heavy workloadsInvalidation overhead
Security-sensitive dataCache timing attacks

Monitoring

Key metrics to watch:

MetricHealthyInvestigate
L1 hit rate>80%<50%
L2 hit rate>70%<40%
L1 eviction rateLowIncreasing
Invalidation lag<10ms>100ms

Next Steps

Released under the MIT License.