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

13 KiB
Raw Blame History

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_profile80 行
      • custom_world_profile1 行
      • match3d_work_profile0 行
      • big_fish_*:当前样本中相关表为 0 行
    • 原始文件还包含 user_accountauth_identityrefresh_sessionprofile_wallet_ledgerasset_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. 为作品列表卡片展示所需的最小字段:
    • 稳定 IDprofile_idwork_idpublic_work_code
    • 标题:work_title / level_name / world_name
    • 描述:work_description / summary / summary_text / subtitle
    • 作者:owner_user_idauthor_display_nameauthor_public_user_code
    • 封面:cover_image_srccover_asset_id(如果接口只返回 asset id则压测阶段不额外导入二进制 asset
    • 状态与计数:publication_statuspublished_atplay_countlike_countremix_count
    • 作品内容摘要:levels_jsonprofile_payload_jsontheme_tags_json 等列表渲染或进入作品详情可能需要的 JSON 字段

本次不导入/不使用

  • 认证与账号:user_accountauth_identityrefresh_sessionauth_store_snapshot
  • 用户资产与钱包:profile_wallet_ledgerprofile_dashboard_stateprofile_redeem_*profile_invite_*
  • 游玩历史/存档/运行态:profile_played_worldpublic_work_play_daily_statpuzzle_runtime_runprofile_save_archiveruntime_snapshot
  • AI 任务过程:ai_taskai_task_stageai_text_chunk
  • asset 二进制与绑定:asset_objectasset_entity_binding,除非后续确认作品列表接口强依赖它们;即便需要,也只导入作品列表封面所需的最小 metadata不导入原始大对象。

推荐目录与文件

建议新增:

.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

{
  "loadtest:extract-works": "node scripts/loadtest/extract-works-list-data.mjs",
  "loadtest:k6:works": "k6 run scripts/loadtest/k6-works-list.js"
}

数据提取方案

输入

默认读取:

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

{
  "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_jsonprofile_payload_json 保留原文,但可以记录大小;如果过大,再提供 --compact 选项只保留摘要。
  4. 输出 .local.json 默认加入 .gitignore;如果要提交样例数据,只提交脱敏/裁剪后的 works-list.sample.json

K6 压测接口矩阵

需要先确认本地 api-server 实际端口。默认以 http://127.0.0.1:8787 为例,实际运行时通过环境变量覆盖:

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

目的:确认脚本、数据和目标服务可用。

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
    • itemsworks 是数组
    • 列表项包含 profileId/profile_id、标题字段、状态字段
  5. 使用 Trend / Rate 细分指标:
    • works_list_duration
    • works_detail_duration
    • works_list_shape_error_rate
  6. 支持环境变量:
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_URLWORKS_DATASCENARIO
    • 覆盖列表接口,必要时增加详情/启动前读取接口。
  5. 新增执行说明
    • scripts/loadtest/README.md 写明:安装 K6、启动本地 dev 栈、提取数据、运行 smoke/baseline/spike、查看结果。
  6. 本地验证
    • 启动 Genarrative dev 栈;注意端口可能漂移,使用实际 api-server 端口。
    • 跑 smokeSCENARIO=smoke
    • 确认失败率、p95、响应 shape。
  7. 可选集成 npm scripts
    • 如果团队希望标准化入口,再加入 package.json scripts。
  8. 记录结果
    • 将 smoke/baseline/spike 的结果摘要追加到 scripts/loadtest/README.md 或单独保存到 .hermes/plans/ 的结果文档中。

启动与运行建议

本地服务启动按当前 Genarrative dev 栈约定:

npm run dev

如果 SpacetimeDB/API/Vite 端口被占用,项目脚本会寻找可用端口;压测时必须从启动日志中读取实际 api-server 地址,并传给 K6

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 对齐:itemsworks 数组。
  • K6 输出中能区分不同 endpoint 的耗时。

性能阈值初稿

  • Smokep95 < 800ms,失败率 < 1%
  • Baselinep95 < 1000msp99 < 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