Skip to content

chore(db): PG 迁移卫生与热表索引覆盖审计(#2039) - #2051

Merged
DeliciousBuding merged 2 commits into
masterfrom
chore/migration-index-audit
Aug 28, 2026
Merged

chore(db): PG 迁移卫生与热表索引覆盖审计(#2039)#2051
DeliciousBuding merged 2 commits into
masterfrom
chore/migration-index-audit

Conversation

@DeliciousBuding

Copy link
Copy Markdown
Collaborator

Closes #2039

Wave 5 Lane W5-C(数据库):hub-server PG 迁移卫生 + 热表索引覆盖审计。静态比对 hub-server/internal/repository/ 全部查询与 hub-server/migrations/ 全部索引,处置 5 个确定性缺失 + 1 个确定性冗余(迁移 0068),并用真实 PG16 做了 up 路径与执行计划验证。

一、迁移卫生审计(结论:健康,无需修复)

检查项 结论 证据
文件命名/顺序/配对 ✅ 67 up + 67 down,0001..0067 连续无缺号,全部匹配 ^\d{4}_[a-z0-9_]+\.(up|down)\.sql$ 本 lane 脚本化盘点(Get-ChildItem + 正则全量校验)
应用入口 ✅ golang-migrate,启动时 RunMigrations(file://migrations);健康检查 VerifyMigrations hub-server/internal/repository/migrate.go:16,27cmd/server-hub/main.go:44internal/handler/health.go:139
集成测试入口 RunMigrationsFrom(file://../../migrations) hub-server/tests/integration/setup_test.go:117
CWD 假设 ✅ 镜像 WORKDIR /app + COPY migrations ./migrations,与 file://migrations 相对路径一致 hub-server/deployments/Dockerfile
幂等性 ✅ 由 golang-migrate 版本跟踪(schema_migrations,每版本只执行一次)保证;0040 起文件级防御性 IF NOT EXISTS/DO $$ 加固,早期初始迁移无防护但语义上只跑一次,符合 golang-migrate 契约 各迁移文件;0041_ensure_performance_indexes0057_devices_primary_key 为防护风格代表
CI 环境匹配 postgres:16-alpine + redis:7-alpineAGENTHUB_PG_MIGRATION_TEST_ADMIN_URL 驱动 migration_audit_test.go 建独立库跑真实 up 路径 .github/workflows/checks.yml:713,756hub-server/internal/repository/migration_audit_test.go:226

二、索引覆盖矩阵(热表)

图例:✅=覆盖;❌=确定性缺失(0068 已修);🟡=存疑(建议真实负载观察,不臆造修复)

查询位置 WHERE/ORDER BY 现有索引 结论
agent_team_events agent_team_events.go:78 (MAX(seq) 追加) team_run_id = uq_agent_team_events_run_seq (team_run_id, seq)
agent_team_events agent_team_events.go:104 ListTeamEventsByRun team_run_id = ORDER seq ASC 同上
agent_team_events agent_team_usage.go:73 CountTeamRouteDecisions team_run_id = AND type = + jsonb 表达式 仅 uq 前缀 team_run_id 🟡 type 为扫描后过滤;单 run 事件量大时才有感,建议真实负载 EXPLAIN
audit_events audit.go:57 链尾查询 ORDER created_at DESC, id DESC LIMIT 1 idx_audit_events_created (created_at DESC) ✅(同 created_at 内 id tie-break 排序极小,且写入被 advisory lock 串行化)
audit_events audit.go:92-107 ListAuditEvents user_id/event_type/severity/created_at 单维 + keyset 四组 (维度, created_at DESC) 复合 ✅ 单维;🟡 多维组合由规划器择一 + 过滤
audit_events prev_hash 查找(校验路径) prev_hash = 0061 唯一索引 + 0040 裸索引同列并存 ❌ 冗余 → 0068 删裸索引
sessions session.go:69,84,99 List/SearchSessions 经 session_members 驱动 + workspace_id = idx_sessions_workspace_id ✅ workspace 过滤;🟡 ORDER sm.pinned DESC, COALESCE(...) 跨表表达式排序无法索引,LIMIT 500 可接受
session_members session_member.go 全部 session_id = 前置 UNIQUE (session_id, member_type, member_id)(session_id, left_at)
session_members session.go:69,84,99message.go:248message_attachment.go:24 member_id = AND left_at IS NULL (member_type, member_id)——member_id 非前缀 ❌ 缺失 → 0068 建部分索引
session_members session.go:70,85,100 member_count 子查询 全表 GROUP BY session_id 🟡 每次列表调用全表聚合,属算法问题非索引可解,建议后续重构(超本 lane 边界)
messages message.go:38-58 列表/增量/去重 session_id + seq_id 范围/client_msg_id session_seq、UNIQUE session_client_msg
messages message.go:202-263 搜索 session_id + 全文/tsvector GIN 部分索引(recalled=false
agent_team_runs agent_team_runs.go:48,54 session_id/team_id ORDER created_at DESC session_createdteam_created(0062)
agent_team_tasks/assignments/artifacts agent_team_tasks.go:29agent_team_assignments.go:19agent_team_artifacts.go:22 team_run_id = 0037/0041/0038 各表 team_run 索引 ✅(GetAssignmentByToMember 双单列索引 bitmap-AND,run 内量小)
agent_teams agent_team_teams.go:26,33 ListTeamsByOwner/ReadableByUser owner_id = ORDER created_at DESC 全表无任何二级索引 ❌ 缺失 → 0068 建复合索引
pending_agent_tasks agent.go:226 FindActivePendingTaskByAgentInstance(#1430 触发门禁)、agent.go:215 CancelTasksByAgentInstance agent_instance_id = AND status IN ORDER created_at DESC 无 agent_instance_id 索引 ❌ 缺失 → 0068 建复合索引
pending_agent_tasks agent.go:209 ScanExpiredTasks(查询体 :211) expire_at < AND status IN status_expire (status, expire_at)
custom_agents agent.go:74 ListCustomAgentsByOwner、agent_team_teams.go:36 join owner_user_id = AND deleted_at IS NULL 全表无任何二级索引 ❌ 缺失 → 0068 建部分索引
friendships friendship.go:76 ListPendingRequests(收到方向) friend_id = AND status = 仅 user_id 前缀索引 ❌ 缺失 → 0068 建索引
其余表(devices/notifications/refresh_tokens/users/attachments/message_attachments/message_reactions/user_settings/workspaces/skills/mcp_servers/provider_bindings/agent_profiles/execution_targets/documents/agent_run_events) 各 repository PK/唯一/复合前缀匹配 已有索引 ✅ 逐条比对通过(详见 lane 审计记录)

三、0068 处置内容与真实执行计划证据

hub-server/migrations/0068_index_coverage_fixes.up.sql(down 已验证可回滚):

  1. DROP idx_audit_events_prev_hash —— 与 0061 的 idx_audit_events_prev_hash_unique 同列同方法,唯一索引严格更强;与 0065 清理的三个冗余同型,保留只浪费 append 热表写放大。
  2. CREATE idx_session_members_member_active ON session_members(member_id) WHERE left_at IS NULL
  3. CREATE idx_agent_teams_owner_created ON agent_teams(owner_id, created_at DESC)
  4. CREATE idx_pending_agent_tasks_instance_created ON pending_agent_tasks(agent_instance_id, created_at DESC)
  5. CREATE idx_custom_agents_owner ON custom_agents(owner_user_id) WHERE deleted_at IS NULL
  6. CREATE idx_friendships_friend_status ON friendships(friend_id, status)

EXPLAIN (ANALYZE, BUFFERS) 前后对比(真实 postgres:16-alpine 16.15,播种 2 万行 session_members / 2 万行 pending_agent_tasks / 3 千行 agent_teams / 3 千行 custom_agents):

查询 修复前 修复后
session_members member_id 驱动 全索引扫描 (member_type, member_id),cost 422,136 buffers Bitmap 命中新部分索引,cost 203(索引段 4.96),95 buffers,left_at IS NULL 进索引条件
ListTeamsByOwner Seq Scan,过滤 2985/3000 行 Bitmap 命中 idx_agent_teams_owner_created,15 行精确
FindActivePendingTaskByAgentInstance Bitmap status 索引扫 7500 行→过滤 7480 行,327 buffers,1.55ms Index Scan 新复合索引,4 buffers,0.14ms(~11×,buffers 1/80)
ListCustomAgentsByOwner Seq Scan,过滤 2987/3000 行 Bitmap 命中部分索引,13 行精确
ListPendingRequests Seq Scan(演示表仅 200 行,规划器合理保留) 索引已就位,表增长后自动切换

四、验证记录(明卷)

  • cd hub-server && go test ./... -short -count=1 全绿(40 包,含 repository/handler/service)
  • go vet ./... 干净(exit 0)
  • 新增 TestMigration0068IndexCoverageFixes 静态审计测试(migration_audit_test.go,沿用既有 readMigration/normalizeSQL 约定)通过
  • 真实 PG16 up 路径:AGENTHUB_PG_MIGRATION_TEST_ADMIN_URL 驱动的 TestMigration* 全绿(含全 68 迁移从零应用到空库)
  • 完整集成套件:go test -tags integration ./tests/integration/(PG16 + Redis7 真实容器)ok 48.677s —— 证明 0068 up 路径不破坏现有测试
  • 0068 down 路径在真实 PG16 执行验证(5 删 1 恢复)
  • git diff --check 干净

证据边界

  • 执行计划证据来自本地 Docker postgres:16-alpine(16.15)合成数据(规模 2×10⁴ 行级),非生产负载;🟡 存疑项明确不修,建议上线后用真实负载复测。
  • 本机为 Windows,L0/L1 在 Docker 容器等价执行;未跑 L3(不在本 lane)。
  • 环境备注(不影响代码):本机 MSYS2 gcc 曾被 C:\Program Files\GTK3-Runtime Win64\bin(PATH 第 31 位)的旧 MinGW DLL 抢占(msys64\ucrt64\bin 在第 61 位),导致 cgo 编译 STATUS_ENTRYPOINT_NOT_FOUND。本 lane 会话内将 C:\msys64\ucrt64\bin 前置解决;建议后续把该 PATH 顺序修正为永久修复(影响本机所有 cgo 构建)。

五、变更清单

  • hub-server/migrations/0068_index_coverage_fixes.up.sql(新增)
  • hub-server/migrations/0068_index_coverage_fixes.down.sql(新增)
  • hub-server/internal/repository/migration_audit_test.go(+25 行:0068 静态审计测试)

@coderabbitai

coderabbitai Bot commented Aug 28, 2026

Copy link
Copy Markdown

Important

Review skipped

Auto reviews are disabled on this repository. Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: 64495ce2-e4bb-4cd7-8544-075e4071f2db

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

DeliciousBuding and others added 2 commits August 29, 2026 05:06
Co-authored-by: Codex <codex@vectorcontrol.tech>
Co-authored-by: Codex <codex@vectorcontrol.tech>
@DeliciousBuding
DeliciousBuding force-pushed the chore/migration-index-audit branch from dec757c to 1bc7725 Compare August 28, 2026 21:06
@DeliciousBuding
DeliciousBuding merged commit af1b9b0 into master Aug 28, 2026
38 checks passed
@DeliciousBuding
DeliciousBuding deleted the chore/migration-index-audit branch August 28, 2026 21:15
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

chore(hub): PG 迁移卫生与热表索引覆盖审计

1 participant