docs: record db-limited capacity findings

This commit is contained in:
Codex
2026-07-01 06:33:39 +08:00
parent 458c0be94f
commit a45a4f1a5b
7 changed files with 63 additions and 15 deletions

View File

@@ -53,7 +53,7 @@
| 对象存储/资料安全 | √ 可联调,待生产 AV/CDN | OSS/COS/Supabase Storage 签名、上传确认、短签名预览下载、水印 traceId、复检和安全扫描地基已完成生产配置会拒绝 local_dev、非 HTTPS 公开 URL、非官方阿里云 OSS endpoint、阿里云 OSS 内网直签和未接外部扫描服务 | | 对象存储/资料安全 | √ 可联调,待生产 AV/CDN | OSS/COS/Supabase Storage 签名、上传确认、短签名预览下载、水印 traceId、复检和安全扫描地基已完成生产配置会拒绝 local_dev、非 HTTPS 公开 URL、非官方阿里云 OSS endpoint、阿里云 OSS 内网直签和未接外部扫描服务 |
| PocketBase 真实数据迁移 | √ 本地跑通,待人工复核 blocker | SQLite 导出、标准化导入、校验和抽样脚本已跑通;正式切换前处理缺用户订单和缺归属手册章节 | | PocketBase 真实数据迁移 | √ 本地跑通,待人工复核 blocker | SQLite 导出、标准化导入、校验和抽样脚本已跑通;正式切换前处理缺用户订单和缺归属手册章节 |
| Taro H5 三端前端 | √ 第一版可构建 | 学生端、租户后台、平台后台均有真实 API 页面;已补 H5 `index.html` 模板和发布产物守卫;后续继续补小程序兼容、视觉精修、状态管理、包体优化和端到端测试 | | Taro H5 三端前端 | √ 第一版可构建 | 学生端、租户后台、平台后台均有真实 API 页面;已补 H5 `index.html` 模板和发布产物守卫;后续继续补小程序兼容、视觉精修、状态管理、包体优化和端到端测试 |
| 生产安全/压测交付 | △ 本地真实数据压测已跑,云端待复测 | 本地 Docker/Supabase 已完成真实迁移数据 30/50/100/150 并发只读和混合读写压测;最新 Docker API 受限资源复核为 30 并发只读 0 错误、349.7 req/s、P95 202ms50 并发混合读写 0 错误、416.5 req/s、P95 215ms100 并发混合读写 0 错误、394.2 req/s、P95 415ms150 并发混合读写 0 错误、370.5 req/s、P95 622ms。50 worker 属于本地舒适区100 可用但延迟偏高150 是压力观察;上云后仍需执行生产 readiness、远程 Auth/RLS、4c16g 压测、PostgreSQL 调优`security:repo`、真实 `@codex-security` 扫描和上线证据门禁。 | | 生产安全/压测交付 | △ 本地真实数据压测已跑,云端待复测 | 本地 Docker/Supabase 已完成真实迁移数据 30/50/100/150 并发只读和混合读写压测Docker API 受限资源复核为 30 并发只读 0 错误、349.7 req/s、P95 202ms50 并发混合读写 0 错误、416.5 req/s、P95 215ms。进一步显式限制 DB 容器为 2 CPU/8G 后30 只读和 50 混合读写 0 错误但 P95 分别升至 1189ms/905ms未通过上线延迟门禁。上云后必须执行生产 readiness、PostgreSQL shared-host 调优、远程 Auth/RLS、4C16G 复测`security:repo`、真实 `@codex-security` 扫描和上线证据门禁。 |
更完整的进度看这些文档: 更完整的进度看这些文档:
@@ -640,6 +640,8 @@ README 只保留最新 Docker API 受限资源复核。更早的高资源 Docker
| Docker API 2c4g / 50 | 60s | 10% | 25,157 | 0.00% | 416.54 req/s | 215.00 ms | 292.56 ms | | Docker API 2c4g / 50 | 60s | 10% | 25,157 | 0.00% | 416.54 req/s | 215.00 ms | 292.56 ms |
| Docker API 2c4g / 100 | 60s | 8% | 23,873 | 0.00% | 394.18 req/s | 414.67 ms | 495.91 ms | | Docker API 2c4g / 100 | 60s | 8% | 23,873 | 0.00% | 394.18 req/s | 414.67 ms | 495.91 ms |
| Docker API 2c4g / 150 | 60s | 6% | 22,523 | 0.00% | 370.53 req/s | 621.55 ms | 750.70 ms | | Docker API 2c4g / 150 | 60s | 6% | 22,523 | 0.00% | 370.53 req/s | 621.55 ms | 750.70 ms |
| Docker API 2c4g + DB 2c8g / 30 | 120s | 0% | 8,029 | 0.00% | 66.50 req/s | 1188.62 ms | 1697.71 ms |
| Docker API 2c4g + DB 2c8g / 50 | 60s | 10% | 7,088 | 0.00% | 115.82 req/s | 905.13 ms | 1211.98 ms |
只读上线门禁继续要求 `includeWrites=false`;混合读写报告需要显式使用 `--allow-writes` 做人工容量观察,例如: 只读上线门禁继续要求 `includeWrites=false`;混合读写报告需要显式使用 `--allow-writes` 做人工容量观察,例如:
@@ -649,7 +651,7 @@ npm run perf:summary -- --input docs/refactor/performance-reports/api-benchmark-
使用 `--allow-writes` 时会输出 `capacityObservation`,不输出 `launchGateCheck`,不能把写入场景误填成生产上线门禁的只读证据。 使用 `--allow-writes` 时会输出 `capacityObservation`,不输出 `launchGateCheck`,不能把写入场景误填成生产上线门禁的只读证据。
本地结论:当前 Docker Desktop 分配 20 CPU、约 62.7GB 内存,高于常见 4 核 16G 云服务器,不能直接作为生产 SLA。2026-07-01 05:57 受限 API 容器复核中30 worker/120 秒只读上线门禁为 42,089 请求、0 错误、349.70 req/s、P95 202.20ms50 worker/60 秒/10% 写入为 25,157 请求、0 错误、416.54 req/s、P95 215.00ms100 worker/60 秒/8% 写入为 23,873 请求、0 错误、394.18 req/s、P95 414.67ms150 worker/60 秒/6% 写入为 22,523 请求、0 错误、370.53 req/s、P95 621.55ms,属于压力区。按单学生 0.05 到 0.2 req/s 的页面节奏粗略折算,当前受限 API 容器舒适观察区间约对应 2,080 到 8,330 名活跃在线学生的请求吞吐;保守按生产首版 30% 到 50% 预留容量时,可先规划约 620 到 4,160 名活跃在线学生,等云端 4 核 16G 复测和真实前端埋点后再上调。正式对外容量承诺必须在目标 4 核 16G 云服务器、生产 PostgreSQL 参数、生产对象存储/CDN 和真实前端请求节奏下复跑。脱敏摘要和剩余功能清单见: 本地结论:当前 Docker Desktop 分配 20 CPU、约 62.7GB 内存,高于常见 4 核 16G 云服务器,不能直接作为生产 SLA。2026-07-01 05:57 只限制 API 容器50 worker 混合读写为 25,157 请求、0 错误、416.54 req/s、P95 215.00ms这是“DB 未受限”的乐观观察2026-07-01 06:26 同时限制 API 2C/4G 和 DB 2C/8G 时30 worker 只读和 50 worker 混合读写仍 0 错误,但 P95 分别升至 1188.62ms 和 905.13ms未通过上线延迟门禁这是“DB 受限但未调 PostgreSQL 参数”的悲观观察。正式对外容量承诺必须在目标 4 核 16G 云服务器、生产 PostgreSQL shared-host 参数、`pg_stat_statements`、生产对象存储/CDN 和真实前端请求节奏下复跑。脱敏摘要和剩余功能清单见:
```text ```text
docs/refactor/performance-benchmark-summary-20260630.md docs/refactor/performance-benchmark-summary-20260630.md

