Files
ai-contract-platform/project-docs/V2-database-postgresql-explain-baseline-2026-06-24.md
yuqei a3bb78e06b
Security Quality Gate / npm audit, Bandit, pip-audit, detect-secrets (pull_request) Failing after 16m35s
release: freeze V2 release candidate
2026-07-12 15:32:24 +08:00

7.9 KiB
Raw Permalink Blame History

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。