---
name: performance-testing
description: Microi 高并发、性能压力测试规范。用于对 ApiEngine、V8 事件、FormEngine CRUD、VS Code 插件性能页、压力/尖峰/长稳测试、报告、并发、吞吐、延迟分位和瓶颈诊断做压测。
---

> **Codex 非阻塞自动更新：** 当前宿主为 Codex 时，吾码 CLI、Codex 插件与工作区 AI/MCP 由后台自动更新；需要诊断时读取 `../microi-codex-installer/SKILL.md`。更新失败、等待空闲或尚未重载均不得阻断当前、正在进行或新建任务。非 Codex 宿主跳过此项。

# Microi 高并发性能压力测试

> 排查长任务时，严禁把“等待或执行超过 1 分钟”直接判定为失败；接口引擎、V8、表单写入、导入导出、批处理等合法长任务默认应支持 10 分钟以上，同步链路推荐 10 分钟，排队窗口推荐 30 分钟。

本 Skill 用于设计、实现和执行 Microi 吾码性能测试，覆盖接口引擎、V8 事件、FormEngine 表 CRUD、前端工作流与 VS Code 插件性能测试页。

## 总原则

1. **真实链路优先**：接口引擎压测默认走真实 HTTP `/apiengine/{ApiEngineKey}`，不要用调试执行接口替代线上链路。
2. **事件隔离与真实触发分开**：单独测 V8 事件代码可用 `ExecuteV8Event`；要验证表单事件真实成本，必须通过 FormEngine Add/Upt/Del 触发服务端事件。
3. **读写分级**：先跑只读基线，再跑写入压力；写入压测必须使用测试表、测试租户或自动清理策略。
4. **渐进升压**：不要一上来极限并发。标准顺序是 smoke -> baseline -> load -> stress -> spike -> soak。
5. **报告必须可读**：报告至少包含并发数、总请求、成功/失败、RPS、平均耗时、P50/P90/P95/P99、错误 Top、每秒趋势和测试参数。
6. **通用坑必须回写 Skill**：修复过程中发现可复用的平台、插件、MCP、前端、V8、FormEngine 坑时，不要只写到本地 memory；必须更新对应 `microi.skills/*/SKILL.md`，必要时新增 Skill，让 VS Code 插件打包后其他用户也能受益。

## 测试类型

| 类型 | 目的 | 建议配置 |
| --- | --- | --- |
| Smoke | 确认目标可用 | 并发 1-2，总请求 3-10 |
| Baseline | 单机基线 | 并发 1，总请求 50-200 |
| Load | 常规高并发 | 目标业务并发，持续 3-10 分钟 |
| Stress | 找瓶颈 | 阶梯增加并发直到错误率或 P95 不可接受 |
| Spike | 突增流量 | 短时间从低并发升到高并发，再回落 |
| Soak | 长稳态 | 30 分钟到数小时，重点看内存、连接池、缓存和慢查询 |

## 接口引擎压测

默认使用真实入口：

```text
POST /apiengine/{ApiEngineKey}
Body: { "OsClient": "xxx", ...业务参数 }
Header: Authorization / Token 复用当前登录态
```

规则：

- 除非明确在调试 V8 代码，否则不要用 `/api/V8Debug/ExecuteApiEngine` 作为性能结论。
- 测试参数要覆盖真实业务分支，包括分页、关键过滤条件、权限上下文、缓存命中/未命中场景。
- 返回必须按 DosResult 判断业务成功：`Code === 1` 才算成功；HTTP 200 但 `Code !== 1` 要计入失败。
- 报告中隐藏 token、密码、API Key 等敏感参数。

## V8 事件压测

两种模式要区分清楚：

1. **隔离执行**：使用 `/api/V8Debug/ExecuteV8Event`，传 `EventType`、`V8Code`、`Form`，用于判断单段 V8 代码在当前服务器上的解释执行成本。
2. **真实触发**：通过 `/api/formengine/AddFormData`、`/api/formengine/UptFormData`、`/api/formengine/DelFormData` 触发 `SubmitBeforeServerV8`、`SubmitAfterServerV8`、`DataFilterV8`，用于判断真实表单保存/查询成本。

常见结论不能混用：隔离执行快，不代表真实保存快；真实保存慢也可能是 SQL、索引、事件、外部 HTTP、缓存或权限链路导致。