View File

@@ -75,4 +75,15 @@
当前本地结论:在受限 API 容器下,后端真实刷题读写链路的舒适观察区间约为 416 req/s折算约 2,080 到 8,330 名活跃在线学生100 worker 仍可用但 P95 已超过 400ms150 worker 仍 0 错误但 P95 已超过 600ms视为压力区不建议作为生产承诺。受限 API 容器会明显压低上沿,因此上线承诺仍以云端受限资源复测为准。 当前本地结论:在受限 API 容器下,后端真实刷题读写链路的舒适观察区间约为 416 req/s折算约 2,080 到 8,330 名活跃在线学生100 worker 仍可用但 P95 已超过 400ms150 worker 仍 0 错误但 P95 已超过 600ms视为压力区不建议作为生产承诺。受限 API 容器会明显压低上沿,因此上线承诺仍以云端受限资源复测为准。
正式对外容量必须等 4 核 16G 云服务器部署后复跑。保守规划时,生产首版建议先按本地折算值的 30% 到 50% 做容量承诺,约 620 到 4,160 名活跃在线学生等云端压测、CDN、对象存储和真实前端埋点完成后再上调。 ### 2026-07-01 Docker API + DB 显式受限复核
本轮使用同一压测入口,但显式设置 `BENCHMARK_LIMIT_DB_RESOURCES=true`API 容器限制为 2 CPU/4GSupabase PostgreSQL 容器限制为 2 CPU/8G`DB_POOL_MAX=10`。PostgreSQL 参数仍是本地默认值,没有应用 `docs/refactor/postgresql-4c16g-tuning.md` 的 shared-host profile。
| 场景 | 并发 worker | 时长 | 刷题写入比例 | 请求数 | 错误率 | 吞吐 | P95 | P99 | 结论 |
| --- | ---: | ---: | ---: | ---: | ---: | ---: | ---: | ---: | --- |
| 只读上线门禁 | 30 | 120s | 0% | 8,029 | 0.00% | 66.50 req/s | 1188.62 ms | 1697.71 ms | 失败,延迟超过门禁 |
| 混合读写 | 50 | 60s | 10% | 7,088 | 0.00% | 115.82 req/s | 905.13 ms | 1211.98 ms | 失败,延迟超过观察线 |
| 混合读写 | 100 | 60s | 8% | 6,383 | 0.00% | 103.63 req/s | 1720.26 ms | 2101.02 ms | 压力区 |
| 混合读写 | 150 | 60s | 6% | 5,640 | 0.00% | 91.52 req/s | 2688.31 ms | 3284.99 ms | 压力区 |
这组结果说明:代码在更严格资源下仍保持 0 错误,但默认 PostgreSQL 参数无法支撑上线延迟门禁。正式对外容量必须等 4 核 16G 云服务器部署、PostgreSQL shared-host 调参、`pg_stat_statements` 和慢 SQL 观察完成后复跑。没有云端复测前,不再把本地 05:57 API-only 观察值当生产容量承诺只保留为乐观参考06:26 DB 受限结果则作为未调参下限参考。

