Skills DirectorySkills Directory
SkillsLearnSecurityCategoriesDocsBlogPro
Sign InSubmit Skill
Skills Directory

Security-tested agent skills for Claude, coding agents, and AI workflows.

Directory

  • Browse Skills
  • All Skills A–Z
  • Claude Skills
  • Claude Code Skills
  • Agent Skills
  • Categories
  • Authors
  • Submit a Skill

Learn

  • Learn Hub
  • Install Claude Skills
  • Write SKILL.md
  • Skills vs MCP
  • Directories Compared

Security

  • Security
  • Methodology
  • Secure Claude Skills
  • Security Badges
  • Chrome Extension
  • Skill Manager

Company

  • About
  • Community
  • Blog
  • API Docs
  • Advertise

2026 Skills Directory. All rights reserved.

ProTermsPrivacyRefunds
Back to skills

Caching Patterns

ASecurity

Redis caching strategies, cache invalidation, write-through/write-behind, TTL management, and cache stampede protection.

530 stars
0 votes
0 copies
3 views
Added 5/29/2026
ai-agentstypescriptnodenodejsdatabase

Works with

cli

Security Analysis

A100/100

Scanned 5/29/2026

$npx -y skills add vibeeval/vibecosystem --skill caching-patterns --agent claude-code

Installs into .claude/skills of the current project.

Are you the author of Caching Patterns?

Add the live security badge to your README — it updates automatically with every re-scan.

