diff --git a/README.md b/README.md index 4652dcf7..2f7257ce 100644 --- a/README.md +++ b/README.md @@ -654,20 +654,20 @@ node scripts\taro-h5-release-guardrails-test.js --require-dist --require-runtime | 50 | 60s | 10% | 35,608 | 0.00% | 584.18 req/s | 174.26 ms | 224.04 ms | | 100 | 60s | 8% | 33,843 | 0.00% | 554.42 req/s | 326.21 ms | 422.33 ms | | 150 | 60s | 6% | 32,364 | 0.00% | 528.81 req/s | 490.38 ms | 662.42 ms | -| Docker API 2c4g / 30 | 120s | 0% | 44,692 | 0.00% | 371.62 req/s | 188.91 ms | 275.79 ms | -| Docker API 2c4g / 50 | 60s | 10% | 26,815 | 0.00% | 444.49 req/s | 204.08 ms | 268.88 ms | -| Docker API 2c4g / 100 | 60s | 8% | 25,950 | 0.00% | 428.30 req/s | 375.38 ms | 447.47 ms | -| Docker API 2c4g / 150 | 60s | 6% | 23,970 | 0.00% | 394.80 req/s | 574.37 ms | 696.35 ms | +| Docker API 2c4g / 30 | 120s | 0% | 42,982 | 0.00% | 357.28 req/s | 192.25 ms | 287.93 ms | +| Docker API 2c4g / 50 | 60s | 10% | 26,086 | 0.00% | 431.78 req/s | 207.10 ms | 283.57 ms | +| Docker API 2c4g / 100 | 60s | 8% | 25,242 | 0.00% | 417.03 req/s | 383.30 ms | 464.22 ms | +| Docker API 2c4g / 150 | 60s | 6% | 24,201 | 0.00% | 398.22 req/s | 567.89 ms | 706.09 ms | 只读上线门禁继续要求 `includeWrites=false`;混合读写报告需要显式使用 `--allow-writes` 做人工容量观察,例如: ```powershell -npm run perf:summary -- --input docs/refactor/performance-reports/api-benchmark-20260701-002524.json --json --allow-writes --min-duration-seconds=60 --min-concurrency=50 --max-p95-ms=500 --max-p99-ms=1200 +npm run perf:summary -- --input docs/refactor/performance-reports/api-benchmark-20260701-050648.json --json --allow-writes --min-duration-seconds=60 --min-concurrency=50 --max-p95-ms=500 --max-p99-ms=1200 ``` 使用 `--allow-writes` 时会输出 `capacityObservation`,不输出 `launchGateCheck`,不能把写入场景误填成生产上线门禁的只读证据。 -本地结论:当前 Docker Desktop 分配 20 CPU、约 62.7GB 内存,高于常见 4 核 16G 云服务器,不能直接作为生产 SLA。2026-07-01 04:44 受限 API 容器复核中,30 worker/120 秒只读上线门禁为 44,692 请求、0 错误、371.62 req/s、P95 188.91ms;50 worker/60 秒/10% 写入为 26,815 请求、0 错误、444.49 req/s、P95 204.08ms;100 worker/60 秒/8% 写入为 25,950 请求、0 错误、428.30 req/s、P95 375.38ms;150 worker/60 秒/6% 写入为 23,970 请求、0 错误、394.80 req/s、P95 574.37ms,属于压力区。该受限容器系列压测曾抓到自动勋章并发发放唯一键冲突,已修复并新增并发回归测试。按单学生 0.05 到 0.2 req/s 的页面节奏粗略折算,当前受限 API 容器舒适观察区间约对应 2,100 到 8,900 名活跃在线学生的请求吞吐。正式对外容量承诺必须在目标 4 核 16G 云服务器、生产 PostgreSQL 参数、生产对象存储/CDN 和真实前端请求节奏下复跑。脱敏摘要和剩余功能清单见: +本地结论:当前 Docker Desktop 分配 20 CPU、约 62.7GB 内存,高于常见 4 核 16G 云服务器,不能直接作为生产 SLA。2026-07-01 05:05 受限 API 容器复核中,30 worker/120 秒只读上线门禁为 42,982 请求、0 错误、357.28 req/s、P95 192.25ms;50 worker/60 秒/10% 写入为 26,086 请求、0 错误、431.78 req/s、P95 207.10ms;100 worker/60 秒/8% 写入为 25,242 请求、0 错误、417.03 req/s、P95 383.30ms;150 worker/60 秒/6% 写入为 24,201 请求、0 错误、398.22 req/s、P95 567.89ms,属于压力区。该受限容器系列压测曾抓到自动勋章并发发放唯一键冲突,已修复并新增并发回归测试。按单学生 0.05 到 0.2 req/s 的页面节奏粗略折算,当前受限 API 容器舒适观察区间约对应 2,100 到 8,600 名活跃在线学生的请求吞吐;保守按生产首版 30% 到 50% 预留容量时,可先规划约 630 到 4,300 名活跃在线学生,等云端 4 核 16G 复测和真实前端埋点后再上调。正式对外容量承诺必须在目标 4 核 16G 云服务器、生产 PostgreSQL 参数、生产对象存储/CDN 和真实前端请求节奏下复跑。脱敏摘要和剩余功能清单见: ```text docs/refactor/performance-benchmark-summary-20260630.md diff --git a/docs/refactor/backend-open-items-and-capacity-20260701.md b/docs/refactor/backend-open-items-and-capacity-20260701.md index c1595930..7d1225e2 100644 --- a/docs/refactor/backend-open-items-and-capacity-20260701.md +++ b/docs/refactor/backend-open-items-and-capacity-20260701.md @@ -43,7 +43,7 @@ - 环境:Windows + Docker Desktop + 本地 Supabase/PostgreSQL + 本地 API 进程。 - Docker Desktop 当前资源:20 CPU、约 62.7GB 内存。这个本机结果会高于常见 4 核 16G 云服务器,不能直接作为生产 SLA。 - 数据库:PocketBase 真实导入数据,并已包含前期写入压测产生的练习/答题记录。 -- 当前本地迁移库规模:约 7.4 万道题、3,700 个用户、1,600 个题目合集、3,100 个练习蓝图、3,500 个单词、2,600 条知识手册、25.2 万条答题记录、3.8 万条错题、468 条权益。后续 `db:smoke-seed`、集成测试和压测会继续写入练习/答题/测试租户数据,因此这里按当前本地观察描述,不把每次测试后的瞬时行数当作生产 SLA 口径。 +- 当前本地迁移库规模:13 个租户、约 7.4 万道题、3,710 个用户、1,619 个题目合集、3,114 个练习蓝图、3,513 个单词、2,686 条知识手册条目、6.0 万练习 session、26.4 万条答题记录、3.8 万条错题、1,477 个内容资源、468 条权益。后续 `db:smoke-seed`、集成测试和压测会继续写入练习/答题/测试租户数据,因此这里按当前本地观察描述,不把每次测试后的瞬时行数当作生产 SLA 口径。 - 排行榜未纳入默认负载,因为产品默认关闭。 ### 2026-07-01 真实迁移库 Docker API 受限资源复核 @@ -54,10 +54,10 @@ | 场景 | 并发 worker | 时长 | 刷题写入比例 | 请求数 | 错误率 | 吞吐 | P95 | P99 | 结论 | | --- | ---: | ---: | ---: | ---: | ---: | ---: | ---: | ---: | --- | -| 只读上线门禁 | 30 | 120s | 0% | 44,692 | 0.00% | 371.62 req/s | 188.91 ms | 275.79 ms | 通过 | -| 混合读写 | 50 | 60s | 10% | 26,815 | 0.00% | 444.49 req/s | 204.08 ms | 268.88 ms | 舒适 | -| 混合读写 | 100 | 60s | 8% | 25,950 | 0.00% | 428.30 req/s | 375.38 ms | 447.47 ms | 接近舒适区上沿 | -| 混合读写 | 150 | 60s | 6% | 23,970 | 0.00% | 394.80 req/s | 574.37 ms | 696.35 ms | 压力区 | +| 只读上线门禁 | 30 | 120s | 0% | 42,982 | 0.00% | 357.28 req/s | 192.25 ms | 287.93 ms | 通过 | +| 混合读写 | 50 | 60s | 10% | 26,086 | 0.00% | 431.78 req/s | 207.10 ms | 283.57 ms | 舒适 | +| 混合读写 | 100 | 60s | 8% | 25,242 | 0.00% | 417.03 req/s | 383.30 ms | 464.22 ms | 接近舒适区上沿 | +| 混合读写 | 150 | 60s | 6% | 24,201 | 0.00% | 398.22 req/s | 567.89 ms | 706.09 ms | 压力区 | 混合读写包含真实刷题闭环:创建练习 session、拉取 session detail、提交答案、交卷、读取报告。 @@ -69,10 +69,10 @@ 按目前常见页面节奏先用 `0.05 到 0.2 req/s/人` 粗略折算: -- 50 worker 舒适场景 444 req/s,约等于 2,220 到 8,880 名活跃在线学生的请求吞吐。 -- 100 worker 上沿场景 428 req/s,约等于 2,140 到 8,560 名活跃在线学生的请求吞吐。 -- 150 worker 压力场景 395 req/s,约等于 1,975 到 7,900 名活跃在线学生的请求吞吐。 +- 50 worker 舒适场景 432 req/s,约等于 2,160 到 8,640 名活跃在线学生的请求吞吐。 +- 100 worker 上沿场景 417 req/s,约等于 2,085 到 8,340 名活跃在线学生的请求吞吐。 +- 150 worker 压力场景 398 req/s,约等于 1,990 到 7,960 名活跃在线学生的请求吞吐。 -当前本地结论:在受限 API 容器下,后端真实刷题读写链路的舒适观察区间约为 428 到 444 req/s,折算约 2,100 到 8,900 名活跃在线学生;150 worker 仍 0 错误但 P95 已接近 600ms,视为压力区,不建议作为生产承诺。同日本地 API 进程 30 worker/30 秒/10% 写入观察到 15,864 请求、0 错误、513.56 req/s、P95 113.97ms;受限 API 容器会明显压低上沿,因此上线承诺仍以云端受限资源复测为准。 +当前本地结论:在受限 API 容器下,后端真实刷题读写链路的舒适观察区间约为 417 到 432 req/s,折算约 2,100 到 8,600 名活跃在线学生;150 worker 仍 0 错误但 P95 已接近 600ms,视为压力区,不建议作为生产承诺。同日本地 API 进程 30 worker/30 秒/10% 写入观察到 15,864 请求、0 错误、513.56 req/s、P95 113.97ms;受限 API 容器会明显压低上沿,因此上线承诺仍以云端受限资源复测为准。 -正式对外容量必须等 4 核 16G 云服务器部署后复跑。保守规划时,生产首版建议先按本地折算值的 30% 到 50% 做容量承诺,等云端压测、CDN、对象存储和真实前端埋点完成后再上调。 +正式对外容量必须等 4 核 16G 云服务器部署后复跑。保守规划时,生产首版建议先按本地折算值的 30% 到 50% 做容量承诺,约 630 到 4,300 名活跃在线学生,等云端压测、CDN、对象存储和真实前端埋点完成后再上调。 diff --git a/docs/refactor/next-development-todo.md b/docs/refactor/next-development-todo.md index 04008e0d..b3cd37d4 100644 --- a/docs/refactor/next-development-todo.md +++ b/docs/refactor/next-development-todo.md @@ -94,7 +94,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` 扫描工具时,该项只能标为待补,不能伪造完成。 - 确认数据库迁移流程、备份恢复、日志、告警。 - 准备 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 04:44 受限 API 容器真实迁移库复核中,30 worker/120s 只读为 44,692 请求、0 错误、371.62 req/s、P95 188.91ms、P99 275.79ms;50 worker/60s/10% 写入为 26,815 请求、0 错误、444.49 req/s、P95 204.08ms、P99 268.88ms;100 worker/60s/8% 写入为 25,950 请求、0 错误、428.30 req/s、P95 375.38ms、P99 447.47ms;150 worker/60s/6% 写入为 23,970 请求、0 错误、394.80 req/s、P95 574.37ms、P99 696.35ms。受限容器系列压测曾抓到自动勋章并发发放唯一键冲突,已修复并新增 `scripts/auto-badge-concurrency-test.js`。当前 Docker Desktop 给了 20 CPU/约 62.7GB 内存,但 API 容器限制为 2 CPU/4G,舒适观察区暂按 50 到 100 个无停顿 worker 估算,150 worker 已是压力区;按单学生 0.05 到 0.2 req/s 粗略折算约为 2,100 到 8,900 名活跃在线学生的本机吞吐观察区间,正式容量仍以上云 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:05 受限 API 容器真实迁移库复核中,30 worker/120s 只读为 42,982 请求、0 错误、357.28 req/s、P95 192.25ms、P99 287.93ms;50 worker/60s/10% 写入为 26,086 请求、0 错误、431.78 req/s、P95 207.10ms、P99 283.57ms;100 worker/60s/8% 写入为 25,242 请求、0 错误、417.03 req/s、P95 383.30ms、P99 464.22ms;150 worker/60s/6% 写入为 24,201 请求、0 错误、398.22 req/s、P95 567.89ms、P99 706.09ms。受限容器系列压测曾抓到自动勋章并发发放唯一键冲突,已修复并新增 `scripts/auto-badge-concurrency-test.js`。当前 Docker Desktop 给了 20 CPU/约 62.7GB 内存,但 API 容器限制为 2 CPU/4G,舒适观察区暂按 50 到 100 个无停顿 worker 估算,150 worker 已是压力区;按单学生 0.05 到 0.2 req/s 粗略折算约为 2,100 到 8,600 名活跃在线学生的本机吞吐观察区间,正式容量仍以上云 4 核 16G 复测为准。 - 当前本地 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`。后续不要再把学生头像上传或默认排行榜当作待办;头像只保留男女预设,排行榜仅作为租户显式开启后的活动能力。 diff --git a/docs/refactor/performance-benchmark-runbook.md b/docs/refactor/performance-benchmark-runbook.md index a40b3c8c..f0970590 100644 --- a/docs/refactor/performance-benchmark-runbook.md +++ b/docs/refactor/performance-benchmark-runbook.md @@ -238,7 +238,7 @@ npm run perf:api:local npm run launch:gate ``` -`launch:gate` 会强制检查一条真实数据读路径压测证据: +`launch:gate` 会强制检查真实数据只读压测和混合读写压测两条证据。只读证据用于基础上线门禁: ```json { @@ -285,6 +285,37 @@ npm run perf:summary -- --input docs/refactor/performance-reports/api-benchmark- 使用 `--allow-writes` 时,摘要工具输出 `capacityObservation`,不输出 `launchGateCheck`;不要把写入场景误填进生产上线门禁的 `performance.api-real-data-read`。 -更高的 50/100 并发、写入混合场景和容量结论仍应作为人工容量报告归档;门禁只负责挡住明显不达标的基础读路径。 +混合读写证据用于确认“创建练习 session -> 拉题 -> 答题 -> 交卷 -> 报告”的真实写入链路在上线容量基线内: + +```json +{ + "id": "performance.api-real-data-mixed", + "status": "pass", + "command": "PERF_START_SERVER=false PERF_API_BASE=https://api.example.com PERF_DURATION_SECONDS=120 PERF_CONCURRENCY=50 PERF_RAMP_SECONDS=15 PERF_INCLUDE_WRITES=true PERF_PRACTICE_FLOW_RATIO=0.1 PERF_AUTH_MODE=app_session npm run perf:api:local > docs/refactor/launch-artifacts/api-real-data-mixed-benchmark.log", + "completedAt": "2026-06-30T10:49:00+08:00", + "artifact": "launch-artifacts/api-real-data-mixed-benchmark.log", + "summary": { + "errors": 0, + "errorRate": 0, + "p95Ms": 0, + "p99Ms": 0, + "concurrency": 50, + "durationSeconds": 120, + "includeWrites": true + } +} +``` + +混合读写上线门槛: + +- `errors = 0` +- `errorRate <= 0.001` +- `p95Ms <= 500` +- `p99Ms <= 1200` +- `concurrency >= 50` +- `durationSeconds >= 60` +- `includeWrites = true` + +更高的 100/150 并发和更长时间压测仍应作为人工容量报告归档;门禁负责挡住基础读路径和刷题写入闭环明显不达标的情况。 证据中只记录报告路径、并发矩阵、P95/P99、错误率和结论,不保存真实 token、支付密钥、用户隐私或完整响应。 diff --git a/docs/refactor/postgresql-4c16g-tuning.md b/docs/refactor/postgresql-4c16g-tuning.md index af1bb9da..238feba0 100644 --- a/docs/refactor/postgresql-4c16g-tuning.md +++ b/docs/refactor/postgresql-4c16g-tuning.md @@ -12,7 +12,7 @@ - PostgreSQL 官方 WAL/Checkpoint:https://www.postgresql.org/docs/current/runtime-config-wal.html - PostgreSQL 官方 Connections:https://www.postgresql.org/docs/current/runtime-config-connection.html -说明:postgresqlco.nf 的页面可作为参数分类和调参入口参考;具体参数语义、重启要求和风险以 PostgreSQL 官方文档为准。当前 Codex 环境访问 `https://postgresqlco.nf/tuning-guide` 会返回 403,因此落地值不直接抓取该站页面,而是把它作为导航来源,并以 PostgreSQL 官方文档和本项目真实压测结果共同校验。 +说明:postgresqlco.nf 的 tuning guide 适合作为 `postgresql.conf` 参数分类和调参入口参考;具体参数语义、重启要求、风险边界和版本差异仍以 PostgreSQL 官方文档为准。当前 Codex 环境直接访问 `https://postgresqlco.nf/tuning-guide` 会返回 403,因此落地值不直接抓取该站页面,而是把它作为导航来源,并以 PostgreSQL 官方文档、本项目真实压测结果和上线可观测性共同校验。 ## 适用前提 @@ -45,6 +45,12 @@ | `idle_in_transaction_session_timeout` | `60s` | `60s` | 防止后台或脚本长事务占锁 | | `statement_timeout` | `30s` | `30s` | API 请求不应长期占用数据库;导入脚本用会话级覆盖 | | `lock_timeout` | `5s` | `5s` | 防止普通请求长时间等锁 | +| `temp_file_limit` | `4GB` | `8GB` | 给报表/导入留出空间,同时避免异常 SQL 无限落临时文件 | +| `log_temp_files` | `128MB` | `128MB` | 记录大临时文件,定位排序/哈希溢出和缺索引问题 | +| `log_checkpoints` | `on` | `on` | 记录 checkpoint,便于把延迟波动和 WAL/checkpoint 压力关联起来 | +| `track_io_timing` | `on` | `on` | 压测和灰度期定位读写 I/O 耗时 | +| `track_wal_io_timing` | `on` | `on` | 定位答题、订单、导入等写入场景的 WAL I/O 压力 | +| `max_parallel_workers_per_gather` | `1` | `2` | 4 核 OLTP/API 场景避免单个查询吃掉过多并行 worker | ## 证据采集脚本 @@ -75,6 +81,8 @@ PG_TUNING_PROFILE=shared-host npm run perf:postgres:evidence -- --strict --json - `jit=off`。 - `statement_timeout`、`idle_in_transaction_session_timeout`、`lock_timeout` 不得为 0。 +- `track_io_timing=on`、`track_wal_io_timing=on`、`log_checkpoints=on`。 +- `temp_file_limit`、`log_temp_files` 和 `max_parallel_workers_per_gather` 必须落在 profile 范围内。 - 所有 profile 参数必须在允许范围内。 - `pending_restart` 必须为 0。 - `pg_stat_statements` 必须可用。 @@ -122,7 +130,13 @@ where name in ( 'log_min_duration_statement', 'idle_in_transaction_session_timeout', 'statement_timeout', - 'lock_timeout' + 'lock_timeout', + 'temp_file_limit', + 'log_temp_files', + 'log_checkpoints', + 'track_io_timing', + 'track_wal_io_timing', + 'max_parallel_workers_per_gather' ) order by name; ``` @@ -154,10 +168,16 @@ alter system set log_min_duration_statement = '500ms'; alter system set idle_in_transaction_session_timeout = '60s'; alter system set statement_timeout = '30s'; alter system set lock_timeout = '5s'; +alter system set temp_file_limit = '4GB'; +alter system set log_temp_files = '128MB'; +alter system set log_checkpoints = 'on'; +alter system set track_io_timing = 'on'; +alter system set track_wal_io_timing = 'on'; +alter system set max_parallel_workers_per_gather = '1'; select pg_reload_conf(); ``` -以下参数需要重启 PostgreSQL 才会生效:`max_connections`、`shared_buffers`、`wal_buffers`。执行后用下面语句确认: +以下参数需要重启 PostgreSQL 才会生效:`max_connections`、`shared_buffers`、`wal_buffers`。`track_io_timing`、`track_wal_io_timing`、`log_checkpoints`、`log_temp_files`、`temp_file_limit`、`max_parallel_workers_per_gather` 通常可 reload,但仍以后续 `pending_restart` 检查为准。执行后用下面语句确认: ```sql select name, setting, unit, pending_restart @@ -278,6 +298,12 @@ alter system reset log_min_duration_statement; alter system reset idle_in_transaction_session_timeout; alter system reset statement_timeout; alter system reset lock_timeout; +alter system reset temp_file_limit; +alter system reset log_temp_files; +alter system reset log_checkpoints; +alter system reset track_io_timing; +alter system reset track_wal_io_timing; +alter system reset max_parallel_workers_per_gather; select pg_reload_conf(); ``` diff --git a/scripts/lib/postgres-tuning-profile.js b/scripts/lib/postgres-tuning-profile.js index 92439c1e..458a9303 100644 --- a/scripts/lib/postgres-tuning-profile.js +++ b/scripts/lib/postgres-tuning-profile.js @@ -131,6 +131,45 @@ export const postgresTuningProfiles = { type: 'duration', rationale: 'Ordinary API requests should not wait a long time behind locks.', }, + temp_file_limit: { + recommended: '4GB', + min: '1GB', + max: '8GB', + type: 'bytes', + rationale: 'Bound accidental large sorts/hash spills from reports or imports on a shared host.', + }, + log_temp_files: { + recommended: '128MB', + min: '64MB', + max: '512MB', + type: 'bytes', + rationale: 'Log large temp-file spills during launch without logging every tiny sort.', + }, + log_checkpoints: { + recommended: 'on', + exact: 'on', + type: 'text', + rationale: 'Checkpoint logs help correlate latency spikes with WAL/checkpoint pressure.', + }, + track_io_timing: { + recommended: 'on', + exact: 'on', + type: 'text', + rationale: 'Required to diagnose read/write I/O time for real-data benchmark bottlenecks.', + }, + track_wal_io_timing: { + recommended: 'on', + exact: 'on', + type: 'text', + rationale: 'Required to diagnose WAL fsync/write pressure during answer/order/import writes.', + }, + max_parallel_workers_per_gather: { + recommended: '1', + min: 0, + max: 2, + type: 'number', + rationale: 'Keep OLTP API requests from consuming too many workers per query on 4 vCPU.', + }, }, }, 'dedicated-db': { @@ -156,6 +195,12 @@ export const postgresTuningProfiles = { idle_in_transaction_session_timeout: { recommended: '60s', min: '30s', max: '120s', type: 'duration' }, statement_timeout: { recommended: '30s', min: '5s', max: '60s', type: 'duration' }, lock_timeout: { recommended: '5s', min: '1s', max: '10s', type: 'duration' }, + temp_file_limit: { recommended: '8GB', min: '2GB', max: '12GB', type: 'bytes' }, + log_temp_files: { recommended: '128MB', min: '64MB', max: '512MB', type: 'bytes' }, + log_checkpoints: { recommended: 'on', exact: 'on', type: 'text' }, + track_io_timing: { recommended: 'on', exact: 'on', type: 'text' }, + track_wal_io_timing: { recommended: 'on', exact: 'on', type: 'text' }, + max_parallel_workers_per_gather: { recommended: '2', min: 0, max: 2, type: 'number' }, }, }, }; diff --git a/scripts/postgres-tuning-evidence-test.js b/scripts/postgres-tuning-evidence-test.js index 841e82f0..55c01b80 100644 --- a/scripts/postgres-tuning-evidence-test.js +++ b/scripts/postgres-tuning-evidence-test.js @@ -25,11 +25,15 @@ assert.equal(sharedSql.status, 0, sharedSql.stderr); assert.match(sharedSql.stdout, /alter system set shared_buffers = '3GB';/); assert.match(sharedSql.stdout, /alter system set statement_timeout = '30s';/); assert.match(sharedSql.stdout, /alter system set jit = 'off';/); +assert.match(sharedSql.stdout, /alter system set track_io_timing = 'on';/); +assert.match(sharedSql.stdout, /alter system set track_wal_io_timing = 'on';/); +assert.match(sharedSql.stdout, /alter system set temp_file_limit = '4GB';/); const dedicatedSql = run(['--print-sql', '--profile=dedicated-db']); assert.equal(dedicatedSql.status, 0, dedicatedSql.stderr); assert.match(dedicatedSql.stdout, /alter system set shared_buffers = '4GB';/); assert.match(dedicatedSql.stdout, /alter system set max_wal_size = '8GB';/); +assert.match(dedicatedSql.stdout, /alter system set temp_file_limit = '8GB';/); const invalidProfile = run(['--print-sql', '--profile=unknown']); assert.notEqual(invalidProfile.status, 0, 'unknown profile should fail'); diff --git a/scripts/postgres-tuning-evidence.js b/scripts/postgres-tuning-evidence.js index 7d35d401..6e2d966b 100644 --- a/scripts/postgres-tuning-evidence.js +++ b/scripts/postgres-tuning-evidence.js @@ -217,6 +217,9 @@ function evaluateProfile(settingsRows, profile) { if (actual !== String(rule.exact).toLowerCase()) { failures.push(`${name} should be ${rule.exact} for ${profile.label}, got ${setting.setting}.`); } + if (String(setting.source || '').toLowerCase() === 'default') { + warnings.push(`${name} still comes from default; recommended ${profile.name} value is ${rule.recommended}.`); + } continue; } if (rule.min !== undefined) {