Files
gongxue-base/docs/refactor/performance-benchmark-runbook.md

7.9 KiB
Raw Blame History

API 压测与 4 核 16G 容量评估 Runbook

更新时间2026-06-30

这份 runbook 用于在本地 Docker/Supabase 或未来 4 核 16G 云服务器上,对 SaaS 题库 API 做可重复压测。目标不是一次性给出永久容量承诺而是建立一套可以随着题库、用户量、SQL 和硬件变化持续复跑的基线。

工具

压测脚本:

npm run perf:api:local

脚本会:

  • 自动从 DATABASE_URL 指向的 PostgreSQL 中发现一个活跃租户、学生、题库入口、分类节点、合集、练习蓝图、单词单元和知识手册。
  • 默认启动本地 API 服务,并使用真实 API 请求做混合读路径压测。
  • 默认不创建练习 session不写业务数据。
  • 输出 JSON 和 Markdown 报告到 docs/refactor/performance-reports/。该目录已被 .gitignore 忽略,不应提交。

前置条件

本地真实迁移库压测:

npx supabase db reset
$env:DATABASE_URL="postgresql://postgres:postgres@127.0.0.1:54322/postgres"
npm run pb:import:json
npm run pb:import:validate
npm run perf:api:local

烟测 seed 压测:

$env:DATABASE_URL="postgresql://postgres:postgres@127.0.0.1:54322/postgres"
npm run db:smoke-seed
npm run perf:api:local

如果 API 已经在运行:

$env:PERF_START_SERVER="false"
$env:PERF_API_BASE="http://127.0.0.1:8787"
npm run perf:api:local

环境变量

变量 默认值 说明
DATABASE_URL postgresql://postgres:postgres@127.0.0.1:54322/postgres 压测使用的数据源
PERF_API_BASE 已运行 API 地址;为空时默认启动本地 API
PERF_START_SERVER 根据 PERF_API_BASE 推断 是否由脚本启动 API
PERF_API_PORT 随机端口 脚本启动 API 时使用的端口
PERF_TENANT_CODE master 优先选择的租户 slug
PERF_DURATION_SECONDS 15 压测持续时间
PERF_CONCURRENCY 6 并发 worker 数
PERF_RAMP_SECONDS 3 并发爬坡时间
PERF_QUESTION_LIMIT 20 单次合集题目请求数量
PERF_INCLUDE_WRITES false 是否加入创建练习 session 写请求
PERF_INCLUDE_LEADERBOARD false 是否加入排行榜接口;排行榜租户默认关闭,仅在租户明确开启并需要专项压测时打开
PERF_OUTPUT_DIR docs/refactor/performance-reports 报告输出目录
PERF_REQUEST_TIMEOUT_MS 15000 单请求超时

默认工作负载

默认混合读路径包含:

  • /health
  • /api/tenant/resolve
  • /api/catalog/regions
  • /api/catalog/content-entries
  • /api/catalog/content-nodes?mode=flat
  • /api/catalog/question-collections
  • /api/catalog/question-collections/questions
  • /api/catalog/practice-blueprints
  • /api/learning/stats
  • /api/learning/trend
  • /api/catalog/vocabulary-words
  • /api/learning/vocabulary/stats
  • /api/catalog/handbook-chapters
  • /api/catalog/handbook-entries?includeContent=true

排行榜租户默认关闭,不进入默认压测负载。只有在租户已经开启 feature_flags.enableLeaderboard=true,并准备验证独立排行榜页或活动页容量时,才打开:

$env:PERF_INCLUDE_LEADERBOARD="true"
npm run perf:api:local

写入路径默认关闭。只有在专门的测试库或可丢弃预生产库中,才建议打开:

$env:PERF_INCLUDE_WRITES="true"
npm run perf:api:local

开启后会加入低权重 POST /api/learning/practice-sessions,用于观察组卷、免费额度、权益校验和 session 写入开销。

4 核 16G 阶梯压测建议

在云服务器上先按 docs/refactor/postgresql-4c16g-tuning.md 配置 shared-host 起步值,再跑以下矩阵。每轮之间间隔 2 到 5 分钟,观察 CPU、内存、磁盘 I/O、连接数和慢 SQL。