Security grade badge for Caching Patterns
[![Security: A — Skills Directory](https://www.skillsdirectory.com/api/skills/vibeeval-caching-patterns/badge)](https://www.skillsdirectory.com/skills/vibeeval-caching-patterns)

More formats (shields.io, HTML) on the badges page. Keep it an A: scan every change in CI with Pro.

Download with Pro
Files
SKILL.md
---
name: caching-patterns
description: Redis caching strategies, cache invalidation, write-through/write-behind, TTL management, and cache stampede protection.
---

# Caching Patterns

Redis-based caching strategies for reducing latency and database load.

## Cache Key Design

```typescript
// Namespace:entity:id format
const CacheKeys = {
  market: (id: string) => `market:v1:${id}`,
  marketList: (filters: string) => `market:list:${filters}`,
  user: (id: string) => `user:v1:${id}`,
  userMarkets: (userId: string, page: number) => `user:${userId}:markets:${page}`,
  leaderboard: () => 'leaderboard:v1:global'
}

// Version prefix allows instant cache bust on schema change:
// bump v1 → v2 to invalidate all market keys without scanning
```

## Cache-Aside (Lazy Loading)

```typescript
import Redis from 'ioredis'

const redis = new Redis(process.env.REDIS_URL!)
const DEFAULT_TTL = 300  // 5 minutes

async function getOrSet<T>(
  key: string,
  loader: () => Promise<T>,
  ttl = DEFAULT_TTL
): Promise<T> {
  const cached = await redis.get(key)
  if (cached) return JSON.parse(cached) as T

  const value = await loader()
  await redis.setex(key, ttl, JSON.stringify(value))
  return value
}

// Usage
async function getMarket(id: string): Promise<Market> {
  return getOrSet(
    CacheKeys.market(id),
    () => db.market.findUniqueOrThrow({ where: { id } }),
    300
  )
}
```

## Write-Through Pattern

```typescript
// Write to cache AND database together - cache is always fresh
async function updateMarket(id: string, data: UpdateMarketDto): Promise<Market> {
  const updated = await db.market.update({ where: { id }, data })

  // Synchronously update cache so next read is fresh
  await redis.setex(CacheKeys.market(id), DEFAULT_TTL, JSON.stringify(updated))

  return updated
}

async function deleteMarket(id: string): Promise<void> {
  await db.market.delete({ where: { id } })
  await redis.del(CacheKeys.market(id))
}
```

## Write-Behind (Write-Back) Pattern

```typescript
// Write to cache immediately, flush to DB asynchronously (higher throughput)
// Risk: data loss on crash if queue not durable

class WriteBehindCache {
  private dirtyKeys = new Set<string>()
  private flushInterval: NodeJS.Timeout

  constructor(private flushEveryMs = 1000) {
    this.flushInterval = setInterval(() => this.flush(), flushEveryMs)
  }

  async write(key: string, value: unknown, dbWriter: () => Promise<void>): Promise<void> {
    // Instant cache update
    await redis.setex(key, DEFAULT_TTL, JSON.stringify(value))
    this.dirtyKeys.add(key)

    // Schedule DB write
    dbWriter().catch(err => {
      console.error(`Write-behind flush failed for ${key}:`, err)
      this.dirtyKeys.add(key)  // re-queue
    })
  }

  private async flush(): Promise<void> {
    // Implementation: drain dirty keys to DB in batch
    this.dirtyKeys.clear()
  }

  destroy(): void {
    clearInterval(this.flushInterval)
  }
}
```

## Cache Stampede Protection

```typescript
// Problem: 1000 concurrent requests on cache miss → 1000 DB queries
// Solution: mutex lock - only first request queries DB, rest wait

import { Mutex } from 'async-mutex'

const mutexMap = new Map<string, Mutex>()

function getMutex(key: string): Mutex {
  if (!mutexMap.has(key)) {
    mutexMap.set(key, new Mutex())
    // Cleanup after 30s to prevent memory leak
    setTimeout(() => mutexMap.delete(key), 30_000)
  }
  return mutexMap.get(key)!
}

async function getWithMutex<T>(
  key: string,
  loader: () => Promise<T>,
  ttl = DEFAULT_TTL
): Promise<T> {
  const cached = await redis.get(key)
  if (cached) return JSON.parse(cached) as T

  const mutex = getMutex(key)

  return mutex.runExclusive(async () => {
    // Double-check after acquiring lock
    const rechecked = await redis.get(key)
    if (rechecked) return JSON.parse(rechecked) as T

    const value = await loader()
    await redis.setex(key, ttl, JSON.stringify(value))
    return value
  })
}

// Probabilistic Early Expiration (alternative, no lock needed)
async function getWithEarlyExpire<T>(
  key: string,
  loader: () => Promise<T>,
  ttl = DEFAULT_TTL,
  beta = 1
): Promise<T> {
  const raw = await redis.get(key)

  if (raw) {
    const { value, expires } = JSON.parse(raw) as { value: T; expires: number }
    const ttlRemaining = (expires - Date.now()) / 1000
    // Probabilistically re-fetch before expiry
    if (ttlRemaining - beta * Math.log(Math.random()) > 0) {
      return value
    }
  }

  const value = await loader()
  const payload = { value, expires: Date.now() + ttl * 1000 }
  await redis.setex(key, ttl, JSON.stringify(payload))
  return value
}
```

## Multi-Level Caching (L1 Memory + L2 Redis)

```typescript
import LRU from 'lru-cache'

const l1 = new LRU<string, unknown>({
  max: 500,           // max 500 items in memory
  ttl: 30_000         // 30 seconds
})

async function getMultiLevel<T>(
  key: string,
  loader: () => Promise<T>,
  l2Ttl = DEFAULT_TTL
): Promise<T> {
  // L1: in-process memory (0ms)
  const l1Hit = l1.get(key) as T | undefined
  if (l1Hit !== undefined) return l1Hit

  // L2: Redis (~1ms)
  const l2Hit = await redis.get(key)
  if (l2Hit) {
    const value = JSON.parse(l2Hit) as T
    l1.set(key, value)  // warm L1
    return value
  }

  // L3: Database (~10ms+)
  const value = await loader()
  l1.set(key, value)
  await redis.setex(key, l2Ttl, JSON.stringify(value))
  return value
}

async function invalidateMultiLevel(key: string): Promise<void> {
  l1.delete(key)
  await redis.del(key)
}
```

## Event-Based Cache Invalidation

```typescript
// Instead of TTL-only, invalidate on data change events
import { EventEmitter } from 'events'

const cacheEvents = new EventEmitter()

// Emit on mutations
async function resolveMarket(id: string, outcome: string): Promise<void> {
  await db.market.update({ where: { id }, data: { status: 'resolved', outcome } })
  cacheEvents.emit('market:updated', id)
}

// Subscribe and invalidate
cacheEvents.on('market:updated', async (id: string) => {
  await redis.del(CacheKeys.market(id))
  // Also bust list caches containing this market
  const listKeys = await redis.keys('market:list:*')
  if (listKeys.length) await redis.del(...listKeys)
})
```

## Cache Warming

```typescript
// Pre-populate cache before traffic hits (e.g., after deploy)
async function warmCache(): Promise<void> {
  console.log('Warming cache...')

  // Top markets by volume
  const topMarkets = await db.market.findMany({
    take: 100,
    orderBy: { volume: 'desc' }
  })

  const pipeline = redis.pipeline()
  for (const market of topMarkets) {
    pipeline.setex(CacheKeys.market(market.id), 3600, JSON.stringify(market))
  }
  await pipeline.exec()

  console.log(`Cache warmed: ${topMarkets.length} markets`)
}

// Call on app startup
app.on('ready', warmCache)
```

## Monitoring Cache Health

```typescript
async function getCacheStats(): Promise<{
  hitRate: number
  memoryUsed: string
  connectedClients: number
  keyCount: number
}> {
  const info = await redis.info('stats')
  const memory = await redis.info('memory')
  const clients = await redis.info('clients')

  const hits = parseInt(info.match(/keyspace_hits:(\d+)/)?.[1] || '0')
  const misses = parseInt(info.match(/keyspace_misses:(\d+)/)?.[1] || '0')
  const total = hits + misses

  return {
    hitRate: total > 0 ? hits / total : 0,
    memoryUsed: memory.match(/used_memory_human:(.+)/)?.[1]?.trim() || 'unknown',
    connectedClients: parseInt(clients.match(/connected_clients:(\d+)/)?.[1] || '0'),
    keyCount: await redis.dbsize()
  }
}

// Alert if hit rate drops below 70%
setInterval(async () => {
  const stats = await getCacheStats()
  if (stats.hitRate < 0.7) {
    console.warn(`Low cache hit rate: ${(stats.hitRate * 100).toFixed(1)}%`)
  }
}, 60_000)
```

## Common Pitfalls

```
Cache penetration: requests for non-existent keys bypass cache every time
  → Cache null results with short TTL (30s)

Thundering herd: many requests hit DB simultaneously on cache expiry
  → Use mutex lock or probabilistic early expiration

Stale data: cache serves outdated values after DB update
  → Use write-through or event-based invalidation, not only TTL

Hot key: single cache key gets millions of requests/sec
  → Shard into multiple keys or replicate across Redis cluster

Big value: storing 10MB JSON in a single key blocks Redis
  → Compress with msgpack, split into smaller units, use streaming
```

**Remember**: Cache is eventually consistent by design. Design your system to tolerate brief staleness, and use invalidation events for correctness-critical data.

Attribution

vibeevalvibeeval
View sourceSee grades on GitHubMore from vibeeval →
SSkills DirectorySkills Directory

Ship a skill? Prove it's safe.

Free 120-pattern security scan, letter grade, and an embeddable README badge.

Submit a skill

Is this your skill, or is something wrong with this listing? Request removal or report an issue. Author removals are honored within 72 hours.

Comments (0)

No comments yet. Be the first to comment!

SSkills DirectorySkills Directory

Ship a skill? Prove it's safe.

Free 120-pattern security scan, letter grade, and an embeddable README badge.

Submit a skill

Related Skills

Caveman

Terse caveman voice: answer first, fluff gone, every technical fact kept. Use for /caveman, "caveman mode", "talk like caveman", "be brief", "less tokens". Stays on until "stop caveman" or "normal mode".

1100021 votes

Hyperplan

Adversarial multi-agent planning skill. Self-orchestrates 5 hostile category members (unspecified-low, unspecified-high, deep, ultrabrain, artistry) via team-mode for ruthless cross-critique debate, distills only the defensible insights, then MANDATORILY hands the distilled insight bundle to the `plan` agent for executable plan formalization. Use when planning needs maximum rigor and surfacing of weak assumptions, blind spots, and over-engineering. Triggers: 'hyperplan', 'hpp', '/hyperplan', ...

698431 votes

Writing Skills

Create and manage Claude Code skills in HASH repository following Anthropic best practices. Use when creating new skills, modifying skill-rules.json, understanding trigger patterns, working with hooks, debugging skill activation, or implementing progressive disclosure. Covers skill structure, YAML frontmatter, trigger types (keywords, intent patterns), UserPromptSubmit hook, and the 500-line rule. Includes validation and debugging with SKILL_DEBUG. Examples include rust-error-stack, cargo-dep...

3931 votes

Mcp Code Execution

Routes multi-tool workflows through MCP servers for large datasets and pipelines. Use when Bash tool overhead is limiting throughput on data-heavy tasks.

3421 votes

catchup

Recovers the conversation and failed tool calls of a previous Codex, Amp, Claude Code, Antigravity, Cline, Copilot CLI, Cursor, DeepSeek Harness, Grok Build, Kimi, OpenCode, Pi Agent, or ZCode session. Use when the user says "catch up", "what did the last session do", "get me up to speed", "I switched agents", asks to recover/summarize a previous session before continuing, or asks to diagnose or report a catchup failure. Do NOT use for the current conversation, git history, or any non-agent log.

741 votes
View all in ai-agents →