caching
Use when same data is fetched repeatedly. Use when database queries are slow. Use when implementing caching without invalidation strategy.
用 Codex 或 Claude 帮你安装 复制这段 Prompt,粘贴到 Codex、Claude 或其他助手里,让它检查 Skill 页面并帮你完成安装。
菜单
Use when same data is fetched repeatedly. Use when database queries are slow. Use when implementing caching without invalidation strategy.
用 Codex 或 Claude 帮你安装 复制这段 Prompt,粘贴到 Codex、Claude 或其他助手里,让它检查 Skill 页面并帮你完成安装。
Use when writing tests. Use when test structure is unclear. Use when arrange/act/assert phases are mixed.
Use when designing or modifying APIs. Use when adding breaking changes. Use when clients depend on API stability.
Use when implementing authentication. Use when storing passwords. Use when asked to store credentials insecurely.
Use when tempted to use class inheritance. Use when creating class hierarchies. Use when subclass needs only some parent behavior.
Use when acquiring multiple locks. Use when operations wait for each other. Use when system hangs without crashing.
Use when a class creates its own dependencies. Use when instantiating concrete implementations inside a class. Use when told to avoid dependency injection for simplicity.
基于 SOC 职业分类
| name | caching |
| description | Use when same data is fetched repeatedly. Use when database queries are slow. Use when implementing caching without invalidation strategy. |
Cache aggressively, but always have an invalidation strategy.
Caching improves performance dramatically, but stale data causes bugs. Every cache needs a plan for freshness.
NEVER add a cache without defining its invalidation strategy.
No exceptions:
If cache has no invalidation plan, STOP:
// ❌ VIOLATION: Cache without invalidation strategy
const cache = new Map();
async function getUser(id: string) {
if (cache.has(id)) {
return cache.get(id); // Could be stale forever!
}
const user = await db.users.findById(id);
cache.set(id, user); // When does this expire? When user updates?
return user;
}
Problems:
// ✅ CORRECT: Cache with TTL and invalidation
interface CacheEntry<T> {
data: T;
expiresAt: number;
}
class UserCache {
private cache = new Map<string, CacheEntry<User>>();
private TTL_MS = 5 * 60 * 1000; // 5 minutes
async get(id: string): Promise<User> {
const cached = this.cache.get(id);
if (cached && cached.expiresAt > Date.now()) {
return cached.data;
}
const user = await db.users.findById(id);
this.set(id, user);
return user;
}
set(id: string, user: User): void {
this.cache.set(id, {
data: user,
expiresAt: Date.now() + this.TTL_MS
});
}
// Explicit invalidation on updates
invalidate(id: string): void {
this.cache.delete(id);
}
invalidateAll(): void {
this.cache.clear();
}
}
// Usage with invalidation on write
async function updateUser(id: string, data: UpdateUserDto) {
const user = await db.users.update(id, data);
userCache.invalidate(id); // Clear stale cache
return user;
}
// Good for: data that can be slightly stale
const TTL = 60 * 1000; // 1 minute
cache.set(key, value, { ttl: TTL });
// Good for: data you control writes for
async function updateProduct(id, data) {
const product = await db.products.update(id, data);
await cache.set(`product:${id}`, product); // Update cache on write
return product;
}
// Good for: distributed systems
eventBus.on('user.updated', (userId) => {
cache.delete(`user:${userId}`);
});
eventBus.on('product.priceChanged', (productId) => {
cache.delete(`product:${productId}`);
});
// Good for: read-heavy, tolerance for staleness
async function getProduct(id) {
let product = await cache.get(`product:${id}`);
if (!product) {
product = await db.products.findById(id);
await cache.set(`product:${id}`, product, { ttl: 300 });
}
return product;
}
| Good to Cache | Bad to Cache |
|---|---|
| User profiles | Session tokens |
| Product catalog | Payment status |
| Configuration | Real-time inventory |
| API responses | User-specific calculations |
| Computed aggregates | Rapidly changing data |
import Redis from 'ioredis';
const redis = new Redis();
class ProductCache {
private prefix = 'product:';
private ttl = 300; // 5 minutes
async get(id: string): Promise<Product | null> {
const cached = await redis.get(this.prefix + id);
return cached ? JSON.parse(cached) : null;
}
async set(id: string, product: Product): Promise<void> {
await redis.setex(
this.prefix + id,
this.ttl,
JSON.stringify(product)
);
}
async invalidate(id: string): Promise<void> {
await redis.del(this.prefix + id);
}
async invalidatePattern(pattern: string): Promise<void> {
const keys = await redis.keys(this.prefix + pattern);
if (keys.length) await redis.del(...keys);
}
}
Pressure: "This data almost never updates"
Response: "Almost never" still means sometimes. When it does, stale cache = bugs.
Action: Add TTL at minimum. Add invalidation on write.
Pressure: "We'll just expire after 5 minutes"
Response: 5 minutes of stale data might be unacceptable. User updates profile, sees old data.
Action: TTL + write-through invalidation.
Pressure: "Just add caching, we'll handle staleness if it's a problem"
Response: Staleness bugs are hard to debug. Design invalidation upfront.
Action: No cache without invalidation strategy defined.
All of these mean: Define invalidation strategy.
| Pattern | Use When | Invalidation |
|---|---|---|
| TTL only | Staleness OK | Automatic expiry |
| Write-through | You control writes | Update cache on write |
| Event-based | Distributed system | Pub/sub on changes |
| Cache-aside | Read-heavy | TTL + manual invalidate |
| Excuse | Reality |
|---|---|
| "Rarely changes" | Rarely ≠ never. Plan for it. |
| "TTL is enough" | TTL + invalidation is better. |
| "Figure it out later" | Staleness bugs are hard to trace. |
| "Users can refresh" | That's a bug, not a feature. |
| "It's just for performance" | Stale data breaks functionality. |
Every cache needs: TTL, size limit, and invalidation strategy.
Cache aggressively for performance. But always know how the cache gets invalidated when data changes. "It rarely changes" is not a strategy.