View File

@@ -95,7 +95,7 @@
- 已补 `npm run launch:gate` 生产上线证据门禁和 `docs/refactor/production-launch-evidence.template.json` 模板;最终切换前必须把 readiness、远程 Auth、RLS、PostgreSQL 4c16g 严格调参证据、生产 dry-run、导入校验、`pb:import:sample` 业务抽样、真实数据 API 只读压测、真实数据混合读写压测、API/worker/Taro、运行时审计、`security:repo`、真实 `@codex-security`、备份/回滚/真实抽样/生产 provider 等证据填入本地 `production-launch-evidence.json` 并通过门禁。当前 Codex 环境未暴露可调用的 `@codex-security` 扫描工具时,该项只能标为待补,不能伪造完成。 - 已补 `npm run launch:gate` 生产上线证据门禁和 `docs/refactor/production-launch-evidence.template.json` 模板;最终切换前必须把 readiness、远程 Auth、RLS、PostgreSQL 4c16g 严格调参证据、生产 dry-run、导入校验、`pb:import:sample` 业务抽样、真实数据 API 只读压测、真实数据混合读写压测、API/worker/Taro、运行时审计、`security:repo`、真实 `@codex-security`、备份/回滚/真实抽样/生产 provider 等证据填入本地 `production-launch-evidence.json` 并通过门禁。当前 Codex 环境未暴露可调用的 `@codex-security` 扫描工具时,该项只能标为待补,不能伪造完成。
- 确认数据库迁移流程、备份恢复、日志、告警。 - 确认数据库迁移流程、备份恢复、日志、告警。
- 准备 API 容器部署和 Supabase 云端/自托管连接方案。 - 准备 API 容器部署和 Supabase 云端/自托管连接方案。
- 已补 `npm run perf:api:local``npm run perf:summary``npm run perf:postgres:evidence``npm run perf:postgres:sql``npm run smoke:launch-persona``npm run smoke:taro:h5``npm run smoke:taro:h5:interaction``node scripts/taro-api-contract-test.js``node scripts/taro-persona-contract-test.js``docs/refactor/performance-benchmark-runbook.md`可在本地或云端对真实迁移数据做只读门禁、混合读写容量观察、PostgreSQL 4c16g profile 调参证据、三类后端角色旅程烟测、三类 Taro 前端角色旅程契约、H5 发布目录启动烟测、真实浏览器关键点击烟测和前端 API 契约检查。2026-07-01 05:57 受限 API 容器真实迁移库复核中30 worker/120s 只读为 42,089 请求、0 错误、349.70 req/s、P95 202.20ms、P99 295.16ms50 worker/60s/10% 写入为 25,157 请求、0 错误、416.54 req/s、P95 215.00ms、P99 292.56ms100 worker/60s/8% 写入为 23,873 请求、0 错误、394.18 req/s、P95 414.67ms、P99 495.91ms150 worker/60s/6% 写入为 22,523 请求、0 错误、370.53 req/s、P95 621.55ms、P99 750.70ms。受限容器系列压测曾抓到自动勋章并发发放唯一键冲突,已修复并新增 `scripts/auto-badge-concurrency-test.js`。当前 Docker Desktop 给了 20 CPU/约 62.7GB 内存,但 API 容器限制为 2 CPU/4G舒适观察区暂按 50 个无停顿 worker 估算100 worker 可用但延迟偏高150 worker 已是压力区;按单学生 0.05 到 0.2 req/s 粗略折算约为 2,080 到 8,330 名活跃在线学生的本机吞吐观察区间,正式容量仍以上云 4 核 16G 复测为准。 - 已补 `npm run perf:api:local``npm run perf:summary``npm run perf:postgres:evidence``npm run perf:postgres:sql``npm run smoke:launch-persona``npm run smoke:taro:h5``npm run smoke:taro:h5:interaction``node scripts/taro-api-contract-test.js``node scripts/taro-persona-contract-test.js``docs/refactor/performance-benchmark-runbook.md`可在本地或云端对真实迁移数据做只读门禁、混合读写容量观察、PostgreSQL 4c16g profile 调参证据、三类后端角色旅程烟测、三类 Taro 前端角色旅程契约、H5 发布目录启动烟测、真实浏览器关键点击烟测和前端 API 契约检查。2026-07-01 05:57 API-only 受限容器复核中,50 worker/60s/10% 写入为 25,157 请求、0 错误、416.54 req/s、P95 215.00ms、P99 292.56ms2026-07-01 06:26 进一步显式限制 DB 容器为 2 CPU/8G 后30 worker 只读为 8,029 请求、0 错误、66.50 req/s、P95 1188.62ms50 worker/60s/10% 写入为 7,088 请求、0 错误、115.82 req/s、P95 905.13ms,均未通过上线延迟门禁。受限容器系列压测曾抓到自动勋章并发发放唯一键冲突,已修复并新增 `scripts/auto-badge-concurrency-test.js`。当前结论是:本地 API-only 结果只能作为乐观观察DB 受限未调参结果作为悲观下限;正式容量仍以上云 4 核 16G、PostgreSQL shared-host 调参、真实前端埋点和生产对象存储/CDN 复测为准。
- 当前本地 PostgreSQL evidence 仍提示 `jit=on``statement_timeout=0``idle_in_transaction_session_timeout=0``lock_timeout=0`;上云后必须按 `docs/refactor/postgresql-4c16g-tuning.md` 调整参数,执行 `PG_TUNING_PROFILE=shared-host npm run perf:postgres:evidence -- --strict --json` 并通过后,再按 6/30/50/100 阶梯并发复跑容量报告并归档到本地上线证据。 - 当前本地 PostgreSQL evidence 仍提示 `jit=on``statement_timeout=0``idle_in_transaction_session_timeout=0``lock_timeout=0`;上云后必须按 `docs/refactor/postgresql-4c16g-tuning.md` 调整参数,执行 `PG_TUNING_PROFILE=shared-host npm run perf:postgres:evidence -- --strict --json` 并通过后,再按 6/30/50/100 阶梯并发复跑容量报告并归档到本地上线证据。
- 本轮剩余功能和容量复核已经整理到 `docs/refactor/backend-open-items-and-capacity-20260701.md`。后续不要再把学生头像上传或默认排行榜当作待办;头像只保留男女预设,排行榜仅作为租户显式开启后的活动能力。 - 本轮剩余功能和容量复核已经整理到 `docs/refactor/backend-open-items-and-capacity-20260701.md`。后续不要再把学生头像上传或默认排行榜当作待办;头像只保留男女预设,排行榜仅作为租户显式开启后的活动能力。
@@ -200,7 +200,7 @@
4. 运维 4. 运维
- 后台操作审计报表。 - 后台操作审计报表。
- 定时备份、恢复演练。 - 定时备份、恢复演练。
- 性能压测、慢 SQL、索引审查当前已有 API 压测脚本、真实刷题读写闭环和 4 核 16G runbook。最新 Docker API 受限资源复核为 30 并发只读 0 错误、349.7 req/s、P95 202ms50 并发混合读写 0 错误、416.5 req/s、P95 215ms100 并发混合读写 0 错误、394.2 req/s、P95 415ms150 并发混合读写 0 错误、370.5 req/s、P95 622ms。云服务器部署后还要按同一矩阵复跑并输出正式容量报告。 - 性能压测、慢 SQL、索引审查当前已有 API 压测脚本、真实刷题读写闭环和 4 核 16G runbook。API-only 受限结果可作为乐观观察;显式限制 DB 2C/8G 且未调 PostgreSQL 参数时30 只读和 50 混合读写 0 错误但未通过延迟门禁。云服务器部署后必须先按 PostgreSQL shared-host profile 调参,再按同一矩阵复跑并输出正式容量报告。
## Taro 前端开发 TODO ## Taro 前端开发 TODO

