Caching Strategies
Comprehensive guide to caching in SCORM API integrations.
Table of Contents
Overview
Caching reduces:
- API calls
- Response times
- Server load
- Costs
Client-Side Caching
Package Metadata Caching
Cache Package Information:
class PackageCache {
private cache = new Map<string, { data: any; expiresAt: number }>();
private ttl = 5 * 60 * 1000; // 5 minutes
async get(packageId: string) {
const cached = this.cache.get(packageId);
if (cached && cached.expiresAt > Date.now()) {
return cached.data;
}
const pkg = await fetchPackage(packageId);
this.cache.set(packageId, {
data: pkg,
expiresAt: Date.now() + this.ttl
});
return pkg;
}
invalidate(packageId: string) {
this.cache.delete(packageId);
}
}
Session Data Caching
Cache Session State:
class SessionCache {
private cache = new Map<string, SessionData>();
private ttl = 30 * 1000; // 30 seconds (sessions change frequently)
async get(sessionId: string) {
const cached = this.cache.get(sessionId);
if (cached && cached.expiresAt > Date.now()) {
return cached.data;
}
const session = await fetchSession(sessionId);
this.cache.set(sessionId, {
data: session,
expiresAt: Date.now() + this.ttl
});
return session;
}
update(sessionId: string, updates: any) {
const cached = this.cache.get(sessionId);
if (cached) {
cached.data = { ...cached.data, ...updates };
cached.expiresAt = Date.now() + this.ttl;
}
}
}
List Caching
Cache Package Lists:
class ListCache {
private cache = new Map<string, { data: any[]; expiresAt: number }>();
async getPackages(tenantId: string, filters: any) {
const key = `packages:${tenantId}:${JSON.stringify(filters)}`;
const cached = this.cache.get(key);
if (cached && cached.expiresAt > Date.now()) {
return cached.data;
}
const packages = await fetchPackages(tenantId, filters);
this.cache.set(key, {
data: packages,
expiresAt: Date.now() + 2 * 60 * 1000 // 2 minutes
});
return packages;
}
}
Server-Side Caching
Redis Caching
Setup Redis:
import Redis from 'ioredis';
const redis = new Redis(process.env.REDIS_URL);
async function getCachedPackage(packageId: string) {
const cached = await redis.get(`package:${packageId}`);
if (cached) {
return JSON.parse(cached);
}
const pkg = await fetchPackageFromDB(packageId);
await redis.setex(
`package:${packageId}`,
300, // 5 minutes TTL
JSON.stringify(pkg)
);
return pkg;
}
In-Memory Caching
Node.js Cache:
import NodeCache from 'node-cache';
const cache = new NodeCache({ stdTTL: 300 }); // 5 minutes default
async function getPackage(packageId: string) {
const cached = cache.get(`package:${packageId}`);
if (cached) {
return cached;
}
const pkg = await fetchPackage(packageId);
cache.set(`package:${packageId}`, pkg);
return pkg;
}
CDN Caching
Static Assets
Cache Package Files:
// Package files served via CDN
const packageUrl = `https://cdn.app.allureconnect.com/packages/${packageId}/index.html`;
// CDN caches with long TTL
// Cache-Control: public, max-age=31536000
API Responses
Cache API Responses:
// Cache-Control headers
response.headers.set('Cache-Control', 'public, max-age=300'); // 5 minutes
response.headers.set('ETag', generateETag(data));
// Client sends If-None-Match
if (request.headers.get('If-None-Match') === etag) {
return new Response(null, { status: 304 }); // Not Modified
}
Cache Invalidation
Manual Invalidation
Invalidate on Updates:
async function updatePackage(packageId: string, updates: any) {
// Update in database
await db.packages.update({
where: { id: packageId },
data: updates
});
// Invalidate cache
cache.delete(`package:${packageId}`);
cache.delete(`packages:list:${tenantId}`);
// Invalidate CDN (if using)
await invalidateCDN(`/packages/${packageId}/*`);
}
Time-Based Invalidation
TTL-Based:
// Cache expires after TTL
const cache = new Map<string, { data: any; expiresAt: number }>();
function isExpired(key: string): boolean {
const cached = cache.get(key);
return !cached || cached.expiresAt < Date.now();
}
Event-Based Invalidation
Webhook-Based:
// Invalidate cache on webhook events
webhook.on('package.processing.completed', (event) => {
cache.delete(`package:${event.data.package_id}`);
cache.delete(`packages:list:${event.tenant_id}`);
});
Best Practices
1. Choose Appropriate TTLs
const TTLs = {
packageMetadata: 5 * 60 * 1000, // 5 minutes
sessionData: 30 * 1000, // 30 seconds
packageList: 2 * 60 * 1000, // 2 minutes
staticAssets: 365 * 24 * 60 * 60 * 1000 // 1 year
};
2. Cache Keys
Use Descriptive Keys:
// Good keys
`package:${packageId}`
`packages:list:${tenantId}:page:${page}:limit:${limit}`
`session:${sessionId}`
// Bad keys
`pkg:${id}`
`list`
`data`
3. Handle Cache Misses
async function getWithFallback(key: string, fetchFn: () => Promise<any>) {
// Try cache first
const cached = await cache.get(key);
if (cached) {
return cached;
}
// Fetch on miss
const data = await fetchFn();
// Cache result
await cache.set(key, data);
return data;
}
4. Monitor Cache Performance
class CacheMetrics {
hits = 0;
misses = 0;
get hitRate() {
const total = this.hits + this.misses;
return total > 0 ? this.hits / total : 0;
}
}
const metrics = new CacheMetrics();
async function get(key: string) {
const cached = await cache.get(key);
if (cached) {
metrics.hits++;
return cached;
}
metrics.misses++;
return null;
}
Related Documentation
- Optimization Guide - Performance optimization
- Scaling Guide - Scaling strategies
- API Reference - Complete API docs
Last Updated: 2025-01-15