Co-Authored-By: Claude Opus 5.5 <[email protected]> Claude-Session: https://claude.ai/code/session_01Cxwgnn2pbzQ7rXv3rq7fei
272 lines
21 KiB
Markdown
272 lines
21 KiB
Markdown
# 存储预算与保留(CACHE-2)
|
||
|
||
按 4C / 8G / 100G 的机器分配:PG ≤ 25 GB(20 GB 告警)、图片缓存 20 GB、备份 20 GB(14 份)、会话与证据 10 GB。
|
||
流水数据按保留期定时分批删除,缓存有效期延长,容量不够时按 LRU 淘汰。本轮只有后端与配置,设置页 UI 后续接。
|
||
|
||
## 设置 `settings.storage`
|
||
|
||
三组,在「设置 → 系统」(存储预算、数据保留)与「设置 → 缓存」(缓存有效期)。PATCH 可以只给子字段:
|
||
|
||
```sh
|
||
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`;成功的操作账本 |
|
||
| `retentionDays.agentEvent` | 90 | 已结束会话的事件流与已消费消息。`agent_run` 本身保留 |
|
||
| `retentionDays.audit` | 180 | 审计事件(能力调用审计除外) |
|
||
| `retentionDays.capabilityStats` | 90 | 能力调用审计(`target_type=capability`)与已结束的 `capability_invocation` |
|
||
| `retentionDays.identityPoolArchive` | 30 | 待识别池 unmatched 条目无新证据多少天后归档(不删) |
|
||
| `retentionDays.engineRun` | 14 | 已结束的引擎 run(步骤级联)与运行元数据 / 事实 / 信号;仍被保留中的业务任务引用的跟随任务 |
|
||
| `retentionDays.outboxEvent` | 30 | 已投递的 outbox 事件(只删无幂等键或一次性幂等键的,见「保留规则」) |
|
||
| `retentionDays.resolutionAttempt` | 30 | 识别尝试记录;每条资源至少保留最近 3 次 |
|
||
| `retentionDays.identityResolved` | 60 | 已识别(资源仍绑定作品)的待识别池条目;忽略、归档的不删 |
|
||
| `retentionDays.siteSnapshot` | 30 | 站点快照;每个站点最近一次成功的列表快照保留 |
|
||
| `cacheTtl.tmdbSearchDays` | 7 | TMDB `/search/*`、`/find/*` 响应与识别缓存 |
|
||
| `cacheTtl.tmdbDetailDays` | 30 | TMDB 详情 / 季 / 剧集组。关注中的作品命中时续期 |
|
||
| `cacheTtl.tmdbMissDays` | 1 | TMDB 空结果与未识别负缓存 |
|
||
| `cacheTtl.tmdbListHours` | 6 | TMDB 榜单 / 热门 / 趋势 / 按条件筛选(小时) |
|
||
| `cacheTtl.tmdbListEmptyHours` | 1 | TMDB 列表空结果(小时) |
|
||
| `cacheTtl.imageDays` | 90 | TMDB 人物 / 图片列表(`/person/*`、`…/images`) |
|
||
| `cacheTtl.metaSearchDays` | 7 | 豆瓣 / bgm 搜索请求(2026-10-06 起缺省 7 天,以前 1 天) |
|
||
| `cacheTtl.metaDetailDays` | 30 | 豆瓣 / bgm 条目、发现页条目 → TMDB 匹配 |
|
||
| `cacheTtl.metaMissDays` | 1 | 豆瓣 / bgm 搜索空结果、条目不存在 |
|
||
| `cacheTtl.metaListHours` | 6 | 豆瓣 / bgm 榜单、热门、每日放送等列表(小时) |
|
||
| `cacheTtl.metaEmptyHours` | 1 | 豆瓣 / bgm 列表空结果(小时) |
|
||
| `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](../porting/moviepilot-tmdb-cache.md))。
|
||
2026-10-06 起 TMDB / 豆瓣 / Bangumi 按「来源 × 资源类型」统一取值、过期先用旧值(宽限期 TTL 的 50%,最多 7 天),
|
||
完整策略表、两级缓存与关注作品的 pinned / 续期见 [cache.md](cache.md)「统一元数据缓存」。
|
||
|
||
- TMDB 请求缓存:键以 `/search/` 或 `/find/` 开头的用搜索 TTL;榜单类用列表 TTL;`/person/*`、`…/images` 用图片 TTL;其余用详情 TTL。`results` 为空的用未命中 TTL。
|
||
- TMDB 识别缓存:正常条目用搜索 TTL(识别结果就是名称到条目的映射),负缓存 `{id:0}` 用未命中 TTL。
|
||
- 关注中的作品续期:口径同 `isEntityWatched`,它的详情 / 季 / 剧集组 / 图片键命中时把过期时间推到「现在 + 本类 TTL」,
|
||
只延长不缩短,并标 pinned(条数上限淘汰时跳过)。关注集合每进程缓存 5 分钟。
|
||
- 站点详情:`kv_cache` TTL 取 `max(站点详情缓存期, siteDetailDays)`。是否重抓仍按站点详情缓存期
|
||
(`sites.defaultDetailCacheSec`,站点定义可更长)判断,下载前复核促销和 H&R 走的是这条。
|
||
- 改了 TTL:新写入的条目按新值,已有条目保持原过期时间。
|
||
|
||
## 图片缓存容量 LRU
|
||
|
||
- 读盘命中时把文件 atime 显式写成当前时间(`utimes`,不依赖挂载的 relatime),mtime 不动。
|
||
- `cache.images_lru`(维护组,30 分钟):总量超过 `imagesGb` 时按 atime 从旧到新删到预算的 90%;没超不动。
|
||
- `cache.images_clean`(每天,MP `clear_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`(`step_attempts` 级联)、`tasks_run_meta` / `tasks_fact` / `workflow_signals` / `tasks_op_ledger` | 6 小时 | 见下「保留规则」 |
|
||
| `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.pipeline` | `outbox_event`、`resolution_attempt`、`identity_candidate`(已识别)、`site_snapshot` | 每天 04:40(cron) | 见下「保留规则」 |
|
||
| `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 分钟 | 已过硬过期(宽限期内的旧值保留) |
|
||
|
||
### 保留规则(2026-10-06 数据库清理)
|
||
|
||
规则写在 `modules/system/retention.ts`(每个任务的 `rules`),定时任务、`POST /system/storage/cleanup/{target}` 与
|
||
`scripts/db-retention.ts` 共用。原则:只删已结束、过期、没有被引用的行;拿不准的不删。
|
||
|
||
| 表 | 删除条件(都要同时满足) | 缺省保留 |
|
||
|---|---|---|
|
||
| `task` | 已结束(succeeded / failed / cancelled / dead)、`created_at` 早于保留期、没有被 `task.parent_task_id` / `acquisition_plan` / `asset_operation` / `agent_run` / `artifact` / `decision` / `reconcile_run` / `organization_record` 引用(这些外键是 set null,删了会断开引用) | `taskRun` 60 天 |
|
||
| 引擎 `workflow_runs` | 终态(completed / succeeded / failed / canceled);`created_at` 与结束时间都早于保留期;没有被「保留中的」`task.engine_run_id` 引用;有父 run 的,父 run 也已结束 | `engineRun` 14 天 |
|
||
| 引擎 `step_attempts` | 随 run 级联(外键 on delete cascade) | 同上 |
|
||
| `tasks_run_meta` / `tasks_fact` / 引擎 `workflow_signals` | 早于引擎保留期,且对应 run 已不存在或本次可删 | 同上 |
|
||
| `tasks_op_ledger` | `succeeded` 且 `started_at` 早于保留期(不变) | `taskRun` |
|
||
| `outbox_event` | `status = dispatched`、`at` 早于保留期,且幂等键为空或属于 `release.observed:` / `feed.due:` / `decision.created:` / `download.submitted:`(只在创建时发一次或带时间槽)。其它幂等键(生命周期事件 `media.watched/…`、`decision.updated:<id>` 等)靠「全表唯一」做永久去重,删了会重发,保留;pending / failed / dead 不删 | `outboxEvent` 30 天 |
|
||
| `resolution_attempt` | `created_at` 早于保留期,且同一资源有至少 3 次更新的尝试(每条资源留最近 3 次) | `resolutionAttempt` 30 天 |
|
||
| `identity_candidate` | `status = resolved`、`updated_at` 早于保留期、资源仍绑定作品、没有 `metadata_enrichment` 引用、`decision_id` 不指向未结事项。忽略(dismissed,是「不再自动识别」的标记)与归档(archived,仍可手动指定)不删 | `identityResolved` 60 天 |
|
||
| `site_snapshot` | `started_at` 早于保留期、不是 running、不是该站点最近一次成功(ok / partial)的列表快照(periodic / manual / backfill;站点刷新间隔按它判断) | `siteSnapshot` 30 天 |
|
||
| `scheduled_job_run` | 不按天数:每个任务只留最近 50 次(`trimRuns`,原有) | — |
|
||
| `cache_stat` | 不按天数:写入统计时顺带删 30 天前的行(原有) | — |
|
||
|
||
引擎语义核对(openworkflow 0.10.1):run 幂等键只在 24 小时内去重(`DEFAULT_RUN_IDEMPOTENCY_PERIOD_MS`),
|
||
认领 / 恢复只看 pending / running / sleeping,父 run 在 step 里等子 run 时读子 run 状态。
|
||
所以删 14 天前的终态 run 不影响去重与恢复;父 run 未结束的子 run 保留。业务侧去重看 `task` 表(`kind + idempotency_key`)
|
||
与 `enqueueSweep` 的时间槽,不依赖引擎旧 run。
|
||
|
||
删得多(≥ 1000 行)的表,任务结束时顺带 `ANALYZE`。
|
||
|
||
### 脚本 `scripts/db-retention.ts`
|
||
|
||
和定时任务同一套规则,用来对真实库先看清单再删(lessons 第 22 条):
|
||
|
||
```sh
|
||
# 只读:每张表将删多少行(缺省)
|
||
DATABASE_URL=postgres://umt:***@<host>:5432/umt deno run -A scripts/db-retention.ts
|
||
# 只看部分目标
|
||
DATABASE_URL=… deno run -A scripts/db-retention.ts --only task_run,pipeline
|
||
# 执行(分批、每批单独事务;本次不限 5 分钟,最多 1 小时;删完 ANALYZE)
|
||
DATABASE_URL=… deno run -A scripts/db-retention.ts --apply
|
||
```
|
||
|
||
保留天数取库里的 `settings.storage`(缺字段按缺省)。在宿主机源码目录里跑(app 镜像是编译产物,不带脚本);
|
||
部署库的 postgres 没有映射端口,需要临时映射或在 compose 网络里起一个带 deno 的容器去连。
|
||
引擎 schema 不是 `openworkflow` 时设 `ENGINE_SCHEMA`。不含心跳、图片、kv_cache(各自任务管)。
|
||
测试库按部署规模造数(2.35 万 run / 1.1 万 outbox / 1.7 万识别尝试)实测:删 3.7 万行用时约 1.4 秒。
|
||
|
||
分批删除(`modules/system/retention.ts` 的 `deleteInBatches`):
|
||
|
||
- 每批 5000 行:`delete … where ctid = any(array(select ctid … where <条件> limit 5000))`,单独事务。
|
||
- 每批 `statement_timeout` 15 秒、`lock_timeout` 3 秒。超时就结束本次运行,不算失败,摘要写「未删完,下次继续」。
|
||
- 每次运行最多 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);`statements` 为语句统计(见「数据库性能」) |
|
||
| `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`、`pipeline`、`identity_pool`、`analyze`、`images`、`kv_cache`。
|
||
|
||
和定时任务「立即执行」一样,只设置 `run_requested_at`,由调度器执行。已在排队或运行中返回 `queued:false`。
|
||
任务还没登记(调度器没同步过)返回 404。触发成功写审计 `storage.cleanup`。
|
||
|
||
### GET /system/health 的 `storage` 段
|
||
|
||
`{ level, alerts[] }`,level 计入总档位。库体积实时取;目录用量只用缓存值,过期时后台刷新,本次不等
|
||
(进程刚启动时目录项是 unknown,不参与汇总)。
|
||
|
||
## 运行状态页「存储」节(FIX-5)
|
||
|
||
控制台 → 运行状态最下方。`GET /system/health` 的 `storage.level / alerts` 显示为档位与告警条(横幅也会提示),
|
||
`GET /system/storage` 显示:数据库体积 / 图片缓存 / 备份 / 会话与证据对照预算的用量条(竖线是告警线)、体积前 10 的表、
|
||
`kv_cache` 各类缓存的条数 / 已过期 / 体积、DATA_DIR 各子目录占用、各清理任务的保留期与上次结果。每行「立即清理」调
|
||
`POST /system/storage/cleanup/{target}`(已在排队或运行中则合并),结果看计划页的运行记录。明细每分钟刷新,目录用量服务端缓存 5 分钟。
|
||
代码:`apps/web/app/components/status/storage-section.tsx`。
|
||
|
||
## 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` | 批量删除时减少检查点 |
|
||
|
||
| `random_page_cost` | 1.1 | `PG_RANDOM_PAGE_COST` | 库整个在内存里时 1.1;缺省 4 是机械盘的估值 |
|
||
|
||
另外 `shm_size: 512mb`(并行查询的动态共享内存走 /dev/shm),`shared_preload_libraries=pg_stat_statements`
|
||
与 `pg_stat_statements.track=top`(语句统计,见下)。
|
||
|
||
2026-10-06 评估(部署库 172 MB):
|
||
|
||
- `shared_buffers` 2GB 远大于库,但只是上限,页用到才占内存;容器上限 3g 也没紧张,不改。
|
||
- `random_page_cost` 4 → 1.1:库全部命中缓存(`shared_buffers` + 页缓存),随机读的代价被高估,
|
||
规划器在中等表上会偏向整表扫描。这是唯一调整的参数。
|
||
- `work_mem` / `maintenance_work_mem` / `max_wal_size` 按 4C/8G 给的值,没有看到排序落盘或检查点过频,不改。
|
||
- autovacuum 开着,按表参数见上(迁移 0050);部署库死元组都在几十行以内,不改。
|
||
|
||
按机器调整,例如 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,两者一起改。
|
||
|
||
## 数据库性能(2026-10-06)
|
||
|
||
### 语句统计 pg_stat_statements
|
||
|
||
- 迁移 `0089_db_perf` 在镜像带这个扩展时 `create extension if not exists pg_stat_statements`(建在 public);
|
||
扩展不存在或权限不够(非超级用户)时跳过,迁移照常成功。
|
||
- 采集要求 postgres 以 `shared_preload_libraries=pg_stat_statements` 启动(`docker-compose.yml` 已加,`track=top`
|
||
只记顶层语句)。**改 compose 后要重建 postgres 容器才生效**:
|
||
|
||
```sh
|
||
docker compose --env-file .env.deploy up -d postgres # 重建 postgres(数据在卷里不丢);app 会短暂断开数据库后自动重连
|
||
docker compose --env-file .env.deploy exec postgres psql -U umt -c 'show shared_preload_libraries'
|
||
```
|
||
|
||
- 查看:运行状态页「存储 → 数据库语句统计」(平均最慢的 5 条,只看调用 ≥ 10 次的;调用最多的 5 条),
|
||
对应 `GET /system/storage` 的 `db.statements`;没装扩展 / 没预加载时 `enabled=false` 并在 `note` 里说明。
|
||
命令行:
|
||
|
||
```sql
|
||
select calls, round(mean_exec_time::numeric, 3) as mean_ms, round(total_exec_time::numeric) as total_ms, query
|
||
from pg_stat_statements order by total_exec_time desc limit 20;
|
||
select pg_stat_statements_reset(); -- 清零重新统计
|
||
```
|
||
|
||
- 同一节的「体积最大的表」加了死元组列(`pg_stat_all_tables.n_dead_tup`)。
|
||
|
||
### 高频全表扫描的结论
|
||
|
||
部署库的 `pg_stat_user_tables` 里 seq_scan 高的表大多只有几十到一百多行(`scheduled_job` 33、`task` 81、`admin_session` 139、
|
||
`title_alias` 177、`external_identity` 74、`site` 13、`download_task` 4):这种表 1~5 页,整表扫描比走索引便宜,
|
||
规划器选 seq scan 是对的,对应的唯一索引 / 主键「从未使用」也是这个原因(比如会话校验本来就是按 `session_hash`
|
||
等值查,按 10 倍数据量造数后就走 `admin_session_hash_key`)。真正改了的:
|
||
|
||
- 巡检回填(调度器每 15 秒)原来每次整扫 `scheduled_job_run`:加部分索引 `scheduled_job_run_harvest_idx`。
|
||
- 标题模糊匹配原来写 `similarity(x, q) >= t`,函数调用用不上 trigram 索引:改为 `x % q and similarity(x, q) >= t`
|
||
(`%` 的缺省阈值 0.3 不高于各处阈值,结果相同)。`source_release` 标题搜索 8440 行从 75 ms 降到 0.7 ms;
|
||
别名匹配(`title_alias`)现在表太小,规划器仍选整表扫描,表长大后会改走索引。
|
||
- 调度器每秒查一次到期任务(33 行,约 0.15 ms)保留不改:它决定「立即执行」的响应延迟,成本可以忽略。
|
||
- 删除从未使用且没有查询会用到的 `content_unit_air_date_idx`。其余从未使用的索引有用途,保留:
|
||
`source_release_title_trgm_idx`(上面的标题搜索)、`resolution_attempt_release_idx`(保留规则「每条资源留最近 3 次」)、
|
||
`source_release_infohash_v1_idx`(按 hash 接管下载 / 浏览器核验)、`audit_event_action_idx`(能力调用统计)、
|
||
主键与唯一约束(保证唯一性)。
|
||
|
||
## 常见问题
|
||
|
||
- 面板上 PG 超预算:先看 `tables` 前几名。流水表大就临时调短对应保留期,再 `POST /system/storage/cleanup/<target>`;
|
||
死元组多说明 autovacuum 跟不上,可手动 `VACUUM (VERBOSE) <表>`。
|
||
- 清理摘要一直是「未删完」:积压太多,每次 5 分钟删不完。可以把任务周期临时调短,或在低峰期多触发几次。
|
||
- 图片缓存一直 warn:LRU 任务是否启用、最近一次是否失败;磁盘上是否有大量 `.tmp` 残留(不计入用量,可手动删)。
|
||
- 历史曲线只能看 24 小时:这是 `heartbeatHours` 的缺省,要看更久就调大。
|