a3bb78e06b
Security Quality Gate / npm audit, Bandit, pip-audit, detect-secrets (pull_request) Failing after 16m35s
155 lines
7.9 KiB
Markdown
155 lines
7.9 KiB
Markdown
# AI 合同平台 V2 PostgreSQL EXPLAIN ANALYZE 基线
|
||
|
||
日期:2026-06-24
|
||
|
||
## 结论
|
||
|
||
本轮完成 DB-5 第二阶段:在 192.168.3.90 的 V2 smoke Docker 环境中,对真实 PostgreSQL/pgvector 数据库执行测试数据造数、`ANALYZE`、`EXPLAIN ANALYZE` 和 warm-cache 复测。
|
||
|
||
整体结论:
|
||
|
||
- 合同主列表、文件仓库按合同查询、审查 worker 队列、合同审计时间线、AI Trace、AI 质量看板、知识库列表、知识库 worker 队列整体可用。
|
||
- 当前最大性能风险不在普通列表,而在“统一搜索/审查搜索”这类跨表、多字段、`OR + ILIKE '%keyword%'` 查询。
|
||
- AI 向量检索的 HNSW 索引已可用,但当前代码形态使用 `embedding <=> query_vector`,不会命中本轮建立的表达式 HNSW 索引;需要把代码查询表达式调整为 `embedding::vector(64) <=> query_vector::vector(64)` 或在后续 schema 中把 `embedding` 固定为 `vector(64)`。
|
||
- 本轮只是 DB-5 基线,不等同于最终生产压测;后续仍需 DB-6 的安全、备份、RLS 和慢查询监控。
|
||
|
||
## 测试环境
|
||
|
||
- Server:`192.168.3.90`
|
||
- Compose project:`yuqei-contract-v2-smoke`
|
||
- Contract DB:`yuqei_contract`
|
||
- AI DB:`yuqei_ai`
|
||
- PostgreSQL image:`pgvector/pgvector:pg16`
|
||
- 验证目录:`project-docs/qa-artifacts/v2-database-explain-baseline/2026-06-24T18-45-59`
|
||
|
||
## 测试数据量
|
||
|
||
合同库:
|
||
|
||
- `contracts`:12,000
|
||
- `contract_files`:18,000
|
||
- `review_tasks`:16,000
|
||
- `approval_timeline_items`:36,000
|
||
- `contract_audit_events`:48,000
|
||
|
||
AI 库:
|
||
|
||
- `ai_run_audits`:50,000
|
||
- `knowledge_documents`:10,000
|
||
- `knowledge_chunks`:60,000
|
||
- `knowledge_ingestion_jobs`:20,000
|
||
- `ai_quality_improvement_actions`:8,000
|
||
- 另有模型配置、Prompt 变更、重建索引记录、worker 执行记录等辅助数据。
|
||
|
||
所有测试数据都使用 `perf-` 前缀,后续可以重复清理和重建。
|
||
|
||
## 关键结果
|
||
|
||
### 合同库
|
||
|
||
| 查询 | warm-cache 执行时间 | 计划 | 结论 |
|
||
| --- | ---: | --- | --- |
|
||
| 合同列表按状态 | 0.233 ms | `ix_contracts_updated_at` | 通过,但规划器未选 `ix_contracts_status_updated_id` |
|
||
| 合同状态计数 | 0.692 ms | `ix_contracts_status` index only | 通过 |
|
||
| 合同标题/编号/相对方关键词 | 1.598 ms | `ix_contracts_updated_at` + filter | 当前量级可接受 |
|
||
| 合同正文统一搜索 | 1574.955 ms | Seq Scan | 需优化 |
|
||
| 文件仓库按合同 | 0.113 ms | `ix_contract_files_contract_uploaded_id` | 通过 |
|
||
| 文件仓库关键词跨合同 | 60.220 ms | uploaded_at index + nested loop | 中风险 |
|
||
| 审查 worker 领取 | 0.053 ms | `ix_review_tasks_created_at` | 通过,但未选组合索引 |
|
||
| 审查任务统一搜索 | 472.446 ms | Seq Scan + Hash Join | 需优化 |
|
||
| 合同审计时间线 | 0.110 ms | `ix_contract_audit_events_contract_id` | 通过 |
|
||
| 审批时间线 | 0.090 ms | `ix_approval_timeline_items_contract_id` | 通过 |
|
||
|
||
合同库主要问题:
|
||
|
||
1. 合同正文统一搜索走全表扫描,原因是跨多个字段的 `OR + ILIKE`,并且包含 `body_content` 大文本字段。
|
||
2. 审查任务搜索走 `review_tasks` 和 `contracts` 的全量 hash join,后续数据量扩大后会明显拖慢统一搜索页。
|
||
3. 时间线和 worker 查询虽然很快,但规划器优先使用单列索引;这不是当前瓶颈,但后续可以逐步减少重复单列索引,让组合索引更明确。
|
||
|
||
### AI 库
|
||
|
||
| 查询 | warm-cache 执行时间 | 计划 | 结论 |
|
||
| --- | ---: | --- | --- |
|
||
| AI Trace 最新列表 | 0.168 ms | `ix_ai_run_audits_created_at` | 通过 |
|
||
| AI Trace 按 operation | 0.215 ms | created_at index + filter | 通过 |
|
||
| AI 质量看板 14 天窗口 | 8.959 ms | `ix_ai_run_audits_created_at` | 通过 |
|
||
| AI Trace 按 provider/model | 0.336 ms | `ix_ai_run_audits_provider_model_created` | 通过 |
|
||
| AI Trace 问题关键词 | 2.263 ms | created_at index + filter | 当前量级可接受 |
|
||
| 质量改进项待处理队列 | 0.283 ms | updated_at index + filter | 通过 |
|
||
| 质量改进项按 owner/status | 0.120 ms | `ix_quality_actions_owner_status_updated` | 通过 |
|
||
| 知识库文档按 base/status | 0.248 ms | `ix_knowledge_documents_kb_status_created` | 通过 |
|
||
| 知识库关键词检索 | 0.296 ms | `ix_knowledge_chunks_kb_created` + filter | 当前量级可接受 |
|
||
| 当前代码形态向量检索 | 18.221 ms | bitmap + sort | 可用但未命中 HNSW |
|
||
| HNSW 表达式形态向量检索 | 1.051 ms | `ix_knowledge_chunks_embedding_hnsw` | 通过 |
|
||
| 知识导入 worker 领取 | 0.045 ms | updated_at index + filter | 通过 |
|
||
| 知识导入列表按 base/status | 0.043 ms | `ix_ingestion_jobs_kb_status_updated` index only | 通过 |
|
||
| worker 执行时间线 | 0.256 ms | started_at index + filter | 通过 |
|
||
|
||
AI 库主要问题:
|
||
|
||
1. HNSW 索引有效,但当前 repository 中 `_search_knowledge_chunks_with_pgvector` 的 SQL 形态没有使用表达式 cast,因此没有命中 `ix_knowledge_chunks_embedding_hnsw`。
|
||
2. 当前向量检索在 60,000 chunk、单知识库 3,000 chunk 规模下仍可接受;如果知识库扩大到百万 chunk,必须让查询命中 HNSW 或改成固定维度 `vector(64)` 列。
|
||
3. AI Trace 和质量看板目前表现良好,短期不用优先继续加索引。
|
||
|
||
## 优先优化项
|
||
|
||
### P0:统一搜索改造
|
||
|
||
当前合同正文搜索和审查任务搜索是最大风险。
|
||
|
||
建议下一轮做:
|
||
|
||
- 合同搜索从多字段 `OR + ILIKE` 改为 PostgreSQL `tsvector` 搜索列或独立 search document 表。
|
||
- 把 `contracts`、`contract_files`、`review_tasks` 的搜索统一写入 `contract_search_documents`:
|
||
- `id`
|
||
- `source_type`
|
||
- `source_id`
|
||
- `title`
|
||
- `summary`
|
||
- `search_text`
|
||
- `status`
|
||
- `updated_at`
|
||
- `search_vector`
|
||
- 对 `search_vector` 建 GIN 索引。
|
||
- 统一搜索页先查 search document,再按结果类型回表取详情。
|
||
|
||
这样可以避免跨表 OR 和大文本全表扫描。
|
||
|
||
### P1:pgvector 查询形态改造
|
||
|
||
当前 HNSW 索引已经存在并可用,但代码未命中。
|
||
|
||
建议:
|
||
|
||
- 短期:把 `_search_knowledge_chunks_with_pgvector` 的 SQL 改为 `embedding::vector(64) <=> CAST(:query_vector AS vector(64))`。
|
||
- 中期:固定 `knowledge_chunks.embedding` 为 `vector(64)` 或按 embedding dimension 拆表/分区,避免表达式索引长期存在。
|
||
- 长期:按 `knowledge_base_id` 做分区或加检索候选召回策略,避免全局 HNSW 先召回再过滤知识库导致返回不足。
|
||
|
||
### P2:索引策略整理
|
||
|
||
本轮发现一些查询仍选旧单列索引。
|
||
|
||
短期不需要马上删除旧索引,因为当前性能可接受;但后续 DB-2/DB-3 清洁模型落地后,应统一整理:
|
||
|
||
- 保留确实被查询使用的组合索引。
|
||
- 删除重复、低收益单列索引。
|
||
- 让迁移和模型中的 `index=True` 不再随意生成单列索引。
|
||
|
||
## 本轮产物
|
||
|
||
- `project-docs/qa-artifacts/v2-database-explain-baseline/2026-06-24T18-45-59/seed-contract.sql`
|
||
- `project-docs/qa-artifacts/v2-database-explain-baseline/2026-06-24T18-45-59/seed-ai.sql`
|
||
- `project-docs/qa-artifacts/v2-database-explain-baseline/2026-06-24T18-45-59/explain-contract.sql`
|
||
- `project-docs/qa-artifacts/v2-database-explain-baseline/2026-06-24T18-45-59/explain-ai.sql`
|
||
- `project-docs/qa-artifacts/v2-database-explain-baseline/2026-06-24T18-45-59/explain-contract.log`
|
||
- `project-docs/qa-artifacts/v2-database-explain-baseline/2026-06-24T18-45-59/explain-contract-warm.log`
|
||
- `project-docs/qa-artifacts/v2-database-explain-baseline/2026-06-24T18-45-59/explain-ai.log`
|
||
- `project-docs/qa-artifacts/v2-database-explain-baseline/2026-06-24T18-45-59/explain-ai-warm.log`
|
||
- `project-docs/qa-artifacts/v2-database-explain-baseline/2026-06-24T18-45-59/explain-summary.tsv`
|
||
|
||
## 下一步
|
||
|
||
建议继续 DB-5 第三阶段:实现统一搜索专用表或 tsvector 搜索列,先解决合同正文搜索和审查任务搜索的全表扫描问题。
|
||
|
||
之后再做 pgvector 查询形态改造,让线上法律问答和合同审查的 RAG 检索真正稳定命中 HNSW。
|