View File

@@ -188,7 +188,7 @@ Remove-Item Env:\DB_POOL_MAX
每次运行都会额外生成 `docker-4c16g-resource-evidence-*.json/md`,记录 Docker Desktop 总 CPU/内存、API 容器实际 CPU/内存限制、DB 容器是否被限制、`DB_POOL_MAX` 和每个压测报告的摘要。该文件位于已忽略的 `docs/refactor/performance-reports/`,用于防止后续把“只限制 API 容器”的本地结果误写成完整 4 核 16G 生产容量;它不能替代目标云服务器正式压测证据。 每次运行都会额外生成 `docker-4c16g-resource-evidence-*.json/md`,记录 Docker Desktop 总 CPU/内存、API 容器实际 CPU/内存限制、DB 容器是否被限制、`DB_POOL_MAX` 和每个压测报告的摘要。该文件位于已忽略的 `docs/refactor/performance-reports/`,用于防止后续把“只限制 API 容器”的本地结果误写成完整 4 核 16G 生产容量;它不能替代目标云服务器正式压测证据。
如需要在本地显式限制 Supabase PostgreSQL 容器资源,可以打开下面开关。脚本默认会在结束后恢复 DB 容器原始限制;如果你希望保留限制用于排查,才设置 `BENCHMARK_KEEP_DB_LIMIT=true` 如需要在本地显式限制 Supabase PostgreSQL 容器资源,可以打开下面开关。脚本默认会在结束后恢复 DB 容器原始限制;如果原始容器是 unlimited而当前 Docker Desktop 无法用 `docker update --memory 0` 清掉 live memory limit脚本会恢复到 Docker Desktop 当前 CPU/内存上限附近并在资源证据中写明。只有你希望保留限制用于排查,才设置 `BENCHMARK_KEEP_DB_LIMIT=true`
```powershell ```powershell
$env:BENCHMARK_LIMIT_DB_RESOURCES="true" $env:BENCHMARK_LIMIT_DB_RESOURCES="true"

