merge: perf/db

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01Cxwgnn2pbzQ7rXv3rq7fei
This commit is contained in:
rootandClaude Opus 5.5 committed 2026-10-06 20:49:38 +08:00
commit 671874a2c4
22 files changed
+1006 -88

No files matched your search

+22
View File
@@ -527,6 +527,7 @@ export const StorageCleanupTarget = z.enum([
"agent_event",
"audit",
"capability_stats",
"pipeline",
"identity_pool",
"analyze",
"images",
@@ -555,6 +556,16 @@ export const StorageBudgetUsage = z.object({
});
export type StorageBudgetUsage = z.infer<typeof StorageBudgetUsage>;
/** 一条语句的累计统计(pg_stat_statements,参数已归一化为 $n) */
export const StatementStat = z.object({
query: z.string(),
calls: NonNegInt,
totalMs: z.number().nonnegative(),
meanMs: z.number().nonnegative(),
rows: NonNegInt,
});
export type StatementStat = z.infer<typeof StatementStat>;
export const SystemStorage = z.object({
/** 各项最差档(unknown 不参与) */
level: HealthLevel,
@@ -577,6 +588,17 @@ export const SystemStorage = z.object({
lastAnalyzeAt: Timestamp.nullable(),
}),
),
/**
* 语句统计(pg_stat_statements):enabled=false 时 note 说明原因(没装扩展 / postgres 没预加载);
* slowest 按平均耗时(调用 ≥ 10 次),frequent 按调用次数,各前 5;since 为统计起点(上次重置)
*/
statements: z.object({
enabled: z.boolean(),
note: z.string().nullable(),
since: Timestamp.nullable(),
slowest: z.array(StatementStat),
frequent: z.array(StatementStat),
}),
}),
/** PG kv_cache 各 region(条数含未清理的过期行) */
kvCache: z.array(z.object({ region: z.string(), entries: NonNegInt, expired: NonNegInt, bytes: NonNegInt })),
+16
View File
@@ -559,6 +559,17 @@ export const StorageRetentionDays = z.object({
capabilityStats: Days,
/** 待识别池 unmatched 条目无新证据多少天后归档 */
identityPoolArchive: Days,
// ── 2026-10-06 数据库清理(docs/runbook/storage.md「保留规则」);旧库缺这些字段时按缺省补 ──
/** 已结束的引擎 run(步骤级联)、运行元数据 / 事实 / 信号;仍被保留中的业务任务引用的跟随任务 */
engineRun: Days,
/** 已投递的 outbox 事件(只删无幂等键或一次性 / 带时间槽的幂等键) */
outboxEvent: Days,
/** 识别尝试记录;每条资源至少保留最近 3 次 */
resolutionAttempt: Days,
/** 已识别(资源已绑定作品)的待识别池条目;忽略、归档的不删 */
identityResolved: Days,
/** 站点列表快照;每个站点最近一次成功的保留 */
siteSnapshot: Days,
});
export type StorageRetentionDays = z.infer<typeof StorageRetentionDays>;
@@ -612,6 +623,11 @@ export const DEFAULT_STORAGE_SETTINGS: StorageSettings = {
audit: 180,
capabilityStats: 90,
identityPoolArchive: 30,
engineRun: 14,
outboxEvent: 30,
resolutionAttempt: 30,
identityResolved: 60,
siteSnapshot: 30,
},
cacheTtl: {
tmdbSearchDays: 7,
+45
View File
@@ -0,0 +1,45 @@
import type { Kysely } from "kysely";
import { execDdl } from "../src/migration-helpers.ts";
// 数据库性能与可观测(docs/runbook/storage.md「数据库性能」):
// - scheduled_job_run_harvest_idx:调度器每 15 秒回填巡检结果(scheduler.harvest),原来每次全表扫运行历史;
// 部分索引只含「有 agent_run 且尚未回填」的行,通常为 0~几行;
// - 删除 content_unit_air_date_idx:没有任何查询按 air_date 过滤或排序(订阅日历在内存里按日期筛),
// 按作品取单元走 content_unit_entity_numbering_idx;
// - pg_stat_statements:镜像自带时才建(库级共享对象,放 public;down 不卸载)。要真正采集还需要
// postgres 以 shared_preload_libraries=pg_stat_statements 启动(docker-compose.yml),没有预加载时只是查询会报错,
// 状态页按「未启用」处理。没有该扩展或权限不够时跳过,不让迁移失败。
// biome-ignore lint/suspicious/noExplicitAny: 迁移不依赖应用表类型
export async function up(db: Kysely<any>): Promise<void> {
await execDdl(
db,
`
create index if not exists scheduled_job_run_harvest_idx on scheduled_job_run (agent_run_id)
where agent_status is null and agent_run_id is not null;
drop index if exists content_unit_air_date_idx;
do $$
begin
if exists (select 1 from pg_available_extensions where name = 'pg_stat_statements') then
begin
create extension if not exists pg_stat_statements with schema public;
exception when others then
raise notice 'pg_stat_statements 未安装:%', sqlerrm;
end;
end if;
end $$;
`,
);
}
// biome-ignore lint/suspicious/noExplicitAny: 同上
export async function down(db: Kysely<any>): Promise<void> {
await execDdl(
db,
`
drop index if exists scheduled_job_run_harvest_idx;
create index if not exists content_unit_air_date_idx on content_unit (media_entity_id, air_date) where air_date is not null;
-- 扩展为库级共享对象,down 不卸载
`,
);
}
+4
View File
@@ -51,6 +51,10 @@ export async function generateTypesSource(db: Kysely<any>): Promise<string> {
select table_name, column_name, data_type, udt_name, is_nullable, column_default
from information_schema.columns
where table_schema = current_schema() and table_name not like 'kysely_%'
-- 扩展自带的视图(如 public.pg_stat_statements,迁移 0089)不属于应用表
and not exists (select 1 from pg_depend d join pg_class r on r.oid = d.objid
where d.classid = 'pg_class'::regclass and d.deptype = 'e'
and r.relname = table_name and r.relnamespace = current_schema()::regnamespace)
order by table_name, ordinal_position`.execute(db);
const checks = await sql<{ table_name: string; def: string }>`
select c.relname as table_name, pg_get_constraintdef(k.oid) as def