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:

ScenarioMemory HitKV HitD1 Query
Simple content0.5ms8ms25ms
Complex query1ms12ms45ms
With relations1.5ms15ms60ms

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

Isometric visualization of a modular plugin system with extension blocks plugging into a central CMS hub via glowing data lines
Guides

SonicJS Plugins: How to Extend Your CMS

Build, configure, and ship SonicJS plugins with TypeScript β€” custom routes, lifecycle hooks, DB-backed settings, and admin pages on Cloudflare Workers.

Edge authentication illustration showing JWT tokens flowing through Cloudflare Workers with login methods for password, OAuth, magic link, and OTP
Guides

SonicJS Authentication: A Complete Guide

Learn how to wire up password, OAuth, magic link, and OTP authentication in SonicJS with JWTs, role-based access control, and Cloudflare KV-cached sessions.