关于 cache-strategy
指导开发者在应用中采用永久缓存优先策略:读取时先查缓存,未命中再查数据库,数据变更后使缓存失效。内容涵盖候选数据识别、缓存键约定和可选预热。
主要功能
- 01检查现有缓存基础设施
- 02识别适合缓存的数据库读取
- 03实现缓存优先读取
- 04在数据写入或删除后失效缓存
- 05制定缓存键命名约定
- 06提供可选的启动预热方案
适用场景
- 01减少频繁读取且少变数据的数据库查询
- 02为用户资料或商品详情增加缓存
- 03确保数据变更后清除相关条目和列表
- 04为关键数据准备启动时缓存预热
安装
复制下面的命令,在终端中运行。
$
npx skills add https:/ / github.com/ ariadoss/ superskills --skill cache-strategy安装后在 Claude Code 中输入/cache-strategy确认已加载。
详细介绍
/cache-strategy
实现永久的缓存优先策略:先读取缓存,未命中时回退到 DB,随后写入缓存;仅在数据发生变更时使缓存失效。不设置过期计时器——只要底层数据没有变化,缓存就始终有效。
理念
目标是每项数据最多读取 DB 一次(直到数据发生变化):
READ: cache hit? → return cached value
cache miss? → query DB → write to cache → return value
WRITE: update DB → invalidate (or update) cache entry → done
这是 Play Framework 的模式:没有 TTL、没有轮询,也没有后台刷新。缓存始终处于预热状态且始终正确,因为只有在源数据发生变化时才会精准地使缓存失效。
为什么选择永久缓存而非 TTL?
- TTL 缓存以读取到过期数据为代价,换取简单性——每次过期都会确保触发一次 DB 读取,即使数据完全没有变化
- 通过失效机制实现的永久缓存,既不会产生不必要的 DB 读取,也不会读取到过期数据
- 只有在无法得知数据何时变化时(例如第三方 API)才适合使用 TTL
步骤 1:检查缓存基础设施
# Check for Redis
grep -rn "redis\|Redis\|REDIS_URL" --include="*.json" --include="*.env*" --include="*.yml" --include="*.yaml" --include="*.rb" --include="*.py" --include="*.js" --include="*.ts" . 2>/dev/null | grep -v node_modules | head -10
# Check for Memcached
grep -rn "memcached\|Memcached\|MEMCACHE" --include="*.json" --include="*.env*" --include="*.yml" . 2>/dev/null | grep -v node_modules | head -5
# Check for in-process cache (node-cache, lru-cache, etc.)
grep -rn "lru-cache\|node-cache\|memory-cache\|caffeine\|guava.*cache" --include="*.json" --include="*.js" --include="*.ts" --include="*.java" . 2>/dev/null | grep -v node_modules | head -5
报告有哪些可用设施。如果不存在缓存层,则推荐默认使用 Redis,并提出可以添加客户端库。
步骤 2:找出适合缓存的 DB 读取
扫描 DB 查询模式,并根据缓存适用性进行分类:
高价值缓存候选项(读取频繁、变更较少):
- 用户资料、设置、偏好
- 产品/商品详情
- 配置表
- 参考数据(存储在 DB 中的分类、标签、枚举)
- 计算成本较高的聚合数据(计数、总和)
不适合缓存的候选项(跳过这些):
- 实时数据(实时库存数量、聊天消息)
- 每个会话的数据
- 每次写入都会变化的数据(例如每个请求都会递增的计数器)
# Find the most-called query patterns
grep -rn "\.find\b\|\.findById\|\.findOne\|\.where\|SELECT" \
--include="*.rb" --include="*.py" --include="*.js" --include="*.ts" \
. 2>/dev/null | grep -v node_modules | grep -v test | grep -v spec | head -30
对于每个候选项,记录:
- 缓存键模式(例如
user:{id}、product:{slug}) - 写入/更新位置(以确定应在哪里添加失效逻辑)
步骤 3:实现缓存优先读取
对于每个适合缓存的读取,应用以下模式:
Redis(Node.js / TypeScript)
async function getUser(id: string) {
const cacheKey = `user:${id}`;
const cached = await redis.get(cacheKey);
if (cached) return JSON.parse(cached);
const user = await db.users.findById(id);
await redis.set(cacheKey, JSON.stringify(user)); // no TTL — permanent until invalidated
return user;
}
Redis(Ruby / Rails)
def find_user(id)
Rails.cache.fetch("user:#{id}") do
User.find(id) # only called on cache miss
end
end
Redis(Python)
def get_user(user_id: int):
cache_key = f"user:{user_id}"
cached = redis_client.get(cache_key)
if cached:
return json.loads(cached)
user = db.session.get(User, user_id)
redis_client.set(cache_key, json.dumps(user.to_dict())) # no expiry
return user
Play Framework(Scala)
def getUser(id: Long): Future[User] = {
cache.getOrElseUpdate(s"user:$id") {
userRepository.findById(id)
}
}
步骤 4:实现写入时的缓存失效
对于每处会修改已缓存数据的位置,在 DB 写入成功后立即添加失效逻辑:
Node.js / TypeScript
async function updateUser(id: string, data: Partial<User>) {
const user = await db.users.update(id, data);
await redis.del(`user:${id}`); // invalidate on change
return user;
}
Ruby / Rails
def update_user(id, attrs)
user = User.find(id).tap { |u| u.update!(attrs) }
Rails.cache.delete("user:#{id}")
user
end
**重要提示:**删除时也要使缓存失效:
async function deleteUser(id: string) {
await db.users.delete(id);
await redis.del(`user:${id}`);
}
对于列表/集合中的任意项发生变化的情况,都要使集合键失效:
await redis.del(`user:${id}`); // the specific item
await redis.del(`users:list`); // any cached list that included this item
步骤 5:缓存键约定
在整个代码库中使用一致的键命名方案:
<entity>:<identifier> → user:123, product:slug-name
<entity>:list:<filter> → products:list:category-5
<entity>:<id>:<relation> → user:123:orders
aggregate:<entity>:<filter> → aggregate:orders:user-123-total
在缓存模块顶部的注释中记录此约定:
// Cache key conventions:
// user:{id} — single user record
// users:list — paginated user list (invalidate on any user change)
// user:{id}:orders — orders belonging to a user
步骤 6:缓存预热(可选)
对于绝不能在冷启动时未命中的关键数据,在应用启动时添加预热步骤:
async function warmCache() {
const activeUsers = await db.users.findActiveUsers();
await Promise.all(
activeUsers.map(u => redis.set(`user:${u.id}`, JSON.stringify(u)))
);
}
步骤 7:总结
报告已实现的内容:
## Cache Strategy Applied
### Cache layer: Redis (permanent, no TTL)
### Reads cached (X total)
- user:{id} — UserService.getUser()
- product:{slug} — ProductService.findBySlug()
- ...
### Invalidation points added (Y total)
- user:{id} — UserService.updateUser(), UserService.deleteUser()
- product:{slug} — ProductService.updateProduct()
- ...
### Estimated DB load reduction
Before: ~X DB queries/min
After: ~Y DB queries/min (first-read only, then cache)
注意:对于来自第三方 API 或其他无法拦截写入的数据,可将较长的 TTL(数小时,而非数分钟)作为后备方案;但应将这些数据单独标记,以便在有失效 Webhook 可用时重新评估。
评论
支持 Markdown