## FormEngine CRUD 压测

推荐标准 Controller 路由：

```text
POST /api/formengine/GetFormData
POST /api/formengine/GetTableData
POST /api/formengine/AddFormData
POST /api/formengine/UptFormData
POST /api/formengine/DelFormData
```

请求体必须包含：

```json
{ "OsClient": "xxx", "FormEngineKey": "表名", "Id": "测试行Id", "Name": "测试数据" }
```

安全规则：

- CRUD 压测默认创建带 `perf_` 前缀的测试 Id，结束后删除本次创建的数据。
- 不要对生产业务表直接跑删除压力，除非用户明确确认并指定可清理条件。
- Add/Upt payload 必须由用户或测试清单明确给出，避免写不存在字段导致大量业务失败。
- 查询压测要区分 `GetFormData` 单行查询和 `GetTableData` 列表查询；列表查询必须带分页。

## VS Code 插件性能测试页实现规范

插件配置页的“性能测试”功能应遵守：

- Webview 只负责收参、进度和报告展示；请求由扩展宿主执行，复用 `ApiClient` 登录态，避免 CORS 和 token 泄露。
- 支持三类目标：接口引擎、V8 事件、表 CRUD。
- 支持手动 JSON 参数、并发数、总迭代次数、持续时间、渐进升压、单请求超时。
- 支持停止测试：停止发起新请求，等待已发出的请求返回后生成部分报告。
- 支持保存 HTML 报告到 `.microi-performance/`，并可从 VS Code 打开。
- 报告至少展示：完成、成功、失败、RPS、平均耗时、P95/P99、错误率、延迟分布、每秒趋势、错误 Top。

## 结果判定

性能测试不是只看 RPS。结论必须同时看：

- 错误率是否为 0 或低于目标阈值。
- P95/P99 是否满足业务 SLA。
- 是否出现连接超时、身份过期、数据库死锁、缓存穿透、外部接口超时。
- 写入场景是否确实触发了 V8 事件并完成清理。
- 长时间测试后内存、线程、连接池、Redis、数据库连接是否稳定。

## 用户行为日志队列压测

行为审计不能只测 MongoDB 写入速度，必须分开验证请求线程入队、后台批量持久化、故障 spool 和重放幂等：

- 使用本地 Fake Mongo/测试库注入故障，不得为了验证日志队列让生产 MongoDB 停机。
- 至少覆盖 10 万事件、100 以上并发生产者，报告入队吞吐和入队 P95/P99；入队阶段不得等待 MongoDB。
- 模拟前若干批次写 MongoDB 失败，确认事件先进入 spool，恢复后按 `EventId` 幂等重放，最终唯一事件数必须等于入队成功数。
- 在未满批次的 150ms 聚合窗口主动触发正常停机，确认已从 Channel 取出的局部批次仍会写 spool，重启后不丢失。
- 验收同时检查 spool 剩余文件数、重复 EventId、队列内存计数、失败批次数和最后持久化时间；只看接口 HTTP 200 不算通过。
- “有界 Channel + 无界 ConcurrentQueue 溢出区”仍然是无界队列，禁止作为保护方案。主队列和内存重试区都必须有硬容量；两者满时要同步写持久化 spool/WAL 形成回压，并断言 `EmergencySpooled` 可观测、`Dropped=0`。
- 进程被强制结束、宿主机掉电等场景若要求绝对零丢失，必须采用外部持久消息队列或同步 WAL；内存 Channel 加异步 spool 只能保证 Mongo 故障和正常停机，不得宣称覆盖尚未落盘的强杀窗口。

## 网络流量可观测性与写入性能（强制）

查询动作、AI/MCP 调用、权限与数据解释边界统一读取 `../system-observability/SKILL.md`；本节只定义热路径和压测门禁。

API 网络监控必须同时展示“网卡/容器网络命名空间计数”和“可归因 HTTP 请求体、响应体计数”，并明确两者不能直接画等号。Docker NetIO 还可能包含 TLS/HTTP 头、重传、数据库、Redis、MongoDB、对象存储、外部 HTTP 与容器内部通信；界面必须展示未归因差值、采样范围、节点、窗口和数据边界，禁止把差值伪装成某个帐号或接口的精确流量。

