feat(cache): TMDB / 豆瓣 / Bangumi 接入统一元数据缓存与策略表
- 策略表「来源 × 资源类型」(metaCachePolicy):搜索 / 识别 7 天(空 1 天)、详情 / 季 / 剧集组 30 天(空 1 天)、 榜单 / 热门 / 每日放送 6 小时(空 1 小时)、人物 / 图片 90 天;取值来自 storage.cacheTtl, 追加 tmdbListHours / tmdbListEmptyHours / imageDays / metaDetailDays / metaMissDays,metaSearchDays 缺省改 7 天 - TMDB 客户端共享缓存端口加 fetch(带请求类别与空结果判定),服务端整条读取交给 MetadataCache; 识别缓存、douban.request / bangumi.request、douban.subject / bangumi.subject、meta.subject_miss、discover.match 都走这一层 - 豆瓣 / bgm 条目只在「不存在」时记查无,网络失败 / 被拦不缓存(本进程冷却 30 分钟) - 关注中作品(isEntityWatched 口径,含选中的剧集组)的 TMDB 条目写入 pinned、命中续期 - 各进程启动 LISTEN 失效通知并每分钟把命中计数写 cache_stat;GET /cache/regions 给元数据 region 带近 7 天命中统计 Co-Authored-By: Claude Opus 5.5 <[email protected]> Claude-Session: https://claude.ai/code/session_01Cxwgnn2pbzQ7rXv3rq7fei
This commit is contained in:
1 parent
ad472a1a19
commit
1dca95f883
16 files changed
+687
-137
No files matched your search
@@ -82,6 +82,27 @@ export function tmdbImageUrl(path: string | null | undefined, size: TmdbImageSiz
|
||||
export const CacheRegionName = z.string().regex(/^[a-z0-9][a-z0-9._-]{0,63}$/, "缓存区名不合法");
|
||||
export const CacheRegionParam = z.object({ region: CacheRegionName });
|
||||
|
||||
/**
|
||||
* 命中统计口径(docs/runbook/cache.md):各进程在内存里计数,每分钟把增量加进 cache_stat(按天 × region),
|
||||
* 接口返回最近 7 天各进程之和 + 当前进程还没写入的部分(别的进程最近不到一分钟的计数不在内)。
|
||||
* hitRate = (l1Hits + l2Hits + staleHits) / (l1Hits + l2Hits + staleHits + misses);没有读取时为 null。
|
||||
*/
|
||||
export const CacheHitStats = z.object({
|
||||
/** 进程内存(一级)命中 */
|
||||
l1Hits: NonNegInt,
|
||||
/** PG(二级)命中 */
|
||||
l2Hits: NonNegInt,
|
||||
/** 过期在宽限期内、先返回旧值的次数 */
|
||||
staleHits: NonNegInt,
|
||||
misses: NonNegInt,
|
||||
/** 回源失败(网络、被拦、5xx) */
|
||||
failures: NonNegInt,
|
||||
/** 后台刷新次数 */
|
||||
refreshes: NonNegInt,
|
||||
hitRate: z.number().min(0).max(1).nullable(),
|
||||
});
|
||||
export type CacheHitStats = z.infer<typeof CacheHitStats>;
|
||||
|
||||
export const CacheRegionStat = z.object({
|
||||
region: z.string(),
|
||||
/** 未过期条目数 */
|
||||
@@ -92,6 +113,8 @@ export const CacheRegionStat = z.object({
|
||||
bytes: NonNegInt,
|
||||
/** 最近一次写入 */
|
||||
lastWriteAt: Timestamp.nullable(),
|
||||
/** 命中统计(统一元数据缓存的 region 才有):最近 7 天,见 CacheHitStats */
|
||||
hits: CacheHitStats.optional(),
|
||||
});
|
||||
export type CacheRegionStat = z.infer<typeof CacheRegionStat>;
|
||||
|
||||
|
||||
@@ -578,8 +578,19 @@ export const StorageCacheTtl = z.object({
|
||||
metaSearchDays: Days,
|
||||
/** 豆瓣 / bgm 榜单、热门、每日放送等列表请求(小时) */
|
||||
metaListHours: z.number().int().min(1).max(720),
|
||||
/** 豆瓣 / bgm 空结果(小时,对照 MP empty_ttl) */
|
||||
/** 豆瓣 / bgm 列表空结果(小时,对照 MP empty_ttl);搜索空结果改按 metaMissDays */
|
||||
metaEmptyHours: z.number().int().min(1).max(720),
|
||||
// ── 统一元数据缓存(2026-10-06,docs/runbook/cache.md 策略表);旧库缺这些字段时按缺省补 ──
|
||||
/** 豆瓣 / bgm 条目详情(douban.subject / bangumi.subject)与发现页条目 → TMDB 匹配(discover.match) */
|
||||
metaDetailDays: Days,
|
||||
/** 豆瓣 / bgm 查无结果:搜索空结果、条目不存在(meta.subject_miss) */
|
||||
metaMissDays: Days,
|
||||
/** TMDB 榜单 / 热门 / 趋势 / 按条件筛选(小时) */
|
||||
tmdbListHours: z.number().int().min(1).max(720),
|
||||
/** TMDB 列表空结果(小时) */
|
||||
tmdbListEmptyHours: z.number().int().min(1).max(720),
|
||||
/** TMDB 人物 / 图片列表(/person/*、/images) */
|
||||
imageDays: Days,
|
||||
});
|
||||
export type StorageCacheTtl = z.infer<typeof StorageCacheTtl>;
|
||||
|
||||
@@ -608,9 +619,14 @@ export const DEFAULT_STORAGE_SETTINGS: StorageSettings = {
|
||||
tmdbMissDays: 1,
|
||||
siteDetailDays: 7,
|
||||
webSearchDays: 1,
|
||||
metaSearchDays: 1,
|
||||
metaSearchDays: 7,
|
||||
metaListHours: 6,
|
||||
metaEmptyHours: 1,
|
||||
metaDetailDays: 30,
|
||||
metaMissDays: 1,
|
||||
tmdbListHours: 6,
|
||||
tmdbListEmptyHours: 1,
|
||||
imageDays: 90,
|
||||
},
|
||||
};
|
||||
|
||||
|
||||
Reference in new issue
Block a user