feat(web): 设置缓存分区显示命中率
缓存区表加「命中率」列(近 7 天、三个进程合计)与内存 / 数据库 / 旧值 / 未命中明细; 缓存有效期卡片补 TMDB 人物与图片、豆瓣 / Bangumi 搜索与条目。 Co-Authored-By: Claude Opus 5.5 <[email protected]> Claude-Session: https://claude.ai/code/session_01Cxwgnn2pbzQ7rXv3rq7fei
This commit is contained in:
1 parent
1535d8664e
commit
81dca5d664
2 files changed
+93
-4
No files matched your search
@@ -1,4 +1,4 @@
|
||||
import { cacheRegionLabel } from "@umt/contracts";
|
||||
import { type CacheHitStats, cacheRegionLabel } from "@umt/contracts";
|
||||
import { useState } from "react";
|
||||
import { toast } from "sonner";
|
||||
import {
|
||||
@@ -32,6 +32,8 @@ export type CacheRow = {
|
||||
expired: number;
|
||||
bytes: number;
|
||||
lastWriteAt: string | null;
|
||||
/** 统一元数据缓存的区才有:最近 7 天命中统计 */
|
||||
hits?: CacheHitStats | undefined;
|
||||
};
|
||||
|
||||
/** 区名 → 行:有中文名的用中文名,没有的原样显示 region */
|
||||
@@ -39,6 +41,23 @@ export function cacheRow(r: Omit<CacheRow, "label">): CacheRow {
|
||||
return { ...r, label: cacheRegionLabel(r.region) ?? r.region };
|
||||
}
|
||||
|
||||
/** 命中率文字:没有统计的区显示「—」,有统计没读取显示「暂无读取」 */
|
||||
export function hitRateText(h: CacheHitStats | undefined): string {
|
||||
if (!h) return "—";
|
||||
if (h.hitRate === null) return "暂无读取";
|
||||
return `${Math.round(h.hitRate * 1000) / 10}%`;
|
||||
}
|
||||
|
||||
/** 命中明细:内存 / 数据库 / 旧值 / 未命中,有失败与后台刷新时附上 */
|
||||
export function hitDetailText(h: CacheHitStats): string {
|
||||
const parts = [`内存 ${h.l1Hits}`, `数据库 ${h.l2Hits}`];
|
||||
if (h.staleHits) parts.push(`旧值 ${h.staleHits}`);
|
||||
parts.push(`未命中 ${h.misses}`);
|
||||
if (h.failures) parts.push(`回源失败 ${h.failures}`);
|
||||
if (h.refreshes) parts.push(`后台刷新 ${h.refreshes}`);
|
||||
return parts.join(" · ");
|
||||
}
|
||||
|
||||
function CacheRegionsBlock() {
|
||||
const regions = useCacheRegions();
|
||||
const images = useImageCache();
|
||||
@@ -85,6 +104,19 @@ function CacheRegionsBlock() {
|
||||
</div>
|
||||
),
|
||||
},
|
||||
{
|
||||
key: "hitRate",
|
||||
header: "命中率",
|
||||
align: "right",
|
||||
cell: (r) => (
|
||||
<div className="tabular-nums">
|
||||
{hitRateText(r.hits)}
|
||||
{r.hits && r.hits.hitRate !== null ? (
|
||||
<p className="text-meta text-muted-foreground">{hitDetailText(r.hits)}</p>
|
||||
) : null}
|
||||
</div>
|
||||
),
|
||||
},
|
||||
{
|
||||
key: "bytes",
|
||||
header: "占用",
|
||||
@@ -121,7 +153,7 @@ function CacheRegionsBlock() {
|
||||
section="cache"
|
||||
anchor="regions"
|
||||
title="缓存区"
|
||||
description="清空后下次用到时重新请求,不影响作品、资源和文件。已过期的条目由定时任务清理。"
|
||||
description="清空后下次用到时重新请求,不影响作品、资源和文件。已过期的条目由定时任务清理。命中率按最近 7 天、三个进程合计。"
|
||||
plain
|
||||
>
|
||||
<DataTable<CacheRow>
|
||||
@@ -224,7 +256,16 @@ export const IMAGES_CARD: CardSpec = {
|
||||
}),
|
||||
};
|
||||
|
||||
const TTL_KEYS = ["tmdbSearchDays", "tmdbDetailDays", "tmdbMissDays", "siteDetailDays", "webSearchDays"] as const;
|
||||
const TTL_KEYS = [
|
||||
"tmdbSearchDays",
|
||||
"tmdbDetailDays",
|
||||
"tmdbMissDays",
|
||||
"imageDays",
|
||||
"metaSearchDays",
|
||||
"metaDetailDays",
|
||||
"siteDetailDays",
|
||||
"webSearchDays",
|
||||
] as const;
|
||||
const ttlField = (k: string, label: string, hint?: string) => ({
|
||||
name: `storage.cacheTtl.${k}`,
|
||||
label,
|
||||
@@ -234,11 +275,14 @@ const ttlField = (k: string, label: string, hint?: string) => ({
|
||||
export const TTL_CARD: CardSpec = {
|
||||
anchor: "ttl",
|
||||
title: "缓存有效期",
|
||||
description: "单位天。追踪中的作品命中 TMDB 详情缓存时自动续期。",
|
||||
description: "单位天。过期后先用旧值、后台刷新;关注中的作品命中详情缓存时自动续期,不会因条数上限被清掉。",
|
||||
fields: [
|
||||
ttlField("tmdbSearchDays", "TMDB 搜索与识别结果"),
|
||||
ttlField("tmdbDetailDays", "TMDB 详情"),
|
||||
ttlField("tmdbMissDays", "TMDB 空结果", "搜索无结果、未识别时的负缓存"),
|
||||
ttlField("imageDays", "TMDB 人物与图片"),
|
||||
ttlField("metaSearchDays", "豆瓣 / Bangumi 搜索"),
|
||||
ttlField("metaDetailDays", "豆瓣 / Bangumi 条目"),
|
||||
ttlField("siteDetailDays", "站点详情页内容"),
|
||||
ttlField("webSearchDays", "网页搜索结果"),
|
||||
],
|
||||
|
||||
Reference in new issue
Block a user