- 请求热路径只做原子计数和有硬上限的分钟桶聚合；端点、IP、帐号、租户、内容类型等维度必须限制基数和保留 TOP N，禁止逐请求同步写 MySQL/MongoDB、同步序列化完整请求或创建无界队列。
- 高频普通明细只驻留短窗口内存；大文件、可疑、错误或慢请求等有诊断价值的样本进入现有有界日志队列并异步批量写 MongoDB。Mongo 故障、队列满和停机语义继续遵守本 Skill 的 spool/WAL 规则。
- 长期趋势写 MySQL 固定时间桶汇总（默认 5 分钟），按 `BucketStart + Node + DimensionType + DimensionKeyHash` 形成确定性幂等键，批量 upsert；查询必须命中“时间桶+维度+总字节”等索引，页面默认分页 15 条，不得扫描 Mongo 明细生成每次总览。
- 禁止持久化 QueryString、Cookie、Token、Authorization、请求正文或响应正文。文件只记录经过清洗且有长度上限的文件名/扩展名/数量/字节；IP 必须标注是可信代理解析后的客户端 IP 还是直接连接 IP。
- 验收至少覆盖：并发计数无负数、维度基数有界、敏感值不落盘、固定时间桶幂等重放、Mongo 故障不阻塞请求、匿名/登录用户区分、上传/下载字节、网卡重置/回绕、低流量和大流量样本、服务重启后的历史查询，以及开启监控前后 P95/P99、CPU、分配率和内存差异。

### 多节点与滚动重启压测

- 至少启动两个 API/Worker 实例连接同一 Redis、业务数据库和 MongoDB，通过同一负载均衡入口并发施压；禁止用单进程内开两个对象冒充分布式验收。
- 同一 `Microi.Job`/定时任务同时到点时，只允许一个节点取得带 TTL、持有者令牌和续租能力的租约；同时验证锁过期后接管、旧持有者不得释放新锁，以及业务幂等约束可阻止锁超时造成的重复副作用。
- 对同一请求、消息和日志 `EventId` 做跨节点重复投递，最终数据库业务结果和审计事件都只能有一份；节点级本机去重不算通过。
- 在队列有未完成工作、锁已取得、写库成功但响应未返回等时间点分别终止一个节点，再启动连接同一持久卷和共享存储的替代节点，验证 spool/outbox 自动恢复、共享状态不损坏、其余节点持续服务；节点身份由平台自动管理。
- 做滚动升级时让新旧版本同时接流量，验证数据库、Redis 值、消息和 API 合约双向兼容；readiness 关闭后节点不得继续接收新工作，宽限期结束前应完成排空或可靠移交。

## 后台批处理与初始化任务防护

启动初始化、缓存预热、多语言同步、批量翻译、批量建索引、批量修复元数据这类后台任务，不能按普通接口逻辑无界并发执行。即使每个单次 SQL 都很快，几千条元数据逐条 `Get/Add/Upt`、多个租户同时 `Task.Run`、外部翻译超时堆积，也会把数据库连接池、MySQL `max_connections`、`max_connect_errors` 和线程池一起打满。

强制要求：

- 全量后台任务必须有全局并发上限；多租户初始化默认按租户串行或小并发队列执行，不允许启动时对所有租户同时打满数据库。
- 单租户内的后台 DB 写入要有租户级限流，不能和正常用户请求抢满连接池。
- 批处理遇到 `too many connections`、`blocked because of many connection errors`、连接超时等数据库连接压力错误时，必须熔断退避一段时间，停止继续重试撞库。
- 外部 HTTP/翻译/AI 调用必须有超时、并发阀门和失败降级，超时任务不能无限制堆到线程池。
- 能预加载或缓存的元数据不要在循环里重复查库，例如树形根节点、字段结构、已有词条字典等。
- 运行时缓存预热必须先做行数预算检查，只选择必要列并按稳定主键游标固定小分页，同时设置原始行数、字符/字节总预算和单 SQL 超时；只有全部读取成功后才能原子替换旧缓存。多语言缓存只加载租户实际启用的语言列；禁止 `SELECT * ... ToList()` 后再复制为字典，也不能在迁移或依赖检查失败后继续执行无界缓存加载。
- 进度日志应按批次写入，避免每条数据都写日志表。

### API 进程内存熔断验收

