性能基线是“在可重现条件下比较同一场景”的协议,不是一张脱离硬件、数据量和版本的速度榜。BitzOrcas 当前已经能从验收脚本记录工程门禁耗时,但这不等于仓库已有完整 API/数据库性能门禁;运行时场景仍需团队建立稳定负载、预算、存档和回归判定。
测量闭环
先验证结果正确,再测速度。缺少租户过滤、跳过消息投递或吞掉错误会让数字更好,却是功能回归而不是性能优化。
两类不同基线
| 基线 | 当前仓库状态 | 主要用途 |
|---|---|---|
| 工程门禁耗时 | report-acceptance.sh 已记录每步 duration | 发现 restore/build/test/template/trim 退化 |
| 运行时业务性能 | 授权容量基线已内置;其余场景按环境建立 | 判断 API、SQL、broker、JobHost 是否满足 SLO |
不要把前者的“Application.Tests 用时”解释成后者的“应用吞吐”。门禁耗时受 SDK 缓存、NuGet、生成器和测试数量影响;运行时基线受数据、并发、网络和依赖容量影响。
工程门禁耗时
# 生成 13 步验收报告;每步包含状态、耗时、命令和摘要。scripts/build/report-acceptance.sh
# 保存环境信息,后续比较必须使用同一 SDK/RID 与近似机器。dotnet --info > .verify-output/dotnet-info.txtgit rev-parse HEAD > .verify-output/commit.txt重点观察 Restore、Build、Unit、Application、Architecture、CodeGeneration、Template、Integration without Docker 和 Trim Publish。若某步明显变慢,先单独重复至少三轮,再判断是否是缓存冷启动或共享 CI 噪声。
# 对异常步骤重复测量;不要用 --no-build 改变原始测量边界。time dotnet test tests/BitzOrcas.Application.Tests --configuration Releasetime dotnet test tests/BitzOrcas.Application.Tests --configuration Releasetime dotnet test tests/BitzOrcas.Application.Tests --configuration Release首次 restore、首次模板实例化和 Docker 拉镜像应单独标注为冷启动,不能与热缓存中位数混合。
已内置的第一个运行时基线
仓库已有可复用样板:PortRepositoryParityTests.Authorization_Uncached_Rbac_Should_Emit_Repeatable_Capacity_Baseline(独立分片 authorization-capacity)以 10 租户 × 1000 次未缓存 RBAC 为负载,输出 P50/P95/P99、吞吐(ops/s)、CPU 时长、GC 分配字节与工作集增量。为下一个场景建基线时,直接复用它的测量结构与报告字段,而不是另起格式。
运行时场景清单
一个商业框架至少应覆盖这些端到端场景:
| 场景 | 负载变量 | 必看信号 |
|---|---|---|
| 授权读取 | 权限数、角色数、缓存冷热 | p95/p99、cache hit、错误率 |
| 租户化列表 | 行数、过滤器、排序/分页 | DB duration、扫描行、分配 |
| 事务写 + CAP | 并发、消息大小、broker 延迟 | API latency、outbox backlog、消费延迟 |
| 文件预签名 | 对象存储延迟、并发会话 | 外部依赖、超时、失败率 |
| Workflow 完成任务 | 流程深度、并行分支、变量大小 | DB 锁、timer backlog、吞吐 |
| 搜索/报表 | 索引规模、投影宽度、时间窗口 | p99、内存、依赖 CPU |
| JobHost | 批大小、重试、积压量 | lag、吞吐、失败/死信 |
每个场景都需要固定输入和正确性断言。例如列表压测不仅记录 200,还要验证 tenant、总数、排序和分页没有因优化而变化。
基线记录格式
# 示例记录结构;实际结果应存入团队约定的可追溯制品库。scenario: workflow-complete-taskcommit: 0123456789abcdefruntime: .NET 10.0.xenvironment: perf-sqlserver-2026-07dataset: 100-tenants-1m-instancesload: concurrency: 32 duration: 10mresult: # 延迟、吞吐和错误率必须来自同一轮负载,不能跨运行拼接。 throughput_per_second: 0 # 用实测值替换;0 不是框架承诺。 p95_ms: 0 p99_ms: 0 error_rate: 0notes: warm-cache, no deployment overlap记录必须包含 Commit、Runtime、Release/Debug、CPU/内存/OS、容器资源、数据库版本、数据规模、租户数、索引状态、缓存冷热、并发、请求分布、持续时间、错误率、吞吐、p50/p95/p99、CPU、分配和依赖延迟。
可比较性规则
- 同一基线只比较同硬件等级、依赖版本、数据规模和负载模型。
- 固定客户端与服务端时钟源,记录压测机是否成为瓶颈。
- 预热 JIT、连接池和常用缓存,但另保留冷启动场景。
- 使用多轮样本,报告中位数和方差,不只挑最快一轮。
- 部署、备份、索引维护等干扰事件必须记录。
- 测量方法改变时建立新 baseline ID,不拼接旧曲线。
跨机器的绝对毫秒通常不可直接比较。优先比较同环境相对变化,并以明确 SLO/资源预算作为最终边界。
判断回归
平均值会掩盖尾延迟,单看 p99 又可能被少量基础设施故障支配。判定应同时看:吞吐、p50/p95/p99、错误率、超时率、CPU、内存/GC 分配、数据库与 broker 延迟、队列积压。
候选判定示例:- 正确性断言必须 100% 通过;否则直接失败。- p95/p99 与错误率不得超过场景预算。- 相对回归必须连续多轮出现,且超过历史噪声带。- 资源成本增长即使延迟未变,也要解释原因。这些阈值是团队为具体环境制定的策略,不是 BitzOrcas 当前源码内置的统一数值。不要在说明书中虚构全平台固定 TPS 或 p95 承诺。
定位回归
先把变化最小化到一个场景,再按链路分段:客户端排队、ASP.NET Core、授权/租户 pipeline、序列化、ORM/SQL、Redis、RabbitMQ、对象存储或 JobHost。使用 trace 找最长 span,使用 profile 区分 CPU 与 allocation,使用数据库计划判断扫描、索引和锁。
如果新增安全校验导致合理成本增长,记录风险收益、预算影响、Owner 与优化计划;不能静默放宽阈值。例外必须有到期日和运行态告警。
常见陷阱
- 用 Debug 或附加调试器的结果建立发布基线;
- 把首轮 JIT、首次镜像拉取混进稳定样本;
- 多租户查询只验证速度,不验证隔离;
- 只测成功路径,遗漏超时、取消、并发冲突和依赖降级;
- 只看 API latency,把积压转移给 Outbox/JobHost;
- 在共享 CI 抖动中用单次耗时阻塞合并;
- 改变数据量、索引或负载后仍沿用旧 baseline ID。
交付清单
- 场景、数据、正确性断言和 SLO 均可复现;
- 原始结果和汇总绑定 Commit 与环境;
- 冷/热状态、重复轮次和方差已记录;
- API、数据库、broker、缓存与 JobHost 信号可以关联;
- 回归有最小复现、根因、修复前后对比;
- 例外有 Owner、到期日、监控与退出条件;
- 文档明确区分“现有自动门禁”和“建议建设的基准”。