Serverless Function Caching Patterns
Serverless functions have unique caching challenges: cold starts, ephemeral state, and pay-per-invocation pricing. The right caching strategy can cut costs by 80% and eliminate cold start latency. Here's how.
The Serverless Caching Challenge
Traditional caching assumes persistent processes. Serverless breaks this assumption:
- Functions spin down between invocations—no in-memory cache
- Cold starts add 100ms-2s latency
- Each invocation costs money (compute + network)
- Concurrent executions don't share state
You need external caching (Redis, CDN) plus smart connection reuse.
Pattern 1: Connection Reuse
Establish connections outside the handler to reuse across warm invocations:
// AWS Lambda - Reuse database and cache connections
// These persist across warm invocations
let redisClient = null;
let dbPool = null;
async function getRedis() {
if (!redisClient) {
const Redis = require('ioredis');
redisClient = new Redis(process.env.REDIS_URL);
}
return redisClient;
}
async function getDB() {
if (!dbPool) {
const { Pool } = require('pg');
dbPool = new Pool({
connectionString: process.env.DATABASE_URL,
max: 1 // Single connection per Lambda instance
});
}
return dbPool;
}
exports.handler = async (event) => {
const cache = await getRedis();
const db = await getDB();
// Use cache and db...
};
Pattern 2: Edge Caching (CDN)
Cache responses at the edge for static or semi-static data:
// Vercel Edge Function with caching headers
export const config = { runtime: 'edge' };
export default async function handler(req) {
const cacheKey = new URL(req.url).pathname;
// Check if we can serve from cache
const cached = await fetch(`https://cache.example.com/${cacheKey}`);
if (cached.ok) {
return new Response(await cached.text(), {
headers: {
'Content-Type': 'application/json',
'Cache-Control': 'public, s-maxage=60, stale-while-revalidate=300',
'X-Cache': 'HIT'
}
});
}
// Generate response
const data = await generateData();
// Cache for next time
await cacheResponse(cacheKey, data);
return new Response(JSON.stringify(data), {
headers: {
'Content-Type': 'application/json',
'Cache-Control': 'public, s-maxage=60, stale-while-revalidate=300',
'X-Cache': 'MISS'
}
});
}
Pattern 3: Cloudflare Workers KV
Cloudflare's edge key-value store for low-latency caching:
// Cloudflare Worker with KV caching
export default {
async fetch(request, env) {
const url = new URL(request.url);
const cacheKey = `cache:${url.pathname}`;
// Try KV cache first
let data = await env.CACHE_KV.get(cacheKey, 'json');
if (!data) {
// Fetch from origin
const response = await fetch(env.API_ORIGIN + url.pathname);
data = await response.json();
// Cache in KV (60 second TTL)
await env.CACHE_KV.put(cacheKey, JSON.stringify(data), {
expirationTtl: 60
});
}
return new Response(JSON.stringify(data), {
headers: { 'Content-Type': 'application/json' }
});
}
};
Pattern 4: In-Memory Cache for Warm Functions
Cache frequently accessed data in function memory:
// Global cache (survives across warm invocations)
const memoryCache = new Map();
const CACHE_TTL = 60000; // 1 minute
function getCached(key) {
const cached = memoryCache.get(key);
if (cached && Date.now() < cached.expiry) {
return cached.value;
}
return null;
}
function setCached(key, value, ttl = CACHE_TTL) {
memoryCache.set(key, {
value,
expiry: Date.now() + ttl
});
// Limit cache size
if (memoryCache.size > 1000) {
const oldest = memoryCache.keys().next().value;
memoryCache.delete(oldest);
}
}
exports.handler = async (event) => {
const key = `config:${event.tenantId}`;
// Check memory cache first (fastest)
let config = getCached(key);
if (!config) {
// Check Redis (fast)
const redis = await getRedis();
config = await redis.get(key);
if (!config) {
// Database (slowest)
config = await db.query('SELECT * FROM config WHERE tenant_id = $1', [event.tenantId]);
await redis.set(key, JSON.stringify(config), 'EX', 300);
}
setCached(key, config);
}
return config;
};
Pattern 5: Pre-warming Critical Paths
Keep functions warm for critical paths:
// CloudWatch scheduled event to keep Lambda warm
// Run every 5 minutes
exports.warmHandler = async (event) => {
if (event.source === 'aws.events') {
// This is a warming invocation
console.log('Warming invocation - keeping connections alive');
// Touch connections to keep them alive
const redis = await getRedis();
await redis.ping();
return { statusCode: 200, body: 'Warmed' };
}
// Regular invocation logic...
};
Cost Optimization
Caching reduces serverless costs in multiple ways:
- Fewer invocations: Edge cache serves requests without invoking function
- Faster execution: Cached data reduces compute time
- Reduced egress: Less data transferred from database
- Lower database load: Fewer connections and queries
Serverless caching made simple
Cachee.ai integrates with Lambda, Vercel, and Cloudflare for automatic edge caching with zero config.
Start Free Trial