- 进程必须同时有软阈值和硬阈值。软阈值进入 readiness 失败并拒绝新业务请求；硬阈值连续命中后先有界停机，失控任务不响应取消时允许在宽限期后强制退出，不能等宿主机 OOM。
- liveness 与 readiness 必须分开验证：内存压力下 liveness 仍可用于判断进程存活，readiness 必须让负载均衡摘除当前节点。
- 启动 API、Node、浏览器自动化和长稳压测前后都要记录进程树 PID；每 15-30 秒采集 Working Set、Private Bytes、托管堆和整机可用内存，达到全机 95% 时立即终止本次测试进程树。
- 进程内存熔断必须使用实际驻留内存（Windows Working Set / Linux RSS）作为压力指标。Linux 的 `PrivateMemorySize64` 可能包含 .NET GC 预留的巨大虚拟地址空间，只能用于辅助诊断，禁止与 Working Set 取较大值后作为拒绝请求或退出进程的依据。
- 内存阈值不能固定为与机器规格无关的小常量。平台按 cgroup 容器限额或宿主机物理内存动态计算，软阈值为 95%、硬阈值为 98%，不再增加环境变量或 appsettings 参数；多节点或数据库共用宿主机时必须给每个容器设置独立 memory limit，防止各节点重复使用整机额度。
- 内存修复不能只用“限制进程最大内存”代替根因治理。报告必须指出造成增长的具体对象/查询/队列，并分别验证根因边界和进程最后防线。

## 数据库连接压力防护

高并发问题不能只靠调大 MySQL `max_connections`。平台后端和 ORM 层必须把数据库连接视为受保护资源，所有普通接口、接口引擎、FormEngine、V8 事件、后台任务和批量导入都应该走统一连接打开入口。

强制要求：

- ORM 层需要按连接串限制并发 `Open/OpenAsync`，避免瞬时 1 万个请求同时抢连接池或打爆 MySQL 握手。
- 遇到 `Host ... is blocked because of many connection errors`、`too many connections`、连接池超时、连接超时等错误时，应短时间熔断退避，快速失败并停止继续撞库。
- API 层需要对租户、用户、IP、重接口和写接口配置限流/并发阀门；压力超过阈值时返回明确的“系统繁忙/稍后重试”，不要无限排队。
- 连接串的 `Max Pool Size` 不能所有租户默认写很大；要按单进程、租户数、读写库和 MySQL `max_connections` 统一核算。
- 事务、`DbDataReader`、批量导入、后台任务必须确保连接按 `using/Dispose/Close` 释放；压测后要观察连接池、MySQL 当前连接数和等待线程是否回落。

## V8 / 接口引擎失控保护

接口引擎和表单 V8 事件属于用户可编程能力，必须默认假设会出现误写死循环、循环套循环查库、未分页大查询、外部接口长时间超时等问题。平台要在运行时给出硬保护，而不能只靠代码审查。

强制要求：

- V8/Jint 默认超时、最大语句数、内存、递归深度必须保守；需要按租户或机器规格调整时统一在 SaaS 引擎 `sys_osclients` 的受控字段或接口引擎自身配置中维护，并由代码硬上限夹住。禁止为此新增 API 环境变量或 `appsettings` 节点。
- V8Engine.Run 入口必须有全局、租户级、接口/事件级并发阀门；过载时返回 `Code=0` 和“系统繁忙，请稍后重试”，不要继续进入事务和数据库。
- HTTP 入口应在进入 Controller 前做全局、租户、路由、接口引擎级背压；过载时直接返回 DosResult 风格 JSON，保证前端能弹出明确提示。
- 接口引擎配置里的 Timeout、MaxStatements、LimitMemory、LimitRecursion 不能无限放大，必须被平台级最大值夹住。
- 对外部 HTTP、AI、翻译、短信、第三方 ERP 这类慢操作，V8 扩展层必须设置超时和并发限制，不能把同步等待堆满线程池。

## 复盘写回规则

每次压测暴露通用问题后，把经验写回最贴近的 Skill：

- FormEngine 路由/CRUD 坑 -> `v8-formengine-http/SKILL.md` 或本 Skill。
- V8 代码性能/缓存/SQL -> `v8-sql-query`、`v8-cache-pattern`、`v8-debugging`。
- 前端弹层、主题、表格搜索等 UI 运行时坑 -> `microi-client-frontend/SKILL.md`。
- 插件/MCP 能力缺口 -> 本 Skill、`playwright-e2e` 或 `microi-system-delivery`。