View File

@@ -134,11 +134,26 @@
按 0.05 到 0.2 req/s/人的页面节奏粗略折算50 worker 舒适吞吐约对应 2,080 到 8,330 名活跃在线学生请求吞吐100 worker 上沿约对应 1,970 到 7,880 名150 worker 压力区约对应 1,850 到 7,410 名。生产 4 核 16G 首版容量建议先按这组本地受限资源折算值的 30% 到 50% 保守规划,即约 620 到 4,160 名活跃在线学生请求吞吐,再以上云复测和真实前端埋点修正。 按 0.05 到 0.2 req/s/人的页面节奏粗略折算50 worker 舒适吞吐约对应 2,080 到 8,330 名活跃在线学生请求吞吐100 worker 上沿约对应 1,970 到 7,880 名150 worker 压力区约对应 1,850 到 7,410 名。生产 4 核 16G 首版容量建议先按这组本地受限资源折算值的 30% 到 50% 保守规划,即约 620 到 4,160 名活跃在线学生请求吞吐,再以上云复测和真实前端埋点修正。
### 2026-07-01 06:26 Docker API + DB 显式受限复核
本轮使用 `BENCHMARK_LIMIT_DB_RESOURCES=true npm run perf:api:docker-4c16g`API 容器限制为 2 CPU/4GSupabase PostgreSQL 容器限制为 2 CPU/8G`DB_POOL_MAX=10`,压测鉴权继续使用 `PERF_AUTH_MODE=app_session`。这比 05:57 只限制 API 容器更接近本地 4C16G 资源压力,但 PostgreSQL 参数仍是本地默认值,并未按 `docs/refactor/postgresql-4c16g-tuning.md` 应用 shared-host profile。
| 场景 | 并发 worker | 时长 | 刷题写入比例 | 请求数 | 错误率 | 吞吐 | P95 | P99 | 门禁结论 |
| --- | ---: | ---: | ---: | ---: | ---: | ---: | ---: | ---: | --- |
| 只读上线门禁 | 30 | 120s | 0% | 8,029 | 0.00% | 66.50 req/s | 1188.62 ms | 1697.71 ms | 失败,超过 P95/P99 |
| 混合读写 | 50 | 60s | 10% | 7,088 | 0.00% | 115.82 req/s | 905.13 ms | 1211.98 ms | 失败,超过 P95/P99 |
| 混合读写 | 100 | 60s | 8% | 6,383 | 0.00% | 103.63 req/s | 1720.26 ms | 2101.02 ms | 压力区 |
| 混合读写 | 150 | 60s | 6% | 5,640 | 0.00% | 91.52 req/s | 2688.31 ms | 3284.99 ms | 压力区 |
结论:资源更严格后系统仍保持 0 错误,但延迟不满足上线门禁。该结果不能作为生产容量承诺,反而证明云端正式部署前必须先完成 PostgreSQL shared-host 调参、`pg_stat_statements`、慢 SQL 观察和 4C16G 云服务器复测。尤其是 `learning.trend``learning.stats``learning.practice_flow.create/detail/answer` 在受限 DB 下最容易抬高 P95应在云端结合慢 SQL 和 `EXPLAIN (ANALYZE, BUFFERS)` 继续看是否需要预聚合、缓存或索引微调。
按 50 worker 混合读写 115.82 req/s 粗略折算,理论请求吞吐约对应 580 到 2,316 名活跃在线学生,但因为该轮未完成 PostgreSQL 调参且只在本地 Docker Desktop 上运行,只能作为“未调参受限资源下限观察”,不能作为首版生产规划值。首版生产规划仍以上云调参后复测为准。
## 初步结论 ## 初步结论
- 本地高资源 Docker Desktop 历史结果显示代码和索引在 50/100 worker 下有较高吞吐;但最新受限 API 容器复核更接近保守上线口径50 worker 混合读写 P95 约 215ms100 worker P95 约 415ms150 worker P95 约 622ms 已经进入压力区 - 本地高资源 Docker Desktop 历史结果显示代码和索引在 50/100 worker 下有较高吞吐;只限制 API 容器50 worker 混合读写 P95 约 215ms100 worker P95 约 415ms150 worker P95 约 622ms;进一步把 DB 容器也限制为 2 CPU/8G 后30 worker 只读和 50 worker 混合读写均未通过延迟门禁
- 最新 416.54 req/s 的 50 worker 舒适吞吐粗略折算,如果未来前端真实埋点显示每名在线学生平均 0.05 到 0.2 req/s则理论请求吞吐约对应 2,080 到 8,330 名活跃在线学生;按 100 worker 上沿 394.18 req/s 估算约为 1,970 到 7,880 名;按 150 worker 压力区 370.53 req/s 估算约为 1,850 到 7,410 名。这只是吞吐换算,不是生产 SLA - 最新 DB 显式受限结果说明:不能仅凭本地 Docker API 受限跑分给生产承诺,生产 4C16G 必须先应用 PostgreSQL shared-host 调参并启用 `pg_stat_statements`,再跑正式只读/混合读写矩阵
- 生产如果使用 4 核 16G 单机,首版容量承诺建议先按最新本地受限资源折算值的 30% 到 50% 保守规划,即约 620 到 4,160 名活跃在线学生请求吞吐再以云端复测、真实前端埋点、CDN/对象存储和生产 PostgreSQL 调参后结果上调 - 生产如果使用 4 核 16G 单机,首版容量承诺不要再直接沿用 05:57 的 620 到 4,160 人估算;应先以云端调参后复测结果为准。没有云端复测前,可以把 05:57 结果看作“API 受限但 DB 未受限的乐观观察”,把 06:26 结果看作“DB 也受限但未调参的悲观观察”
- 正式容量承诺必须在目标 4 核 16G 云服务器、生产 PostgreSQL 参数、对象存储/CDN、真实前端请求节奏和生产网络下复跑。 - 正式容量承诺必须在目标 4 核 16G 云服务器、生产 PostgreSQL 参数、对象存储/CDN、真实前端请求节奏和生产网络下复跑。
## 后续复测 ## 后续复测

