- Automation section gets storage budget, retention and cache TTL cards bound to settings.storage - Metadata section drops deprecated cache.tmdbTtlHours / cache.emptyResultTtlSec / identity.poolArchiveDays inputs - cache.tmdbMaxSize default 256 -> 5000; migration 0051 bumps stored values still at the old default Co-Authored-By: Claude Opus 5.5 <[email protected]>
11 KiB
存储预算与保留(CACHE-2)
按 4C / 8G / 100G 的机器分配:PG ≤ 25 GB(20 GB 告警)、图片缓存 20 GB、备份 20 GB(14 份)、会话与证据 10 GB。 流水数据按保留期定时分批删除,缓存有效期延长,容量不够时按 LRU 淘汰。本轮只有后端与配置,设置页 UI 后续接。
设置 settings.storage
三组,都在「设置 → 自动化 → 存储与保留」(分区锚点 storage,UI 待接)。PATCH 可以只给子字段:
PATCH /api/v1/settings {"storage":{"retentionDays":{"observation":60}}}
| 字段 | 缺省 | 说明 |
|---|---|---|
budgets.pgDataGb / pgDataWarnGb |
25 / 20 | PG 库体积(pg_database_size,不含 WAL)。≥ 告警线 warn,≥ 预算 critical |
budgets.imagesGb |
20 | 图片代理缓存容量。超出由 cache.images_lru 按最久未访问删到 90% |
budgets.backupsGb |
20 | DATA_DIR/backups。≥ 80% warn,≥ 预算 critical。份数仍由 BACKUP_KEEP 控制 |
budgets.sessionsEvidenceGb |
10 | Agent 会话目录 + DATA_DIR/tmp/evidence。≥ 80% warn,≥ 预算 critical |
retentionDays.observation |
90 | Feed 观察记录 |
retentionDays.sseEvent |
7 | SSE 补读事件(发布方另按条数裁剪 10000 条) |
retentionDays.heartbeatHours |
24 | 进程心跳采样(小时)。历史曲线最多只能看这么久 |
retentionDays.taskRun |
60 | 已结束的业务任务 task、引擎 run(步骤级联)、运行元数据 |
retentionDays.agentEvent |
90 | 已结束会话的事件流与已消费消息。agent_run 本身保留 |
retentionDays.audit |
180 | 审计事件(能力调用审计除外) |
retentionDays.capabilityStats |
90 | 能力调用审计(target_type=capability)与已结束的 capability_invocation |
retentionDays.identityPoolArchive |
30 | 待识别池 unmatched 条目无新证据多少天后归档(不删) |
cacheTtl.tmdbSearchDays |
7 | TMDB /search/*、/find/* 响应与识别缓存 |
cacheTtl.tmdbDetailDays |
30 | TMDB 详情 / 季。追踪中的作品命中时续期 |
cacheTtl.tmdbMissDays |
1 | TMDB 空结果与未识别负缓存 |
cacheTtl.siteDetailDays |
7 | 站点详情页扩展内容(kv_cache site.detail) |
cacheTtl.webSearchDays |
1 | 网页搜索结果 |
被取代的旧字段(保留在 schema 里兼容已存 JSON,服务端不再读取,已列入 DEPRECATED_SETTINGS_FIELDS):
cache.tmdbTtlHours → cacheTtl.tmdbSearchDays / tmdbDetailDays,cache.emptyResultTtlSec → cacheTtl.tmdbMissDays,
identity.poolArchiveDays → retentionDays.identityPoolArchive。设置页「元数据」里这三项接 UI 时要换成新字段。
cache.tmdbMaxSize(条数上限,缺省 5000)仍然有效。TTL 拉长后条数上限会先起作用,需要时调大。
缓存有效期
只改 TTL 的取值来源,缓存语义还是对照 MP(见 porting/moviepilot-tmdb-cache.md)。
- TMDB 请求缓存:键以
/search/或/find/开头的用搜索 TTL,其余用详情 TTL。results为空的用未命中 TTL。 - TMDB 识别缓存:正常条目用搜索 TTL(识别结果就是名称到条目的映射),负缓存
{id:0}用未命中 TTL。 - 追踪中的作品续期:作品有未结束的订阅(active / paused / pending)且有已接受的 TMDB 身份,
它的详情键(
/movie/<id>?…、/tv/<id>…)命中时把过期时间推到「现在 + 详情 TTL」,只延长不缩短。追踪集合每进程缓存 5 分钟。 - 站点详情:
kv_cacheTTL 取max(站点详情缓存期, siteDetailDays)。是否重抓仍按站点详情缓存期 (sites.defaultDetailCacheSec,站点定义可更长)判断,下载前复核促销和 H&R 走的是这条。 - 改了 TTL:新写入的条目按新值,已有条目保持原过期时间。
图片缓存容量 LRU
- 读盘命中时把文件 atime 显式写成当前时间(
utimes,不依赖挂载的 relatime),mtime 不动。 cache.images_lru(维护组,30 分钟):总量超过imagesGb时按 atime 从旧到新删到预算的 90%;没超不动。cache.images_clean(每天,MPclear_temp)照旧按 mtime 删超过cache.globalImageCacheDays天的文件,作兜底。- 存储面板里图片超过预算 10% 才告警(说明 LRU 没跟上),不给 critical。
保留清理任务
都在维护组,开关和周期在定时任务看板改。首次登记在一个周期后才第一次运行。
| key | 目标 | 默认周期 | 删除条件 |
|---|---|---|---|
storage.retention.observation |
observation |
6 小时 | created_at 早于保留期 |
storage.retention.sse_event |
sse_event |
1 小时 | at 早于保留期 |
system.heartbeat_cleanup |
心跳 | 1 小时 | 进程行 24 小时无心跳;采样早于 heartbeatHours |
storage.retention.task_run |
task、引擎 workflow_runs、tasks_run_meta / tasks_fact / tasks_op_ledger |
6 小时 | 早于保留期且已结束(task:succeeded / failed / cancelled / dead;run:completed / succeeded / failed / canceled;账本只删 succeeded) |
storage.retention.agent_event |
agent_run_event、agent_run_message |
6 小时 | 早于保留期且会话已结束;消息只删已消费的 |
storage.retention.audit |
audit_event |
1 天 | at 早于保留期,target_type 不是 capability |
storage.retention.capability_stats |
audit_event(capability)、capability_invocation |
1 天 | 早于保留期;调用记录不删 running |
storage.retention.identity_pool |
identity_candidate |
6 小时 | pending + unmatched,coalesce(last_attempt_at, created_at) 早于保留期 → 改为 archived |
storage.analyze |
全库 | 7 天 | ANALYZE |
cache.images_lru |
图片缓存 | 30 分钟 | 见上 |
cache.sweep |
kv_cache |
10 分钟 | 已过期 |
分批删除(modules/system/retention.ts 的 deleteInBatches):
- 每批 5000 行:
delete … where ctid = any(array(select ctid … where <条件> limit 5000)),单独事务。 - 每批
statement_timeout15 秒、lock_timeout3 秒。超时就结束本次运行,不算失败,摘要写「未删完,下次继续」。 - 每次运行最多 5 分钟,批间停 50 ms。第一次上线积压多时会分几次删完。
- 删除后空间由 autovacuum 回收复用,不会立即还给文件系统。确实要缩文件用
pg_repack或停机VACUUM FULL。
迁移 0050_storage_retention:给 observation(created_at)、sse_event(at)、audit_event(at)、capability_invocation(created_at)、
agent_run_message(created_at)、tasks_fact(created_at)、tasks_run_meta(created_at)、tasks_op_ledger(started_at) 补索引;
按表设 autovacuum:
- 追加加批量删除的大表(observation、sse_event、audit_event、agent_run_event、process_heartbeat_sample、capability_invocation、task、tasks_fact、tasks_run_meta):
vacuum_scale_factor=0.05、analyze_scale_factor=0.02、vacuum_insert_scale_factor=0.1。 - 频繁原地更新的小表(process_heartbeat、rate_limit_bucket、scheduled_job、kv_cache、outbox_event):
vacuum_scale_factor=0.02、vacuum_threshold=100、analyze_scale_factor=0.05。
查看:select relname, reloptions from pg_class where reloptions is not null;
API
GET /system/storage(admin)
| 字段 | 含义 |
|---|---|
level |
各项最差档:ok / warn / critical,取不到的项 unknown 不参与 |
db |
库体积对照 pgDataGb / pgDataWarnGb;tables 为体积前 15 的表(总 / 表 / 索引字节、估算行数、死元组、最近 autovacuum / analyze) |
kvCache[] |
每个 region 的条数、其中已过期未清理的条数、字节 |
images |
图片缓存用量、文件数、预算 |
backups |
备份目录用量、份数(不含 .partial)、预算 |
sessionsEvidence |
会话目录 + tmp/evidence 合计、预算 |
dataDir[] |
DATA_DIR 一级目录用量,cache/、tmp/ 再细分一层;会话目录在 DATA_DIR 外时单列 |
dirsScannedAt |
目录遍历时间。遍历结果缓存 5 分钟 |
cleanup[] |
每个清理目标:任务 key、保留期说明、开关、最近运行时间 / 状态 / 摘要、下次运行 |
POST /system/storage/cleanup/{target}(admin)
target:observation、sse_event、heartbeat、task_run、agent_event、audit、capability_stats、identity_pool、analyze、images、kv_cache。
和定时任务「立即执行」一样,只设置 run_requested_at,由调度器执行。已在排队或运行中返回 queued:false。
任务还没登记(调度器没同步过)返回 404。触发成功写审计 storage.cleanup。
GET /system/health 的 storage 段
{ level, alerts[] },level 计入总档位。库体积实时取;目录用量只用缓存值,过期时后台刷新,本次不等
(进程刚启动时目录项是 unknown,不参与汇总)。
PG 参数
docker-compose.yml 的 postgres 服务按 4C / 8G 给 PG 3G 内存:
| 参数 | 缺省 | 环境变量 | 经验值 |
|---|---|---|---|
| 容器内存上限 | 3g | PG_MEM_LIMIT |
机器内存的 35–40% |
shared_buffers |
2GB | PG_SHARED_BUFFERS |
容器上限的 60–70%(不超过机器内存的 25–30%) |
effective_cache_size |
5GB | PG_EFFECTIVE_CACHE_SIZE |
机器内存的 60–70%(只影响规划,不占内存) |
work_mem |
32MB | PG_WORK_MEM |
每个排序 / 哈希一份,连接多时调小 |
maintenance_work_mem |
256MB | PG_MAINTENANCE_WORK_MEM |
VACUUM / 建索引用 |
max_wal_size |
2GB | PG_MAX_WAL_SIZE |
批量删除时减少检查点 |
另外 shm_size: 512mb(并行查询的动态共享内存走 /dev/shm)。
按机器调整,例如 2C/4G:PG_MEM_LIMIT=1536m PG_SHARED_BUFFERS=768MB PG_EFFECTIVE_CACHE_SIZE=2GB PG_WORK_MEM=16MB PG_MAINTENANCE_WORK_MEM=128MB;
8C/16G:PG_MEM_LIMIT=6g PG_SHARED_BUFFERS=4GB PG_EFFECTIVE_CACHE_SIZE=10GB PG_WORK_MEM=64MB PG_MAINTENANCE_WORK_MEM=512MB。
写进 .env 后 docker compose up -d postgres 重建容器生效(数据在卷里,不丢)。确认:docker compose exec postgres psql -U umt -c 'show shared_buffers'。
shared_buffers 大于容器上限会在启动时 OOM,两者一起改。
常见问题
- 面板上 PG 超预算:先看
tables前几名。流水表大就临时调短对应保留期,再POST /system/storage/cleanup/<target>; 死元组多说明 autovacuum 跟不上,可手动VACUUM (VERBOSE) <表>。 - 清理摘要一直是「未删完」:积压太多,每次 5 分钟删不完。可以把任务周期临时调短,或在低峰期多触发几次。
- 图片缓存一直 warn:LRU 任务是否启用、最近一次是否失败;磁盘上是否有大量
.tmp残留(不计入用量,可手动删)。 - 历史曲线只能看 24 小时:这是
heartbeatHours的缺省,要看更久就调大。