topfans/backend/scripts/loadgen/REPORT_GUIDE.md
2026-06-15 20:10:56 +08:00

267 lines
8.2 KiB
Markdown
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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.

# REPORT_GUIDE — 压测报告怎么读
> **目标读者**:看完压测报告后,需要判断"系统能扛住吗"+"哪里是瓶颈"+"下一步改什么"的工程师
> **报告路径**:`reports/final-report.md` (主) + `reports/{scenario}.json` (原始) + `reports/{scenario}.png` (图)
---
## 1. 报告目录结构
```
reports/
├── S1.json # 场景 1 原始数据 (程序读)
├── S2.json # 场景 2
├── S4.json # 场景 4
├── baseline.csv # Excel 可打开的汇总表
├── s1.png # 场景 1 曲线图 (RPS / P99 / Error)
├── s2.png
├── s4.png
└── final-report.md # ← 你要看的总报告
```
---
## 2. 三步读完报告
### 第 1 步:看汇总表 (1 分钟)
```markdown
| Scenario | Total | Err | 5xx | P50ms | P95ms | P99ms | Maxms | Stages |
|----------|-------|-----|-----|-------|-------|-------|-------|--------|
| S1 | 12500 | 0 | 0 | 86.59 | 119.23 | 200.50 | 450 | 5 |
| S2 | 25000 | 5 | 0 | 12.30 | 35.00 | 88.00 | 250 | 5 |
| S4 | 600 | 12 | 2 | 200.00 | 500.00 | 850.00 | 1200 | 4 |
```
**每个字段的含义**:
| 字段 | 含义 | 健康参考 (4G/2C prod) |
|------|------|----------------------|
| `Scenario` | 场景 ID (S1=登录, S2=读, S3=点赞, S4=铸造, ...) | — |
| `Total` | 该场景总请求数 | 越大越好,代表你扛住了 |
| `Err` | 客户端+服务端错误总和 | **< 1%** |
| `5xx` | 服务端错误 (500-599) | **< 0.1%** (1‰) |
| `P50ms` | 50% 请求在这个时间内 | < 100ms |
| `P95ms` | 95% 请求在这个时间内 | < 300ms |
| `P99ms` | 99% 请求在这个时间内 | < 1000ms (S4 写重可放宽到 2000ms) |
| `Maxms` | 最慢的一次请求 | 一般 3-5x P99 |
| `Stages` | 阶梯测试的阶段数 | = step-schedule 的元素数 |
**判断模板**:
- 全绿 系统扛得住,准备上线
- 某个 S* Err > 1% → 优先看那个场景
- 🚨 某个 S* 5xx > 1% → 服务端有问题,看 §3 定位
---
### 第 2 步:看拐点 (KneeRPS) (2 分钟)
每个 scenario 标题下会出现一行:
```markdown
**⚠️ 拐点**: stage 3 @ 3 RPS (p99 暴涨 514%)
```
**含义**: 当 RPS 升到 3 时,p99 延迟比 stage 2 暴涨 514% (5.14 倍)。
**判定逻辑** (在 `reporter/knee.go`):
- 逐 stage 比 p99
- 第一次涨幅 > 50% 时,标记为拐点
- 全程没涨 > 50% → 显示 "✅ 拐点未触发"
**怎么用这个数字**:
- **S1 拐点 RPS = 15** → 你的登录服务,超过 15 QPS 就开始劣化。生产预估峰值 10 QPS,留 50% buffer
- **S4 拐点 RPS = 2** → 铸造接口很重,2 QPS 就劣化了。要么优化,要么限流
**举例**:
| 拐点 RPS | 业务含义 | 行动项 |
|---------|---------|--------|
| ≥ 期望峰值的 2x | ✅ 健康 | 上线,加监控 |
| ≈ 期望峰值 | ⚠️ 临界 | 加缓存 / 异步化 / 限流 |
| < 期望峰值 | 🚨 不达标 | 重构 + 复测 |
---
### 第 3 步:看阶梯表 + 曲线图 (5 分钟)
**阶梯表** (md 里每个场景下):
```markdown
### 阶梯结果
| Stage | TargetRPS | Total | Err | 5xx | P50ms | P95ms | P99ms | Maxms |
|-------|-----------|-------|-----|-----|-------|-------|-------|-------|
| 1 | 2 | 600 | 0 | 0 | 80 | 100 | 110 | 130 |
| 2 | 5 | 1500 | 0 | 0 | 82 | 105 | 115 | 140 |
| 3 | 10 | 3000 | 0 | 0 | 85 | 110 | 130 | 180 |
| 4 | 15 | 4500 | 0 | 0 | 95 | 130 | 200 | 350 |
| 5 | 20 | 6000 | 5 | 0 | 120 | 200 | 450 | 800 |
```
**怎么读**:
- **Total** 应该是 `TargetRPS × Duration` (近似,因为有误差)
- **P99ms** 应该随 TargetRPS 上升**平滑增加** (10-30% 涨幅/stage 是正常)
- **Err / 5xx** 应该全程 < 1%
- **如果某 stage 突然 P99 翻倍** 拐点,看上面 KneeRPS
**曲线图** (`s1.png` ):
- **X **: Stage 编号 (1, 2, 3, ...)
- **Y **: 三个值 RPS ()、P99ms (绿)、Error% ()
- **怎么看**:
- 三条线**平稳上升** = 正常
- **P99 突然陡升** = 拐点
- **Error% 突然跳起来** = 服务挂了
---
## 3. 定位瓶颈 — 常见模式
### 模式 1: P99 阶梯上升,但 Error 一直 0
**含义**: 系统扛得住,但在变慢
**原因**: GC 抖动 / DB 慢查询 / 锁竞争
**行动**:
1. PG 慢查询日志: `pg_stat_statements` ORDER BY `mean_exec_time` DESC
2. 看应用层 profile: `pprof` heap + cpu
3. 检查连接池配置: 可能太小
### 模式 2: P99 阶梯上升 + Error 也开始涨
**含义**: 系统到极限
**原因**: 资源耗尽 (CPU 100%, 连接池满, DB )。
**行动**:
1. server metrics feed: `tail -f metrics-feed.jsonl`
2. `top` CPU/内存,`iostat` IO
3. 检查是否有连接泄漏 (`netstat | grep TIME_WAIT`)
### 模式 3: 阶梯早期就 5xx > 5%
**含义**: 系统本身有问题,不是负载问题
**原因**: 代码 bug / 配置错误 / 依赖缺失
**行动**:
1. 5xx 的具体响应体 ( log )
2. 检查 error ,对照业务错误码定义
3. 看是不是 auth/JWT 过期
### 模式 4: 第一个 stage P99 很高,后续反而低
**含义**: 热身不够 / 缓存没预热
**原因**: Redis 冷启动 / JIT 编译 / DB 连接池启动慢
**行动**:
1. 第一次 stage 加长 (例如先 2min 预热)
2. 或者用 `--rps=1` 先跑 1-2min 预热,再开阶梯
### 模式 5: S4 (Mint) 在很低的 RPS 就拐
**含义**: 写路径太重
**原因**: 铸造涉及事务 / 签名 / OSS 上传,本身就是慢操作
**行动**:
1. 检查 mint 是不是同步阻塞 (能不能异步化?)
2. mint 数据是否需要落库 (能否用 append-only?)
3. 考虑限流: 服务端拒绝 > 2 QPS 的 mint 请求
---
## 4. 怎么写出行动项
读完报告,应该能回答三个问题:
### Q1: 系统能扛住业务预期峰值吗?
- 业务预期峰值 → 比对拐点 RPS
- 拐点 ≥ 2x 峰值 → ✅ 可以上线
- 拐点 ≈ 1x 峰值 → ⚠️ 加监控告警,谨慎上线
- 拐点 < 峰值 🚨 必须先优化
### Q2: 拐点在哪里?为什么?
看哪个 stage 拐的,然后:
- **CPU 100%** 计算密集,优化算法或加机器
- **DB CPU 100%** 慢查询,加索引或读写分离
- **PG 连接数满** 连接池配置 / 服务降级
- **PG 锁等待** 事务设计问题
- **磁盘 IO ** SSD 或缓存
### Q3: 下一步改什么?
行动项模板:
```markdown
## [Loadtest 2026-06-15] 行动项
### P0 (上线前必修)
- [ ] **S2 Read 拐点 100 RPS < 业务预期 150 RPS**
- 根因: PG `assets` 表全表扫描,10 万行
- 修复: 加 `idx_assets_star_id_status` 索引
- Owner: @dba
### P1 (1 周内修)
- [ ] **S4 Mint 拐点 2 RPS**
- 根因: 同步写 OSS + 同步落库
- 修复: mint 流程拆成 precreate + 后台 worker
- Owner: @backend
### P2 (技术债)
- [ ] 压测期间 CPU 持续 80%,考虑扩容到 4C
```
---
## 5. JSON 原始数据怎么读 (高级)
`reports/S1.json` 长这样:
```json
{
"scenario": "S1",
"total_requests": 12500,
"errors": 5,
"five_xx": 0,
"p50_us": 86591,
"p95_us": 119231,
"p99_us": 200502,
"max_us": 450000,
"stages": [
{
"stage_idx": 1,
"target_rps": 2,
"total_requests": 600,
"errors": 0,
"five_xx": 0,
"p50_us": 80000,
"p95_us": 100000,
"p99_us": 110000,
"max_us": 130000
},
...
]
}
```
**单位说明**:
- 所有 `_us` 后缀 = microseconds (微秒,1ms = 1000us)
- 例: `p99_us: 200502` = 200.5 ms
**怎么用**:
- 画自己的图 ( Excel/Google Sheets 打开 baseline.csv 最方便)
- 跟历史报告对比 (跨版本性能回归)
- CI 集成: 解析 JSON,断言 P99 < 某个阈值
---
## 6. 常见问题
### Q: "5xx=0 但 Err=5" 是什么意思?
A: 5xx 是服务端错,Err 是总错 ( 4xx)。Err > 5xx 表示有客户端错 (一般是 401/403/404)。看 log 里具体错误码。
### Q: 为什么 P50 很低但 P99 很高?
A: 正常 — 长尾效应。99% 都快但 1% 慢。如果 P99 太高说明有少数请求卡住,看是不是 GC / 锁 / IO 抖动。
### Q: Max 比 P99 高很多,是不是异常?
A: 可能是单个网络抖动,正常。Max / P99 < 5x 都是健康
### Q: 同一个场景不同次跑,数据差很多?
A: 检查 prod 是否有其他流量在跑 (业务)。压测应在凌晨,业务低峰
---
## 7. 进一步
- 想优化场景, `seed/README.md`
- 想加新场景, `scenarios/` 新建 `s8_xxx.go`,模仿 s1_login.go BeginStage/EndStage 模式
- 想加新的红线指标, `lib/circuit.go`