本地 memory 只能作为临时笔记；对用户和其他安装插件的人有价值的规则，必须进入 `microi.skills`。

## 长任务限流原则

吾码经常承载大型业务系统，接口引擎、初始化任务、批量导入、批量修复、ERP/第三方同步等场景可能需要处理上万条数据，正常执行时间可能超过 5 分钟。平台保护不能简单把“执行时间长”判定为异常，也不能默认短等待后快速失败。

强制要求：
- 长任务保护的核心是“并发阀门 + 排队限流 + 可配置超时”，不是粗暴拒绝。
- 接口引擎/V8 默认执行窗口应能覆盖常见长任务，默认建议不少于 10 分钟；私有部署需要放宽时使用 SaaS 引擎 `sys_osclients` 的动态运行配置或接口引擎自身配置，并受平台代码硬上限约束，不得新增 API 环境变量或 `appsettings` 节点。
- 入口限流对接口引擎/V8 应使用长排队窗口；只有排队窗口耗尽、请求被客户端取消、或平台资源已进入保护熔断时，才返回“系统繁忙/正在排队，请稍后重试”。
- 不允许为了保护数据库而误杀合法批处理。真正需要治理的是无界并发、循环套循环查库、未分页大查询、外部接口无限等待和连接泄漏。
- 对确实超过 HTTP/网关可承受时间的任务，应改造为后台任务/MQ/进度日志模式；后台任务进度优先通过吾码标准 WebSocket/SignalR 推送，不要让前端频繁轮询接口。但这属于交互形态升级，不应影响同步接口引擎的兼容性。
- 应用商城安装、初始化多语言、批量导入、批量修复、跨系统同步等用户明确需要等待进度的操作，优先做成后台任务。前端按钮使用 `DiyCommon.ApiEngine.RunBackground` 或菜单按钮后台任务字段，后端接口引擎通过 `V8.Method.UpdateBackgroundTask({ Progress, Message, Total, Current })` 持续上报。
- 后台任务必须有任务标题、状态、进度、耗时、错误摘要和清理/取消能力。取消只表示“请求停止后续步骤”，不能强行中断已经写入中的数据库事务。

## 恶意访问与雪崩防护

高并发保护要区分“合法排队”和“恶意攻击”。一个合法接口执行 5 分钟不代表攻击；一个 IP 在很短时间内疯狂请求、扫描不存在接口、反复触发 4xx/5xx，才应进入安全防护。

强制要求：
- HTTP 层应提供全局、租户、路由、接口引擎/V8 等并发阀门，用于排队和保护资源；IP 层安全防护只用于识别高频恶意请求，不要替代业务并发治理。
- 恶意访问记录不应每次请求都同步写数据库。最近访问明细可先保存在内存或专用轻量存储，真正封禁、解封、攻击识别事件才写系统日志和 `mci_` 安全表；必要的访问明细也要异步、采样或只记录可疑/拦截请求。
- 自动封禁必须有白名单、自动解封时间、手动解封接口和系统日志记录；封禁响应要返回 DosResult 风格 JSON，让前端能明确提示。
- 部署在 Nginx/网关后时，必须明确是否信任 `X-Forwarded-For`；公网直连且无法保证 Header 可信时，应使用真实 `RemoteIpAddress`。
- 安全阈值默认要保守，避免误伤同一公司出口 NAT 下的正常用户；需要更激进的策略时应交给网关/WAF 或私有部署配置调整。
- 系统级安全防护表必须使用 `mci_` 前缀，例如 `mci_security_attack_event`、`mci_security_ip_block`、`mci_security_access_log`。业务系统表不要使用 `mci_` 前缀。
- 攻击事件落库必须做原因去重和时间窗合并，不要把同一个 IP、同一个原因、同一个时间窗的失败原因重复写成上万条记录。
- 不能把“接口执行时间长”“排队时间长”“后台任务运行 5 分钟以上”单独作为恶意攻击依据。恶意判断主要看短时间高频请求、异常状态码爆发、扫描不存在路径、命中封禁后继续请求等行为。