场景 并发 时长 写入 用途
smoke 6 30s 确认部署和数据可访问
baseline-10 10 2min 学生正常浏览/刷题入口
baseline-30 30 5min 中小租户晚高峰
baseline-50 50 5min 单机读路径压力观察
mixed-30 30 5min 加入少量创建练习 session
mixed-50 50 5min 观察写入、锁和连接池
spike-100 100 2min 短峰值和缓存命中观察

PowerShell 示例:

$env:DATABASE_URL="postgresql://postgres:***@127.0.0.1:5432/postgres"
$env:PERF_API_BASE="https://api.example.com"
$env:PERF_START_SERVER="false"
$env:PERF_DURATION_SECONDS="300"
$env:PERF_CONCURRENCY="30"
$env:PERF_RAMP_SECONDS="30"
$env:PERF_INCLUDE_WRITES="false"
npm run perf:api:local

初步验收线

上云测试早期建议先用保守指标:

  • 只读 mixed catalog 工作负载P95 小于 300msP99 小于 800ms错误率小于 0.1%。
  • 加入少量 session 写入P95 小于 500msP99 小于 1200ms错误率小于 0.5%。
  • 数据库无连接耗尽、无 OOM、无长时间 idle in transaction
  • pg_stat_databasexact_rollback 不应快速增长。
  • pg_stat_activity 不应长期堆积锁等待或 I/O wait。

这些不是最终 SLA。正式 SLA 要结合真实云服务器、真实 CDN、真实对象存储、真实支付回调和前端 Web Vitals 重新制定。

问题定位

如果 P95 或 P99 明显升高,按下面顺序排查:

  1. 查看报告的接口明细,先定位慢接口。
  2. 打开 PostgreSQL 慢 SQL 日志,或在生产启用 pg_stat_statements
  3. 对慢接口涉及 SQL 执行 explain (analyze, buffers)
  4. 检查 API/worker 连接池是否过大导致数据库活跃连接超过 4 核可承受范围。
  5. 检查 content_nodes pathquestion_collection_itemsanswer_recordspractice_sessionsvocabulary_progress 等大表索引。
  6. 对 dashboard、排行榜、销售转化、分佣报表这类聚合接口优先做日/小时预聚合,而不是无限放大 SQL 超时。

结果归档

本地生成的报告目录不入 Git。正式上云验收时可以把代表性结果摘要写入 production-launch-evidence.json 的本地证据文件,再运行:

npm run launch:gate

launch:gate 会强制检查一条真实数据读路径压测证据:

{
  "id": "performance.api-real-data-read",
  "status": "pass",
  "command": "PERF_START_SERVER=false PERF_API_BASE=https://api.example.com PERF_DURATION_SECONDS=300 PERF_CONCURRENCY=30 PERF_RAMP_SECONDS=30 PERF_INCLUDE_WRITES=false npm run perf:api:local > docs/refactor/launch-artifacts/api-real-data-read-benchmark.log",
  "completedAt": "2026-06-30T10:48:00+08:00",
  "artifact": "launch-artifacts/api-real-data-read-benchmark.log",
  "summary": {
    "errors": 0,
    "errorRate": 0,
    "p95Ms": 0,
    "p99Ms": 0,
    "concurrency": 30,
    "durationSeconds": 300,
    "includeWrites": false
  }
}

上线门禁的最低机器阈值:

  • errors = 0
  • errorRate <= 0.001
  • p95Ms <= 300
  • p99Ms <= 800
  • concurrency >= 30
  • durationSeconds >= 120
  • includeWrites = false

建议用摘要工具从 perf:api:local JSON 报告自动提取门禁字段,避免手工抄错:

npm run perf:summary -- --input docs/refactor/performance-reports/api-benchmark-20260630-xxxxxx.json --json

工具会从 summary.errorssummary.errorRatesummary.latencyOk.p95Mssummary.latencyOk.p99Msconfig.concurrencyconfig.durationSecondsconfig.includeWrites 生成 launchGateCheck.summary,并按上线门禁阈值返回退出码。通过后,把 launchGateCheck.summary 转写到 production-launch-evidence.jsonartifact 保留对应日志或报告路径。

更高的 50/100 并发、写入混合场景和容量结论仍应作为人工容量报告归档;门禁只负责挡住明显不达标的基础读路径。

证据中只记录报告路径、并发矩阵、P95/P99、错误率和结论不保存真实 token、支付密钥、用户隐私或完整响应。