Caching Pattern

Version: 1.0.0
Category: API Patterns
Purpose: Guide for AI agents on implementing effective response caching strategies


Overview

Caching API responses reduces server load, improves performance, and helps stay within rate limits. This pattern explains when and how to cache API responses effectively while maintaining data freshness.


Caching Basics

What Is Caching?

Caching stores API responses locally to avoid redundant requests:

When to Cache

Good Candidates for Caching:

Don’t Cache:


Cache Strategies

Strategy 1: Time-Based Caching (TTL)

Approach: Cache responses for a fixed time period

Implementation:

{
  "strategy": "time-based-ttl",
  "cache": {
    "ttl": 300,
    "unit": "seconds",
    "key": "player-{id}",
    "invalidate": "after-ttl"
  }
}

Example TTL Values:

Use When:

Strategy 2: Event-Based Invalidation

Approach: Invalidate cache when related events occur

Implementation:

{
  "strategy": "event-based-invalidation",
  "cache": {
    "key": "player-{id}",
    "invalidateOn": [
      "structs.player.{id}",
      "structs.planet.{id}",
      "structs.guild.{id}"
    ]
  }
}

Example:

Use When:

Strategy 3: Version-Based Caching

Approach: Cache based on data version/ETag

Implementation:

{
  "strategy": "version-based",
  "cache": {
    "key": "player-{id}",
    "version": "response.headers.ETag",
    "check": "If-None-Match header",
    "invalidate": "when-version-changes"
  }
}

Use When:

Strategy 4: Hybrid Caching

Approach: Combine multiple strategies

Implementation:

{
  "strategy": "hybrid",
  "cache": {
    "ttl": 300,
    "eventInvalidation": true,
    "versionCheck": true,
    "fallback": "ttl"
  }
}

Use When:


Cache Key Patterns

Entity-Based Keys

Pattern: {entity}-{id}

Examples:

{
  "keys": {
    "player": "player-{id}",
    "planet": "planet-{id}",
    "guild": "guild-{id}",
    "struct": "struct-{id}"
  }
}

Query-Based Keys

Pattern: {endpoint}-{params}

Examples:

{
  "keys": {
    "playerPlanets": "player-{id}-planets",
    "guildMembers": "guild-{id}-members",
    "planetStructs": "planet-{id}-structs"
  }
}

Composite Keys

Pattern: {entity}-{id}-{language}-{guildId}

Examples:

{
  "keys": {
    "structTypeWithCosmetics": "struct-type-{id}-{language}-{guildId}",
    "playerWithGuild": "player-{id}-guild-{guildId}"
  }
}

Cache Implementation Examples

Example 1: Simple TTL Cache

{
  "implementation": "simple-ttl-cache",
  "code": {
    "javascript": "class SimpleCache {\n  constructor(ttl = 300) {\n    this.cache = new Map();\n    this.ttl = ttl * 1000; // Convert to ms\n  }\n  \n  get(key) {\n    const item = this.cache.get(key);\n    if (!item) return null;\n    \n    if (Date.now() > item.expires) {\n      this.cache.delete(key);\n      return null;\n    }\n    \n    return item.value;\n  }\n  \n  set(key, value) {\n    this.cache.set(key, {\n      value,\n      expires: Date.now() + this.ttl\n    });\n  }\n}"
  },
  "usage": {
    "example": "const cache = new SimpleCache(300); // 5 min TTL\n\nasync function getPlayer(id) {\n  const cached = cache.get(`player-${id}`);\n  if (cached) return cached;\n  \n  const response = await fetch(`/api/player/${id}`);\n  const data = await response.json();\n  cache.set(`player-${id}`, data);\n  return data;\n}"
  }
}

Example 2: Event-Based Cache with Streaming

{
  "implementation": "event-based-cache",
  "code": {
    "javascript": "class EventBasedCache {\n  constructor() {\n    this.cache = new Map();\n    this.nats = null;\n  }\n  \n  async connectNATS() {\n    // Connect to GRASS/NATS\n    this.nats = await connect({ servers: ['nats://localhost:4222'] });\n    \n    // Subscribe to invalidation events\n    this.nats.subscribe('structs.player.*', (msg) => {\n      const data = JSON.parse(msg.data.toString());\n      this.invalidate(`player-${data.id}`);\n    });\n  }\n  \n  invalidate(key) {\n    this.cache.delete(key);\n  }\n  \n  get(key) {\n    return this.cache.get(key);\n  }\n  \n  set(key, value) {\n    this.cache.set(key, value);\n  }\n}"
  },
  "usage": {
    "example": "const cache = new EventBasedCache();\nawait cache.connectNATS();\n\n// Cache will auto-invalidate on player updates"
  }
}

Example 3: Hybrid Cache (TTL + Events)

