Files
Genarrative/.hermes/plans/2026-05-11_195214-k6-works-list-load-test-plan.md

311 lines
13 KiB
Markdown
Raw Permalink 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.
# K6 作品列表压测计划(使用 spacetime-migration-7.json 作为数据源)
## 目标
使用 K6 对 Genarrative 的“作品列表”相关接口进行压测,并将用户提供的 `spacetime-migration-7.json` 作为压测数据源;数据处理时**只导入作品列表相关数据**,不导入用户、会话、钱包、埋点、运行存档等非作品表,避免把敏感或无关数据带入压测环境。
## 当前上下文
- 工作区:`/c/proj/Genarrative`
- 原始迁移文件:`C:\Users\DSK\AppData\Local\hermes\cache\documents\doc_150e84029b2d_spacetime-migration-7.json`
- 已确认原始迁移文件结构:
- `schema_version = 1`
- `tables = 53`
- 作品相关表中当前有数据的重点表:
- `puzzle_work_profile`80 行
- `custom_world_profile`1 行
- `match3d_work_profile`0 行
- `big_fish_*`:当前样本中相关表为 0 行
- 原始文件还包含 `user_account``auth_identity``refresh_session``profile_wallet_ledger``asset_object`、运行记录等数据,压测导入时必须过滤。
- 当前仓库未发现现成 K6 脚本或 `k6` 相关文件,需要新增压测脚本与数据提取脚本。
- `package.json` 当前有 `dev/dev:rust/test/check` 等脚本,未发现 K6 npm script。
## 范围约束
### 本次只导入/使用
1. 作品列表表:
- `puzzle_work_profile`
- `custom_world_profile`
- 后续若接口覆盖其他玩法,可扩展:
- `match3d_work_profile`
- `square_hole_work_profile`(以实际 SpacetimeDB 表名为准)
- `big_fish_work_profile`(以实际 SpacetimeDB 表名为准)
- `visual_novel_work_profile`(以实际 SpacetimeDB 表名为准)
2. 为作品列表卡片展示所需的最小字段:
- 稳定 ID`profile_id``work_id``public_work_code`
- 标题:`work_title` / `level_name` / `world_name`
- 描述:`work_description` / `summary` / `summary_text` / `subtitle`
- 作者:`owner_user_id``author_display_name``author_public_user_code`
- 封面:`cover_image_src``cover_asset_id`(如果接口只返回 asset id则压测阶段不额外导入二进制 asset
- 状态与计数:`publication_status``published_at``play_count``like_count``remix_count`
- 作品内容摘要:`levels_json``profile_payload_json``theme_tags_json` 等列表渲染或进入作品详情可能需要的 JSON 字段
### 本次不导入/不使用
- 认证与账号:`user_account``auth_identity``refresh_session``auth_store_snapshot`
- 用户资产与钱包:`profile_wallet_ledger``profile_dashboard_state``profile_redeem_*``profile_invite_*`
- 游玩历史/存档/运行态:`profile_played_world``public_work_play_daily_stat``puzzle_runtime_run``profile_save_archive``runtime_snapshot`
- AI 任务过程:`ai_task``ai_task_stage``ai_text_chunk`
- asset 二进制与绑定:`asset_object``asset_entity_binding`,除非后续确认作品列表接口强依赖它们;即便需要,也只导入作品列表封面所需的最小 metadata不导入原始大对象。
## 推荐目录与文件
建议新增:
```text
.hermes/plans/2026-05-11_195214-k6-works-list-load-test-plan.md # 本计划
scripts/loadtest/extract-works-list-data.mjs # 从迁移文件提取作品列表数据
scripts/loadtest/k6-works-list.js # K6 压测脚本
scripts/loadtest/data/works-list.sample.json # 过滤后的样例数据(不要提交敏感原始迁移全量)
scripts/loadtest/README.md # 执行说明与指标阈值
```
可选新增 npm scripts
```json
{
"loadtest:extract-works": "node scripts/loadtest/extract-works-list-data.mjs",
"loadtest:k6:works": "k6 run scripts/loadtest/k6-works-list.js"
}
```
## 数据提取方案
### 输入
默认读取:
```bash
node scripts/loadtest/extract-works-list-data.mjs \
--input "C:/Users/DSK/AppData/Local/hermes/cache/documents/doc_150e84029b2d_spacetime-migration-7.json" \
--output scripts/loadtest/data/works-list.local.json
```
### 输出结构
建议输出为 K6 直接可读的 JSON
```json
{
"source": "spacetime-migration-7.json",
"generatedAt": "<iso datetime>",
"tables": {
"puzzle_work_profile": [
{
"profile_id": "...",
"work_id": "...",
"owner_user_id": "...",
"work_title": "...",
"work_description": "...",
"publication_status": "Published",
"published_at": { "__timestamp_micros_since_unix_epoch__": 0 },
"play_count": 0,
"like_count": 0,
"levels_json": "..."
}
],
"custom_world_profile": []
},
"workIds": {
"puzzle": ["<profile_id>"],
"customWorld": ["<profile_id>"]
}
}
```
### 过滤原则
1.`tables[].name` 白名单过滤,只保留作品 profile 表。
2. 对每个 row 再按字段白名单过滤避免误带账号、手机号、token、钱包流水等字段。
3. 对特别大的字段进行处理:
- `cover_image_src` 如果是 `data:image/...base64`,默认替换为占位符或截断,避免压测数据文件过大。
- `levels_json``profile_payload_json` 保留原文,但可以记录大小;如果过大,再提供 `--compact` 选项只保留摘要。
4. 输出 `.local.json` 默认加入 `.gitignore`;如果要提交样例数据,只提交脱敏/裁剪后的 `works-list.sample.json`
## K6 压测接口矩阵
需要先确认本地 api-server 实际端口。默认以 `http://127.0.0.1:8787` 为例,实际运行时通过环境变量覆盖:
```bash
BASE_URL=http://127.0.0.1:<actual-api-port> k6 run scripts/loadtest/k6-works-list.js
```
初版建议覆盖以下“作品列表”读接口,具体路径以仓库服务端路由为准,实施时需要通过搜索 api-server 路由确认:
| 场景 | 目的 | 候选路径 |
| --- | --- | --- |
| 拼图作品列表 | 作品列表主场景之一,当前数据量最多 | `/api/creation/puzzle/works` 或实际 puzzle works list route |
| RPG/自定义世界作品列表 | 使用 `custom_world_profile` 数据 | `/api/creation/custom-world/works` 或实际 custom world works route |
| 作品详情/启动前读取 | 模拟用户从列表点进作品 | `/api/creation/*/works/:profileId``/api/runtime/*/works/:profileId` |
| 公开作品库 | 如果首页/发现页依赖 | `/api/runtime/*/works` 或 gallery/list route |
> 注意:不要凭空固定 endpoint。实施阶段先用 `search_files` / 路由源码确认真实路径,再写入 K6 脚本。
## K6 场景设计
### 阶段 1基线 smoke
目的:确认脚本、数据和目标服务可用。
```js
export const options = {
scenarios: {
smoke: {
executor: 'constant-vus',
vus: 1,
duration: '30s'
}
},
thresholds: {
http_req_failed: ['rate<0.01'],
http_req_duration: ['p(95)<800']
}
};
```
### 阶段 2常规读压
目的:模拟日常列表浏览。
- `constant-vus`: 10/25/50 三档
- 每个 VU 随机选择作品类型和列表分页参数
- `sleep(0.5~2s)` 模拟用户停留
- 阈值建议:
- `http_req_failed < 1%`
- `p95 < 800ms`
- `p99 < 1500ms`
### 阶段 3峰值/突刺
目的:模拟首页入口或活动导致的作品列表突增。
- `ramping-arrival-rate`
- 从 5 RPS 增长到 100 RPS维持 2~5 分钟,再降回
- 单独输出 `checks`:列表接口状态码、响应 JSON shape、items 数量
### 阶段 4容量探索
目的:找瓶颈,不作为每次回归必跑。
- 每轮提升 RPS 或 VU
- 观察api-server CPU/内存、SpacetimeDB 日志、错误率、p95/p99
- 一旦 `http_req_failed >= 5%` 或 p95 持续超过 2s停止继续加压并记录容量点。
## K6 脚本设计要点
1. 使用 `SharedArray` 加载 `works-list.local.json`,避免每个 VU 重复解析大 JSON。
2. 基于数据源里的 `profile_id` / `work_id` 随机抽样,保证请求覆盖真实作品 ID。
3. 对列表接口添加分页/排序 query例如
- `?limit=20&offset=0`
- `?pageSize=20&cursor=...`(以真实 API 为准)
4. 使用 `check()` 验证:
- HTTP 200
- 响应体是 JSON
- `items``works` 是数组
- 列表项包含 `profileId/profile_id`、标题字段、状态字段
5. 使用 `Trend` / `Rate` 细分指标:
- `works_list_duration`
- `works_detail_duration`
- `works_list_shape_error_rate`
6. 支持环境变量:
```bash
BASE_URL=http://127.0.0.1:8787 \
WORKS_DATA=scripts/loadtest/data/works-list.local.json \
SCENARIO=baseline \
k6 run scripts/loadtest/k6-works-list.js
```
## 实施步骤
1. **确认路由**
- 搜索 api-server / BFF 的作品列表路由。
- 明确各玩法对应 endpoint、鉴权要求、分页参数、返回字段。
2. **实现数据提取脚本**
- 新增 `scripts/loadtest/extract-works-list-data.mjs`
- 只按表白名单读取作品列表 profile 表。
- 对字段做白名单与脱敏/截断。
- 输出 `works-list.local.json`
3. **生成本地压测数据**
- 用用户提供的迁移文件生成 `scripts/loadtest/data/works-list.local.json`
- 验证输出只包含作品表和作品字段。
4. **实现 K6 脚本**
- 新增 `scripts/loadtest/k6-works-list.js`
- 支持 `BASE_URL``WORKS_DATA``SCENARIO`
- 覆盖列表接口,必要时增加详情/启动前读取接口。
5. **新增执行说明**
-`scripts/loadtest/README.md` 写明:安装 K6、启动本地 dev 栈、提取数据、运行 smoke/baseline/spike、查看结果。
6. **本地验证**
- 启动 Genarrative dev 栈;注意端口可能漂移,使用实际 api-server 端口。
- 跑 smoke`SCENARIO=smoke`
- 确认失败率、p95、响应 shape。
7. **可选集成 npm scripts**
- 如果团队希望标准化入口,再加入 `package.json` scripts。
8. **记录结果**
- 将 smoke/baseline/spike 的结果摘要追加到 `scripts/loadtest/README.md` 或单独保存到 `.hermes/plans/` 的结果文档中。
## 启动与运行建议
本地服务启动按当前 Genarrative dev 栈约定:
```bash
npm run dev
```
如果 SpacetimeDB/API/Vite 端口被占用,项目脚本会寻找可用端口;压测时必须从启动日志中读取实际 api-server 地址,并传给 K6
```bash
BASE_URL=http://127.0.0.1:<actual-api-port> \
WORKS_DATA=scripts/loadtest/data/works-list.local.json \
SCENARIO=smoke \
k6 run scripts/loadtest/k6-works-list.js
```
## 验证标准
### 数据源验证
- `works-list.local.json` 中只出现作品 profile 表。
- 不出现以下字段或内容:
- `password_hash`
- `refresh_token_hash`
- `phone_number_e164`
- `phone_number_masked`
- `wallet_ledger_id`
- `auth_identity`
- `user_account`
- `puzzle_work_profile` 行数应接近原始文件中的 80 行。
- `custom_world_profile` 行数应接近原始文件中的 1 行。
### K6 smoke 验证
- 所有目标接口返回 2xx。
- `http_req_failed < 1%`
- 响应 JSON shape 与 shared contracts 对齐:`items``works` 数组。
- K6 输出中能区分不同 endpoint 的耗时。
### 性能阈值初稿
- Smoke`p95 < 800ms`,失败率 `< 1%`
- Baseline`p95 < 1000ms``p99 < 2000ms`,失败率 `< 1%`
- Spike允许短暂 p95 抖动,但 1 分钟内应恢复;失败率 `< 5%`
阈值后续需要结合本地机器性能、SpacetimeDB 本地模式和正式部署规格调整。
## 风险与注意事项
1. **原始迁移文件包含敏感数据。** 必须只提取作品列表白名单字段,禁止把原始 JSON 全量提交到仓库。
2. **base64 封面可能导致压测数据膨胀。** 默认截断或替换为占位符,除非本次明确要测封面 payload 对响应体积的影响。
3. **本地 SpacetimeDB 与 api-server 端口会漂移。** 不要硬编码端口,运行时通过 `BASE_URL` 注入。
4. **列表接口可能需要鉴权。** 若实际接口要求登录,不要导入真实 refresh session应使用本地测试账号或专门的压测 token 生成流程。
5. **作品表名/接口路径可能与候选名称不完全一致。** 实施前必须以源码路由为准。
6. **本计划仅保存压测方案,不执行实际压测。** 后续执行时再创建/修改脚本、导出过滤数据、跑 K6 并记录结果。
## 开放问题
1. 压测目标是本地 dev 栈、测试环境,还是预发/生产只读接口?不同环境阈值和安全边界不同。
2. “作品列表”是否只包含拼图和自定义世界,还是要覆盖 match3d、square-hole、big-fish、visual-novel 的统一列表入口?
3. 是否允许使用专门压测账号/token如果接口无鉴权则无需处理。
4. 是否需要测封面/asset 加载,还是只测作品列表 JSON API