ワンクリックで
cache-framework
Wavelet 项目专用:当新增或修改业务缓存(RAM/Redis/DB 三层读路径)、缓存失效、多节点 pub/sub 同步、或评估高频读是否应接入缓存时必须使用。本技能说明系统标准缓存框架、参考实现、禁止写法与分布式一致性要求。
Codex または Claude でインストール この Prompt をコピーして Codex、Claude、または他のアシスタントに貼り付けると、Skill ページを確認してインストールできます。
メニュー
Wavelet 项目专用:当新增或修改业务缓存(RAM/Redis/DB 三层读路径)、缓存失效、多节点 pub/sub 同步、或评估高频读是否应接入缓存时必须使用。本技能说明系统标准缓存框架、参考实现、禁止写法与分布式一致性要求。
Codex または Claude でインストール この Prompt をコピーして Codex、Claude、または他のアシスタントに貼り付けると、Skill ページを確認してインストールできます。
SOC 職業分類に基づく
Wavelet 项目专用:当新增或修改 ClickHouse 批量写入、接入 internal/db/batchwriter、将业务域异步 flush 到分析表、迁移 risk_control/节点访问日志/可观测时序写入、或评估 async_insert 与背压策略时必须使用。本技能指导分层职责、各域独立 Writer 实例、repository 批量 API 与禁止写法。
Wavelet 项目专用:当新增或修改数据库表结构、索引、初始化数据、系统配置 seed、模板 seed、默认管理员、goose SQL 迁移、internal/db/migrator、ClickHouse 分析库 DDL 或数据库升级流程时必须使用。本技能指导在 internal/db/migrator/goose 下编写 PostgreSQL/SQLite 双方言 SQL 迁移,以及在 goose/clickhouse 下编写 ClickHouse 单方言分析表迁移,并完成验证。
Provides comprehensive code review guidance for React 19, Vue 3, Angular 17+, Svelte 5, Rust, TypeScript, Java, PHP, Python, Django, Go, C#/.NET, Kotlin, Swift, NestJS, C/C++, and more. Helps catch bugs, improve code quality, and give constructive feedback. Use when: reviewing pull requests, conducting PR reviews, code review, reviewing code changes, establishing review standards, mentoring developers, architecture reviews, security audits, checking code quality, finding bugs, giving feedback on code.
Wavelet 项目专用:当业务需要上传文件、读取已上传文件、在 Worker/任务中程序化摄取字节流、选择存储引擎能力、或排查 w_uploads / 文件统计异常时必须使用。本技能指导 storage 与 upload 分层、upload.Ingest 策略选型、前后端接入与禁止旁路写表。
Use when reviewing Go code or checking code against community style standards. Also use proactively before submitting a Go PR or when reviewing any Go code changes, even if the user doesn't explicitly request a style review. Does not cover language-specific syntax — delegates to specialized skills.
Use when writing concurrent Go code — goroutines, channels, mutexes, or thread-safety guarantees. Also use when parallelizing work, fixing data races, or protecting shared state, even if the user doesn't explicitly mention concurrency primitives. Does not cover context.Context patterns (see go-context).
| name | cache-framework |
| description | Wavelet 项目专用:当新增或修改业务缓存(RAM/Redis/DB 三层读路径)、缓存失效、多节点 pub/sub 同步、或评估高频读是否应接入缓存时必须使用。本技能说明系统标准缓存框架、参考实现、禁止写法与分布式一致性要求。 |
开始前阅读根目录 AGENTS.md(含 Skill 关联索引)。Wavelet 标准读路径为 本地 RAM → Redis → PostgreSQL(由快到慢),不是 DB 优先。
详细性能背景见 docs/PERFORMANCE.md。
| 关联 | 何时一并阅读 |
|---|---|
| database-migration | 缓存对象对应新表/列/索引,或 seed 变更 |
| new-setting | 系统配置类缓存(GetSystemConfigByKey、ListSystemConfigsByKeys) |
| file-upload | 上传元数据 upload:meta:{id}、ingest/remove/cleanup 失效钩子 |
| clickhouse-batchwriter | 分析写入走 batchwriter,不要用本技能模式缓存 CH flush 队列 |
| new-api | 在 Handler 层接入 GetXxxCached 或评估高频读 |
| new-async-task | Worker/定时任务变更数据后必须 Invalidate*(如 system:cleanup) |
参考:internal/repository/system_config_cache.go + GetSystemConfigByKey / ListSystemConfigsByKeys。
| 层级 | 技术 | 职责 |
|---|---|---|
| L1 本地 | pkg/cache/ram(Otter v2) | 进程内热数据,最低延迟 |
| L2 共享 | Redis db.GetJSON / SetJSON / HSetJSON + db.PrefixedKey | 跨节点共享,带 TTL 或写穿 |
| L3 权威 | PostgreSQL via db.DB(ctx) | 唯一数据源 |
func GetThingCached(ctx context.Context, key string) (Thing, error) {
ensureThingCacheListener() // 订阅 pub/sub,仅 sync.Once
if v, ok := thingRAM.GetIfPresent(key); ok {
return cloneThing(v), nil
}
if db.Redis != nil {
var v Thing
if err := db.GetJSON(ctx, redisKey(key), &v); err == nil {
thingRAM.Set(key, cloneThing(v))
return v, nil
}
}
v, err := loadThingFromDB(ctx, key)
if err != nil {
return Thing{}, err
}
populateThingCache(ctx, v) // 回写 RAM + Redis
return v, nil
}
DB miss 或业务创建成功后,必须回写上层:
func populateThingCache(ctx context.Context, v Thing) {
thingRAM.Set(v.Key, cloneThing(v))
if db.Redis != nil {
_ = db.SetJSON(ctx, redisKey(v.Key), v, cacheTTL)
}
}
数据变更(Admin 更新、软删除、状态迁移)时:
thingRAM.Invalidate(key) 或 InvalidateAll()Del / HDel 对应 keyfunc InvalidateThingCache(ctx context.Context, key string) error {
ensureThingCacheListener()
thingRAM.Invalidate(key)
if db.Redis != nil {
if err := db.Redis.Del(ctx, db.PrefixedKey(redisKey(key))).Err(); err != nil {
return err
}
publishThingRAMInvalidation(ctx, key) // 只广播 RAM 失效
}
return nil
}
const thingInvalidationChannel = "domain:thing_invalidation"
func startThingCacheInvalidationListener() {
if db.Redis == nil {
return
}
go func() {
pubsub := db.Redis.Subscribe(context.Background(), thingInvalidationChannel)
defer func() { _ = pubsub.Close() }()
for msg := range pubsub.Channel() {
// 解析 payload,Invalidate RAM;勿重复 Del Redis
thingRAM.Invalidate(parsedKey)
}
}()
}
sync.Once 启动监听;ensureListener 必须在 db.Redis == nil 时直接 return,不可消费 Once(否则测试或 Redis 晚初始化时监听器永不启动)。StopThingCacheListener + 重置 Once(参考 StopUploadMetaCacheListener、StopAuthSourceCacheListener)。| 域 | 文件 | L1 | L2 | pub/sub |
|---|---|---|---|---|
| 系统配置 | repository/system_config_cache.go | pkg/cache/store | ❌ 无 Redis 缓存 | system:config_broadcast (别名 system:config_invalidation) ✅ |
| CAPTCHA 运行时 | apps/cap/runtime_settings.go | atomic.Pointer | (借配置 Redis) | 订阅 system:config_invalidation ✅ |
| 上传元数据 | apps/upload/cache/meta_cache.go | Otter | Redis JSON | upload:meta_invalidation ✅ |
| 上传访问白名单 | apps/upload/cache/access_cache.go | 进程内 TTL | (借配置读路径) | upload:file_access_invalidation ✅ |
| Auth Source | repository/auth_source_cache.go | Otter | Redis JSON | oauth:auth_source_invalidation ✅ |
| OAuth 用户/Token | apps/oauth/cache.go | 自研 map | Redis JSON | ❌ 无 pub/sub(历史债) |
| 推送渠道 | repository/push_channel.go | 无 | Redis JSON | ❌ 仅 Redis Del |
| Storage 驱动 | internal/storage/storage.go | RWMutex 快照 | — | storage:config_invalidation ✅ |
pkg/cache/ram.MustNew;禁止自研 map+mutex+TTL,除非有充分理由并文档说明。SetJSON;配置类多条目用 Redis Hash(HSetJSON)。upload:meta:{id});统一 db.PrefixedKey。Reset*RAMCacheForTest 仅清本机 RAMgo test 相关包 + make code-check。| 入口 | 动作 |
|---|---|
ingest.persistUploadRecord 创建成功 | SetUploadMetaCache |
ingest.Remove / RemoveOwned | InvalidateUploadMetaCache |
task/cleanup.go 软删除 pending 文件 | InvalidateUploadMetaCache |
直接 repository.SoftDeleteUpload | 禁止 — 必须走 upload.Remove |
// ❌ 自研 L1,与 pkg/cache/ram 重复
var mu sync.RWMutex
var items = map[uint64]entry{}
// ❌ 只清本机 RAM + Redis,无 pub/sub(多节点 RAM 脏读)
func Invalidate(ctx context.Context, id uint64) {
localDelete(id)
redis.Del(...)
}
// ❌ DB 变更后忘记 Worker 路径
// cleanup 任务删了 upload 行,但未 InvalidateUploadMetaCache
// ❌ 在 Handler 里直接查 DB,绕过已有 GetXxxCached
// ❌ Redis key 不用 PrefixedKey(多环境共 Redis 时冲突)
// ❌ 在 init() 里启动 pub/sub 监听 — 与 bootstrap 规范冲突;用 sync.Once 懒启动
模型 json:"-" 时,Redis DTO 用独立 *RedisRecord struct 显式序列化字段(见 auth_source_cache.go)。
批量接口必须与单 key 一致走 Redis(ListSystemConfigsByKeys 在 RAM miss 后逐 key HGetJSON,再 DB IN)。
可用进程内快照 + 订阅上游 pub/sub(access_cache.go、cap/runtime_settings.go),不必强行 Redis L2。
沿用 oauth/cache.go;新增逻辑调用 SetCachedUser / SetCachedToken 预热,变更调用 InvalidateCachedUser / InvalidateCachedToken。
go test ./internal/repository/... ./internal/apps/upload/cache/...
make code-check
pkg/cache/ram(或已文档化的例外)ensureListener + pub/sub 清他机 RAMpkg/cache/ram/cache.gointernal/db/redis.go(GetJSON, SetJSON, HGetJSON, PrefixedKey)internal/repository/system_config_cache.gointernal/apps/upload/cache/meta_cache.gointernal/repository/auth_source_cache.godocs/PERFORMANCE.md