a3bb78e06b
Security Quality Gate / npm audit, Bandit, pip-audit, detect-secrets (pull_request) Failing after 16m35s
7.9 KiB
7.9 KiB
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,000contract_files:18,000review_tasks:16,000approval_timeline_items:36,000contract_audit_events:48,000
AI 库:
ai_run_audits:50,000knowledge_documents:10,000knowledge_chunks:60,000knowledge_ingestion_jobs:20,000ai_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 |
通过 |
合同库主要问题:
- 合同正文统一搜索走全表扫描,原因是跨多个字段的
OR + ILIKE,并且包含body_content大文本字段。 - 审查任务搜索走
review_tasks和contracts的全量 hash join,后续数据量扩大后会明显拖慢统一搜索页。 - 时间线和 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 库主要问题:
- HNSW 索引有效,但当前 repository 中
_search_knowledge_chunks_with_pgvector的 SQL 形态没有使用表达式 cast,因此没有命中ix_knowledge_chunks_embedding_hnsw。 - 当前向量检索在 60,000 chunk、单知识库 3,000 chunk 规模下仍可接受;如果知识库扩大到百万 chunk,必须让查询命中 HNSW 或改成固定维度
vector(64)列。 - AI Trace 和质量看板目前表现良好,短期不用优先继续加索引。
优先优化项
P0:统一搜索改造
当前合同正文搜索和审查任务搜索是最大风险。
建议下一轮做:
- 合同搜索从多字段
OR + ILIKE改为 PostgreSQLtsvector搜索列或独立 search document 表。 - 把
contracts、contract_files、review_tasks的搜索统一写入contract_search_documents:idsource_typesource_idtitlesummarysearch_textstatusupdated_atsearch_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.sqlproject-docs/qa-artifacts/v2-database-explain-baseline/2026-06-24T18-45-59/seed-ai.sqlproject-docs/qa-artifacts/v2-database-explain-baseline/2026-06-24T18-45-59/explain-contract.sqlproject-docs/qa-artifacts/v2-database-explain-baseline/2026-06-24T18-45-59/explain-ai.sqlproject-docs/qa-artifacts/v2-database-explain-baseline/2026-06-24T18-45-59/explain-contract.logproject-docs/qa-artifacts/v2-database-explain-baseline/2026-06-24T18-45-59/explain-contract-warm.logproject-docs/qa-artifacts/v2-database-explain-baseline/2026-06-24T18-45-59/explain-ai.logproject-docs/qa-artifacts/v2-database-explain-baseline/2026-06-24T18-45-59/explain-ai-warm.logproject-docs/qa-artifacts/v2-database-explain-baseline/2026-06-24T18-45-59/explain-summary.tsv
下一步
建议继续 DB-5 第三阶段:实现统一搜索专用表或 tsvector 搜索列,先解决合同正文搜索和审查任务搜索的全表扫描问题。
之后再做 pgvector 查询形态改造,让线上法律问答和合同审查的 RAG 检索真正稳定命中 HNSW。