{
  "implementation": "hybrid-cache",
  "code": {
    "javascript": "class HybridCache {\n  constructor(ttl = 300) {\n    this.cache = new Map();\n    this.ttl = ttl * 1000;\n    this.nats = null;\n  }\n  \n  get(key) {\n    const item = this.cache.get(key);\n    if (!item) return null;\n    \n    // Check TTL\n    if (Date.now() > item.expires) {\n      this.cache.delete(key);\n      return null;\n    }\n    \n    return item.value;\n  }\n  \n  set(key, value) {\n    this.cache.set(key, {\n      value,\n      expires: Date.now() + this.ttl\n    });\n  }\n  \n  invalidate(key) {\n    this.cache.delete(key);\n  }\n  \n  // Subscribe to events for invalidation\n  async setupEventInvalidation() {\n    this.nats = await connect({ servers: ['nats://localhost:4222'] });\n    \n    this.nats.subscribe('structs.player.*', (msg) => {\n      const data = JSON.parse(msg.data.toString());\n      this.invalidate(`player-${data.id}`);\n    });\n  }\n}"
  }
}

Cache Invalidation Patterns

Pattern 1: TTL Expiration

When: Cache entry expires after TTL

Implementation:

{
  "invalidation": "ttl-expiration",
  "check": "on-get",
  "action": "delete-if-expired"
}

Pattern 2: Event-Driven Invalidation

When: Related event received via GRASS/NATS

Implementation:

{
  "invalidation": "event-driven",
  "events": {
    "structs.player.{id}": "invalidate-player-{id}",
    "structs.planet.{id}": "invalidate-planet-{id}",
    "structs.guild.{id}": "invalidate-guild-{id}"
  }
}

Pattern 3: Manual Invalidation

When: Explicitly invalidate cache

Implementation:

{
  "invalidation": "manual",
  "methods": [
    "cache.invalidate(key)",
    "cache.clear()",
    "cache.invalidatePattern(pattern)"
  ]
}

Pattern 4: Dependency-Based Invalidation

When: Related entity changes

Implementation:

{
  "invalidation": "dependency-based",
  "dependencies": {
    "player-{id}": [
      "player-{id}-planets",
      "player-{id}-guild",
      "guild-{guildId}-members"
    ]
  }
}

Static Data (Long TTL)

Examples: Struct types, game constants

TTL: 3600 seconds (1 hour) or longer

{
  "staticData": {
    "structTypes": 3600,
    "gameConstants": 7200,
    "structTypeCosmetics": 1800
  }
}

Slowly-Changing Data (Medium TTL)

Examples: Player info, guild info

TTL: 300 seconds (5 minutes)

{
  "slowlyChanging": {
    "playerInfo": 300,
    "guildInfo": 300,
    "planetInfo": 180
  }
}

Moderately-Changing Data (Short TTL)

Examples: Planet resources, guild stats

TTL: 60 seconds (1 minute)

{
  "moderatelyChanging": {
    "planetResources": 60,
    "guildStats": 60,
    "playerStats": 120
  }
}

Frequently-Changing Data (Very Short TTL)

Examples: Planet shield, raid status

TTL: 10-30 seconds

{
  "frequentlyChanging": {
    "planetShield": 10,
    "raidStatus": 15,
    "fleetStatus": 30
  }
}

Cache Best Practices

1. Use Appropriate TTL Values

Do:

Don’t:

2. Implement Cache Warming

Do:

{
  "cacheWarming": {
    "onStartup": [
      "Load frequently accessed data",
      "Pre-fetch critical entities",
      "Subscribe to invalidation events"
    ]
  }
}

Benefits:

3. Monitor Cache Performance

Metrics to Track:

Example:

{
  "monitoring": {
    "metrics": {
      "hitRate": 0.85,
      "missRate": 0.15,
      "averageResponseTime": 50,
      "cacheSize": 1000
    }
  }
}

4. Handle Cache Failures Gracefully

Do:

{
  "errorHandling": {
    "cacheMiss": "fetch-from-api",
    "cacheError": "fetch-from-api",
    "cacheCorruption": "clear-and-refetch"
  }
}

Don’t:

5. Use Streaming for Real-time Data

Do:

{
  "realTimeData": {
    "use": "GRASS/NATS streaming",
    "benefit": "No caching needed",
    "example": "Planet shield updates, raid status"
  }
}

Benefits:


Cache and Rate Limiting

How Caching Helps Rate Limits

Benefits:

Example:

{
  "scenario": "100 requests/minute limit",
  "withoutCache": {
    "requests": 100,
    "remaining": 0
  },
  "withCache": {
    "requests": 30,
    "cacheHits": 70,
    "remaining": 70
  }
}

Combining Caching and Rate Limiting

Strategy:

  1. Cache responses aggressively
  2. Monitor rate limit headers
  3. Use cached data when rate limit is low
  4. Fetch fresh data when quota available

Cache Storage Options

In-Memory Cache

Use When:

Example: JavaScript Map, Python dict

Persistent Cache

Use When:

Example: Redis, SQLite, file system

Distributed Cache

Use When:

Example: Redis cluster, Memcached



Quick Reference

Cache Key Patterns

Best Practices

  1. Use appropriate TTL values
  2. Implement cache warming
  3. Monitor cache performance
  4. Handle failures gracefully
  5. Use streaming for real-time data

Pattern Version: 1.0.0 - December 7, 2025