Guides5 min read

Understanding SonicJS Three-Tiered Caching Strategy

Learn how SonicJS implements a three-tiered caching strategy using memory, Cloudflare KV, and D1 to deliver sub-15ms response times globally.

SonicJS Team

Three-tiered caching architecture visualization showing memory cache, KV storage, and database layers

Understanding SonicJS Three-Tiered Caching Strategy

**TL;DR** β€” SonicJS uses a three-tiered caching system: Memory (sub-1ms), KV (5-15ms), and D1 (20-50ms). Content is checked in order from fastest to slowest, with automatic population of faster tiers on cache misses.

Key Stats:

  • Memory cache: Under 1ms access time
  • KV cache: 5-15ms globally distributed
  • 90-95% cache hit rate for well-configured sites
  • Only 5-10% of requests hit the D1 database

Performance is at the heart of SonicJS. To deliver consistent sub-15ms response times globally, SonicJS implements a sophisticated three-tiered caching strategy. This guide explains how it works and how to configure it for your needs.

The Three Tiers

SonicJS caching operates across three layers:

Request β†’ Memory Cache β†’ KV Cache β†’ D1 Database
              ↓              ↓           ↓
           <1ms           5-15ms      20-50ms

Tier 1: Memory Cache

The fastest layer, storing frequently accessed content in V8 isolate memory.

Characteristics:

  • Speed: Sub-millisecond access
  • Scope: Per-isolate (not shared across data centers)
  • Lifetime: Duration of the isolate (typically minutes)
  • Size: Limited by isolate memory

Tier 2: KV Cache

Cloudflare KV provides globally distributed, persistent caching.

Characteristics:

  • Speed: 5-15ms globally
  • Scope: Shared across all edge locations
  • Lifetime: Configurable TTL
  • Size: Up to 25MB per value

Tier 3: D1 Database

The source of truth, providing SQLite at the edge.

Characteristics:

  • Speed: 20-50ms
  • Scope: Replicated globally
  • Lifetime: Persistent
  • Size: 2GB+ per database

How Caching Works

Read Path

When a request comes in:

async function getContent(key: string) {
  // 1. Check memory cache first
  const memoryResult = memoryCache.get(key)
  if (memoryResult) {
    return memoryResult // <1ms
  }

  // 2. Check KV cache
  const kvResult = await env.CACHE.get(key, 'json')
  if (kvResult) {
    memoryCache.set(key, kvResult) // Populate memory
    return kvResult // 5-15ms
  }

  // 3. Query D1 database
  const dbResult = await db.query(...)
  if (dbResult) {
    await env.CACHE.put(key, JSON.stringify(dbResult), { ttl })
    memoryCache.set(key, dbResult)
    return dbResult // 20-50ms
  }

  return null
}

Write Path

When content is updated:

async function updateContent(key: string, data: any) {
  // 1. Update D1 database
  await db.update(...)

  // 2. Invalidate KV cache
  await env.CACHE.delete(key)

  // 3. Memory cache expires naturally
  // (or explicitly invalidated if supported)
}

Configuration Options

Basic Setup

import { createSonicJS } from '@sonicjs-cms/core'
import { cachePlugin } from '@sonicjs-cms/core/plugins'

const cms = createSonicJS({
  plugins: [
    cachePlugin({
      defaultTTL: 3600, // 1 hour default
    }),
  ],
})

Route-Specific TTLs

cachePlugin({
  defaultTTL: 3600,
  patterns: {
    // Frequently updated content
    '/api/content/posts': { ttl: 60 },

    // Rarely changed content
    '/api/content/categories': { ttl: 86400 },

    // Individual posts can cache longer
    '/api/content/posts/*': { ttl: 3600 },

    // Never cache user-specific data
    '/api/users/*': { ttl: 0 },
  },
})

Cache Headers

SonicJS automatically sets appropriate cache headers:

cachePlugin({
  headers: {
    'Cache-Control': 'public, max-age=3600, s-maxage=86400',
    'CDN-Cache-Control': 'max-age=86400',
  },
})

Cache Invalidation Strategies

Time-Based (TTL)

The simplest approach - content expires after a set time:

{ ttl: 300 } // Cache for 5 minutes

Tag-Based

Invalidate groups of related content:

// When saving a post
await cache.tag(postId, ['posts', `category:${categoryId}`])

// Invalidate all posts in a category
await cache.invalidateTag(`category:${categoryId}`)

Event-Based

Invalidate on specific events:

cms.on('content:afterSave', async (content) => {
  await cache.invalidate(`posts:${content.id}`)
  await cache.invalidate('posts:list')
})

Performance Benchmarks

Real-world performance across cache tiers:

Scenario Memory Hit KV Hit D1 Query
Simple content 0.5ms 8ms 25ms
Complex query 1ms 12ms 45ms
With relations 1.5ms 15ms 60ms

Cache Hit Rates

Typical hit rates for well-configured caching:

  • Memory: 60-80% for hot content
  • KV: 90-95% for all cached content
  • D1: Only ~5-10% of requests

Best Practices

1. Set Appropriate TTLs

// Static content: Long TTL
'/api/content/pages': { ttl: 86400 }, // 24 hours

// Dynamic listings: Short TTL
'/api/content/posts': { ttl: 60 }, // 1 minute

// Real-time data: No caching
'/api/analytics': { ttl: 0 },

2. Use Stale-While-Revalidate

Serve stale content while refreshing in the background:

cachePlugin({
  staleWhileRevalidate: true,
  staleTime: 60, // Serve stale for up to 60s while revalidating
})

3. Warm Critical Caches

Pre-populate caches for critical content:

// Warm cache on deploy
async function warmCache() {
  const posts = await db.getAllPublishedPosts()
  for (const post of posts) {
    await cache.set(`post:${post.slug}`, post)
  }
}

4. Monitor Cache Performance

Track cache effectiveness:

cachePlugin({
  metrics: true,
  onCacheHit: (key, tier) => {
    console.log(`Cache hit: ${key} from ${tier}`)
  },
  onCacheMiss: (key) => {
    console.log(`Cache miss: ${key}`)
  },
})

Debugging Cache Issues

Check Cache Status

API responses include cache metadata:

{
  "data": [...],
  "meta": {
    "cache": {
      "hit": true,
      "source": "kv",
      "age": 45,
      "ttl": 3600
    }
  }
}

Force Cache Bypass

For debugging, bypass the cache:

curl -H "Cache-Control: no-cache" https://api.example.com/content/posts

View Cache Keys

List cached keys:

wrangler kv:key list --namespace-id YOUR_KV_ID

Advanced Patterns

Regional Caching

Adjust TTLs based on region:

cachePlugin({
  ttlByRegion: {
    'North America': 3600,
    'Europe': 3600,
    'Asia': 1800, // Lower TTL for faster propagation
  },
})

Content-Aware Caching

Cache based on content type:

cms.on('content:beforeCache', (content, options) => {
  if (content.type === 'breaking-news') {
    options.ttl = 30 // Very short TTL
  }
})

Key Takeaways

  • Three-tiered caching provides sub-15ms response times
  • Memory cache is fastest but per-isolate
  • KV cache provides global persistence
  • D1 is the source of truth, accessed only on cache miss
  • Configure TTLs based on content update frequency
  • Use tag-based invalidation for related content
  • Monitor cache hit rates for optimization

Related Resources

Optimize your SonicJS deployment with intelligent caching!

#caching#performance#cloudflare#optimization

Share this article

Related Articles