mcpField
C
caching

caching

github.com/yanko-belov开发1336 次安装40 周前更新
代码仓库

关于 caching

Caching 是一项开发 Skill,指导选择缓存模式并规划数据新鲜度与失效机制。内容涵盖 TTL、写穿、事件驱动和旁路缓存,并说明陈旧数据的风险。

主要功能

  1. 01要求为每个缓存制定失效方案
  2. 02说明 TTL 和写穿模式
  3. 03介绍事件驱动与旁路缓存策略
  4. 04列出适合及不适合缓存的数据
  5. 05指出数据陈旧和内存限制风险

适用场景

  1. 01为重复的数据查询添加缓存
  2. 02提升高成本计算的性能
  3. 03规划数据库或 API 结果的新鲜度

安装

复制下面的命令,在终端中运行。

$npx skills add https://github.com/yanko-belov/code-craft --skill caching

安装后在 Claude Code 中输入/caching确认已加载。

详细介绍

缓存

概述

积极缓存,但始终要有失效策略。

缓存能大幅提升性能,但过期数据会导致错误。每个缓存都需要有保持数据新鲜度的方案。

适用场景

  • 反复获取相同数据
  • 计算开销大
  • 数据库查询缓慢
  • 外部 API 有速率限制
  • 有人要求“只要加个缓存”

铁律

NEVER add a cache without defining its invalidation strategy.

没有例外:

  • 不能因为“它很少变化”
  • 不能因为“TTL 就够了”
  • 不能因为“以后再想办法”
  • 不能因为“用户可以刷新”

检测:没有策略的缓存异味

如果缓存没有失效方案,请停止:

// ❌ 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;
}

问题:

  • 用户更新姓名 → 缓存仍显示旧姓名
  • 没有内存限制 → 内存泄漏
  • 没有 TTL → 数据永远过期
  • 分布式系统 → 存在多个过期副本

正确模式:带策略的缓存

// ✅ 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;
}

缓存失效策略

1. 基于时间(TTL)
// Good for: data that can be slightly stale
const TTL = 60 * 1000; // 1 minute
cache.set(key, value, { ttl: TTL });
2. 写穿透
// 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;
}
3. 基于事件
// Good for: distributed systems
eventBus.on('user.updated', (userId) => {
  cache.delete(`user:${userId}`);
});

eventBus.on('product.priceChanged', (productId) => {
  cache.delete(`product:${productId}`);
});
4. 缓存旁路(惰性加载)
// 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;
}

缓存什么

适合缓存不适合缓存
用户资料会话令牌
产品目录支付状态
配置实时库存
API 响应特定用户的计算结果
计算得出的聚合数据快速变化的数据

Redis 示例

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);
  }
}

抵御压力的协议

1. “它很少变化”

压力:“这些数据几乎不会更新”

回应:“几乎不会”仍然意味着有时会更新。一旦更新,过期缓存就会导致错误。

**行动:**至少添加 TTL,并在写入时添加失效机制。

2. “TTL 就够了”

压力:“我们只要在 5 分钟后让缓存过期就行”

**回应:**5 分钟的过期数据可能无法接受。用户更新资料后,看到的还是旧数据。

**行动:**TTL + 写穿透失效。

3. “以后再想办法”

压力:“只要加缓存,如果过期数据成了问题,我们再处理”

**回应:**过期数据导致的错误很难调试。应提前设计失效机制。

**行动:**未定义失效策略之前,不要使用缓存。

危险信号——停止并重新考虑

  • 缓存没有 TTL
  • 数据更新时没有失效机制
  • “用户可以刷新以查看新数据”
  • 分布式系统中使用内存缓存
  • 缓存没有内存限制

以上情况都意味着:定义失效策略。

速查

模式适用场景失效方式
仅 TTL可以接受数据过期自动过期
写穿透由你控制写入写入时更新缓存
基于事件分布式系统发生变更时通过发布/订阅通知
缓存旁路读多写少TTL + 手动失效

常见借口(均不成立)

借口事实
“很少变化”很少 ≠ 从不。要做好应对准备。
“TTL 就够了”TTL + 失效机制更好。
“以后再想办法”过期数据导致的错误很难追踪。
“用户可以刷新”这是错误,不是功能。
“只是为了提升性能”过期数据会破坏功能。

底线

每个缓存都需要:TTL、大小限制和失效策略。

积极缓存以提升性能。但始终要清楚数据变化时如何使缓存失效。“它很少变化”不是策略。

评论

支持 Markdown