View File

@@ -17,7 +17,9 @@ assert.match(script, /BENCHMARK_KEEP_DB_LIMIT/, 'benchmark script should support
assert.match(script, /dockerJson\(\['info', '--format', '\{\{json \.\}\}'\]\)/, 'benchmark script should record Docker Desktop resources'); assert.match(script, /dockerJson\(\['info', '--format', '\{\{json \.\}\}'\]\)/, 'benchmark script should record Docker Desktop resources');
assert.match(script, /inspectContainer\(composeServiceContainerId\('api'\)\)/, 'benchmark script should inspect the API container after compose starts'); assert.match(script, /inspectContainer\(composeServiceContainerId\('api'\)\)/, 'benchmark script should inspect the API container after compose starts');
assert.match(script, /inspectContainer\(dbContainerName\)/, 'benchmark script should inspect the Supabase DB container'); assert.match(script, /inspectContainer\(dbContainerName\)/, 'benchmark script should inspect the Supabase DB container');
assert.match(script, /dockerHostRestoreLimit/, 'benchmark script should restore originally-unlimited DB containers to Docker host limits');
assert.match(script, /restoreDbLimit\(dbRestore, resourceEvidence\)/, 'benchmark script should restore DB resource limits by default'); assert.match(script, /restoreDbLimit\(dbRestore, resourceEvidence\)/, 'benchmark script should restore DB resource limits by default');
assert.match(script, /restoreLimit = restore/, 'benchmark script should record the DB restore strategy in evidence');
assert.match(script, /readBenchmarkSummary\(report\)/, 'benchmark script should attach benchmark summaries to resource evidence'); assert.match(script, /readBenchmarkSummary\(report\)/, 'benchmark script should attach benchmark summaries to resource evidence');
assert.match(runbook, /docker-4c16g-resource-evidence-\*\.json\/md/, 'runbook should document the resource evidence artifact'); assert.match(runbook, /docker-4c16g-resource-evidence-\*\.json\/md/, 'runbook should document the resource evidence artifact');

View File

@@ -48,7 +48,6 @@ function runCapture(command, args, options = {}) {
return spawnSync(command, args, { return spawnSync(command, args, {
cwd: repoRoot, cwd: repoRoot,
encoding: 'utf8', encoding: 'utf8',
shell: process.platform === 'win32',
env: baseEnv(options.env), env: baseEnv(options.env),
}); });
} }
@@ -118,6 +117,20 @@ function dockerInfoSummary() {
}; };
} }
function dockerHostRestoreLimit() {
const info = dockerInfoSummary();
const ncpu = Number(info.ncpu || 0);
const memoryBytes = Number(info.memoryBytes || 0);
return {
cpus: ncpu > 0 ? String(ncpu) : '0',
memory: memoryBytes > 0 ? String(Math.floor(memoryBytes * 0.98)) : '0',
memorySwap: memoryBytes > 0 ? String(Math.floor(memoryBytes * 0.98)) : '-1',
note: ncpu > 0 && memoryBytes > 0
? 'Original DB container was unlimited; Docker Desktop may not clear live memory limits with docker update, so restore uses the Docker Desktop host limit.'
: 'Original DB container was unlimited but Docker host resources were unavailable; attempted docker update unlimited restore.',
};
}
function resourceSummaryFromHostConfig(hostConfig = {}) { function resourceSummaryFromHostConfig(hostConfig = {}) {
const nanoCpus = Number(hostConfig.NanoCpus || 0); const nanoCpus = Number(hostConfig.NanoCpus || 0);
const memoryBytes = Number(hostConfig.Memory || 0); const memoryBytes = Number(hostConfig.Memory || 0);
@@ -191,11 +204,14 @@ function maybeApplyDbLimit(resourceEvidence) {
} }
const beforeRaw = resourceEvidence.database.before.resources.raw; const beforeRaw = resourceEvidence.database.before.resources.raw;
const restore = { const restore = beforeRaw.nanoCpus > 0 || beforeRaw.memory > 0
cpus: beforeRaw.nanoCpus > 0 ? String(beforeRaw.nanoCpus / 1_000_000_000) : '0', ? {
memory: String(beforeRaw.memory || 0), cpus: beforeRaw.nanoCpus > 0 ? String(beforeRaw.nanoCpus / 1_000_000_000) : '0',
memorySwap: String(beforeRaw.memorySwap || 0), memory: String(beforeRaw.memory || 0),
}; memorySwap: String(beforeRaw.memorySwap || 0),
note: 'Restoring DB container resource limits captured before benchmark.',
}
: dockerHostRestoreLimit();
try { try {
updateContainerResources(dbContainerName, { updateContainerResources(dbContainerName, {
@@ -219,6 +235,7 @@ function restoreDbLimit(restore, resourceEvidence) {
try { try {
updateContainerResources(dbContainerName, restore); updateContainerResources(dbContainerName, restore);
resourceEvidence.database.restored = true; resourceEvidence.database.restored = true;
resourceEvidence.database.restoreLimit = restore;
resourceEvidence.database.afterRestore = inspectContainer(dbContainerName); resourceEvidence.database.afterRestore = inspectContainer(dbContainerName);
} catch (error) { } catch (error) {
resourceEvidence.database.restored = false; resourceEvidence.database.restored = false;
@@ -381,6 +398,7 @@ function resourceEvidenceMarkdown(evidence) {
if (evidence.database?.afterRestore) { if (evidence.database?.afterRestore) {
lines.push(`- DB 恢复后:${evidence.database.afterRestore.resources.cpus ?? 'unlimited'} CPU / ${evidence.database.afterRestore.resources.memory}`); lines.push(`- DB 恢复后:${evidence.database.afterRestore.resources.cpus ?? 'unlimited'} CPU / ${evidence.database.afterRestore.resources.memory}`);
} }
if (evidence.database?.restoreLimit?.note) lines.push(`- DB 恢复策略:${evidence.database.restoreLimit.note}`);
if (evidence.database?.note) lines.push(`- 说明:${evidence.database.note}`); if (evidence.database?.note) lines.push(`- 说明:${evidence.database.note}`);
if (evidence.database?.limitError) lines.push(`- DB 限制错误:${evidence.database.limitError}`); if (evidence.database?.limitError) lines.push(`- DB 限制错误:${evidence.database.limitError}`);
if (evidence.database?.restoreError) lines.push(`- DB 恢复错误:${evidence.database.restoreError}`); if (evidence.database?.restoreError) lines.push(`- DB 恢复错误:${evidence.database.restoreError}`);