Files
gongxue-base/docs/refactor/performance-benchmark-runbook.md
2026-06-30 13:21:17 +08:00

196 lines
7.5 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# API 压测与 4 核 16G 容量评估 Runbook
更新时间2026-06-30
这份 runbook 用于在本地 Docker/Supabase 或未来 4 核 16G 云服务器上,对 SaaS 题库 API 做可重复压测。目标不是一次性给出永久容量承诺而是建立一套可以随着题库、用户量、SQL 和硬件变化持续复跑的基线。
## 工具
压测脚本:
```bash
npm run perf:api:local
```
脚本会:
- 自动从 `DATABASE_URL` 指向的 PostgreSQL 中发现一个活跃租户、学生、题库入口、分类节点、合集、练习蓝图、单词单元和知识手册。
- 默认启动本地 API 服务,并使用真实 API 请求做混合读路径压测。
- 默认不创建练习 session不写业务数据。
- 输出 JSON 和 Markdown 报告到 `docs/refactor/performance-reports/`。该目录已被 `.gitignore` 忽略,不应提交。
## 前置条件
本地真实迁移库压测:
```powershell
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 压测:
```powershell
$env:DATABASE_URL="postgresql://postgres:postgres@127.0.0.1:54322/postgres"
npm run db:smoke-seed
npm run perf:api:local
```
如果 API 已经在运行:
```powershell
$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_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/learning/leaderboard`
- `/api/catalog/vocabulary-words`
- `/api/learning/vocabulary/stats`
- `/api/catalog/handbook-chapters`
- `/api/catalog/handbook-entries?includeContent=true`
写入路径默认关闭。只有在专门的测试库或可丢弃预生产库中,才建议打开:
```powershell
$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 示例:
```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_database``xact_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 path``question_collection_items``answer_records``practice_sessions``vocabulary_progress` 等大表索引。
6. 对 dashboard、排行榜、销售转化、分佣报表这类聚合接口优先做日/小时预聚合,而不是无限放大 SQL 超时。
## 结果归档
本地生成的报告目录不入 Git。正式上云验收时可以把代表性结果摘要写入 `production-launch-evidence.json` 的本地证据文件,再运行:
```bash
npm run launch:gate
```
`launch:gate` 会强制检查一条真实数据读路径压测证据:
```json
{
"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 报告自动提取门禁字段,避免手工抄错:
```powershell
npm run perf:summary -- --input docs/refactor/performance-reports/api-benchmark-20260630-xxxxxx.json --json
```
工具会从 `summary.errors``summary.errorRate``summary.latencyOk.p95Ms``summary.latencyOk.p99Ms``config.concurrency``config.durationSeconds``config.includeWrites` 生成 `launchGateCheck.summary`,并按上线门禁阈值返回退出码。通过后,把 `launchGateCheck.summary` 转写到 `production-launch-evidence.json``artifact` 保留对应日志或报告路径。
更高的 50/100 并发、写入混合场景和容量结论仍应作为人工容量报告归档;门禁只负责挡住明显不达标的基础读路径。
证据中只记录报告路径、并发矩阵、P95/P99、错误率和结论不保存真实 token、支付密钥、用户隐私或完整响应。