Files
gongxue-base/docs/refactor/postgresql-4c16g-tuning.md

332 lines
16 KiB
Markdown
Raw 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.

# PostgreSQL 4 核 16G 生产调参基线
更新时间2026-07-01
这份文档用于后续把 Supabase/PostgreSQL 自托管到 4 核 16G 云服务器时做生产起步配置。目标是先给题库 SaaS 一个安全、可回滚、可观测的基线,而不是追求一次性压满硬件。
参考来源:
- postgresqlco.nf Tuning Guidehttps://postgresqlco.nf/tuning-guide
- PostgreSQL 官方 Resource Consumptionhttps://www.postgresql.org/docs/current/runtime-config-resource.html
- PostgreSQL 官方 Query Planninghttps://www.postgresql.org/docs/current/runtime-config-query.html
- PostgreSQL 官方 WAL/Checkpointhttps://www.postgresql.org/docs/current/runtime-config-wal.html
- PostgreSQL 官方 Connectionshttps://www.postgresql.org/docs/current/runtime-config-connection.html
说明postgresqlco.nf 的 tuning guide 适合作为 `postgresql.conf` 参数分类和调参入口参考;具体参数语义、重启要求、风险边界和版本差异仍以 PostgreSQL 官方文档为准。如果当前网络环境无法直接打开 `https://postgresqlco.nf/tuning-guide`,不要用第三方转载内容直接替代生产参数,应继续以 PostgreSQL 官方文档、本项目真实压测结果和上线可观测性共同校验。
## 适用前提
- 单台 4 vCPU、16 GB RAM 云服务器。
- 同机可能运行 Supabase 服务、API、worker、Nginx、日志/监控 agent。
- 数据库以题库读多写少、后台批量导入、支付/订单小事务、学习记录持续写入为主。
- 生产必须使用 SSD 云盘,数据库数据盘和备份盘分开更好。
如果 PostgreSQL 是唯一重负载服务,可以使用下方 `dedicated-db` 值;如果 API/worker/Nginx 也在同机,先使用 `shared-host` 值。
## 2026-07-01 调参复核结论
本轮按 postgresqlco.nf 的参数导航重新对照 PostgreSQL 官方文档后,保留当前 `shared-host` profile不把 4 核 16G 机器简单按“数据库独占”调满。原因:
- `shared_buffers`:官方文档说明专用数据库服务器可从约 25% 内存起步,超过 40% 通常不一定更好,因为 PostgreSQL 仍依赖操作系统缓存。同机还要运行 Supabase/API/worker/Nginx因此 `shared-host=3GB` 比 4GB 更保守;独立数据库主机才使用 `dedicated-db=4GB`
- `effective_cache_size`:它只是 planner 对共享缓冲和 OS cache 的估算,不会真实预留内存。`shared-host=10GB` 用来引导 SSD 上合理使用索引,但上线后必须结合 `EXPLAIN (ANALYZE, BUFFERS)` 与慢 SQL 复核。
- `work_mem`:官方文档强调它是每个排序/哈希操作的基准,不是每个连接的总内存。后台报表、导入和复杂查询可能一次使用多份 `work_mem`,所以 `16MB` 是上线起步值,不建议在未观察 temp file spill 和 OOM 风险前直接放大。
- `maintenance_work_mem` / `autovacuum_work_mem`:导入、建索引和 vacuum 会受益,但 autovacuum 多 worker 并发时可能叠加占用内存,因此保留 `512MB/256MB`
- `random_page_cost=1.1`SSD 云盘下可以鼓励索引扫描,但官方文档也提示 planner cost 没有统一理想值,不能只凭单个查询实验盲调。上线后以真实慢 SQL、缓存命中和查询计划为准。
- `max_connections=80`4 vCPU 不靠大量直连抗并发,优先控制 API/worker 连接池和 Supabase pooler。连接数放大只会把数据库推向上下文切换、锁等待和内存压力。
本地 DB 受限压测已经证明:只限制容器 CPU/内存但不调 PostgreSQL 参数时,系统可以保持 0 错误,但 P95 延迟不满足上线门禁。因此生产验收必须按“调参证据 + API 阶梯压测 + 慢 SQL/pg_stat_statements”闭环完成而不是只填一组参数。
## 推荐起步值
| 参数 | shared-host 起步值 | dedicated-db 起步值 | 说明 |
| --- | --- | --- | --- |
| `max_connections` | `80` | `120` | 4 核机器不宜靠大量直连抗并发API 侧连接池和 Supabase pooler 更重要 |
| `shared_buffers` | `3GB` | `4GB` | 官方建议专用库可从约 25% RAM 起步;同机多服务要给 OS cache 和应用留空间 |
| `effective_cache_size` | `10GB` | `12GB` | 这是优化器估算值,不实际占内存 |
| `work_mem` | `16MB` | `16MB` | 每个排序/哈希操作都可能分配一次,不按连接数简单相乘 |
| `maintenance_work_mem` | `512MB` | `768MB` | 导入、建索引、VACUUM 可受益;注意 autovacuum worker 并发 |
| `autovacuum_work_mem` | `256MB` | `256MB` | 避免多个 autovacuum 同时吃掉过多内存 |
| `wal_buffers` | `16MB` | `16MB` | 默认自动通常够用16MB 是常见稳妥上限 |
| `min_wal_size` | `1GB` | `2GB` | 给批量导入和写入波峰预留 WAL |
| `max_wal_size` | `6GB` | `8GB` | 减少频繁 checkpoint值越大崩溃恢复时间可能更长 |
| `checkpoint_timeout` | `10min` | `15min` | 降低 checkpoint 频率;配合 `max_wal_size` 观察恢复窗口 |
| `checkpoint_completion_target` | `0.9` | `0.9` | 平滑 checkpoint I/O |
| `effective_io_concurrency` | `100` | `100` | SSD 云盘起步值;机械盘不适用 |
| `random_page_cost` | `1.1` | `1.1` | SSD 上鼓励合理索引扫描;上线后用 EXPLAIN 复核 |
| `jit` | `off` | `off` | 题库 API 多为短查询,先避免 JIT 带来的计划开销 |
| `log_min_duration_statement` | `500ms` | `500ms` | 上线初期捕捉慢 SQL稳定后可调到 `1000ms` |
| `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 |
## 证据采集脚本
仓库已补上线前证据采集脚本:
```bash
npm run perf:postgres:evidence
```
脚本会输出 JSON/Markdown 到已忽略的 `docs/refactor/launch-artifacts/`,包含:
- `pg_settings` 中关键调参项、来源和 `pending_restart`
- `pg_stat_activity` 连接状态、锁等待和 I/O 等待聚合。
- `pg_stat_database` 事务、回滚和缓存命中率。
- `pg_stat_bgwriter` checkpoint 计数和耗时。
- public schema 大表估算行数、总大小和索引大小。
- 如已启用 `pg_stat_statements`,输出按总执行耗时排序的 Top SQL。
上云后建议顺序是:先采集一次默认值,应用本文件 shared-host 参数并重启需要重启的项,再采集一次,然后跑 API 阶梯压测。调参证据和压测报告一起进入本地 `production-launch-evidence.json`,不要提交真实证据文件。
生产上线门禁使用严格模式:
```bash
PG_TUNING_PROFILE=shared-host npm run perf:postgres:evidence -- --strict --json
```
严格模式会按 `scripts/lib/postgres-tuning-profile.js` 检查 4 核 16G profile
- `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` 必须可用。
如果 PostgreSQL 是独立数据库主机,可改用:
```bash
PG_TUNING_PROFILE=dedicated-db npm run perf:postgres:evidence -- --strict --json
```
## 应用连接池边界
4 核机器的关键不是把 `max_connections` 拉大,而是控制同时活跃 SQL 的数量。
建议:
- `apps/api` 主进程连接池:`DB_POOL_MAX=10``20`
- `apps/worker` 每类 worker 连接池:`DB_POOL_MAX=4``8`
- 导入、导出、对账、公共题库同步等 worker 不要全部高并发同时跑。
- H5/Taro 前端永远不直连数据库,只走 Supabase Auth 或 `apps/api`
- 生产如使用 Supabase pooler应限制事务池大小避免 API 扩容后数据库连接被打满。
## ALTER SYSTEM 示例
上线前先保存当前值:
```sql
select name, setting, unit, source, pending_restart
from pg_settings
where name in (
'max_connections',
'shared_buffers',
'effective_cache_size',
'work_mem',
'maintenance_work_mem',
'autovacuum_work_mem',
'wal_buffers',
'min_wal_size',
'max_wal_size',
'checkpoint_timeout',
'checkpoint_completion_target',
'effective_io_concurrency',
'random_page_cost',
'jit',
'log_min_duration_statement',
'idle_in_transaction_session_timeout',
'statement_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;
```
同机部署推荐先生成 SQL 后人工复核再执行:
```bash
npm run perf:postgres:sql -- --profile=shared-host
```
等价的 shared-host 起步 SQL 如下:
```sql
alter system set max_connections = '80';
alter system set shared_buffers = '3GB';
alter system set effective_cache_size = '10GB';
alter system set work_mem = '16MB';
alter system set maintenance_work_mem = '512MB';
alter system set autovacuum_work_mem = '256MB';
alter system set wal_buffers = '16MB';
alter system set min_wal_size = '1GB';
alter system set max_wal_size = '6GB';
alter system set checkpoint_timeout = '10min';
alter system set checkpoint_completion_target = '0.9';
alter system set effective_io_concurrency = '100';
alter system set random_page_cost = '1.1';
alter system set jit = 'off';
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``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
from pg_settings
where pending_restart = true
order by name;
```
## 导入脚本会话级覆盖
真实迁移导入、批量导入、建索引可能超过普通 API 的 `statement_timeout`。不要为了导入放大生产全局超时,应该在导入连接上局部设置:
```sql
set statement_timeout = '30min';
set lock_timeout = '30s';
set maintenance_work_mem = '1GB';
```
正式切换仍以 `pb:import:dry-run --profile=production``pb:import:json``pb:import:validate` 和业务抽样为准。
## 观察和验收
上线后至少观察 24 到 72 小时:
```sql
select state, wait_event_type, wait_event, count(*)::int
from pg_stat_activity
where datname = current_database()
group by state, wait_event_type, wait_event
order by count desc;
```
```sql
select datname, xact_commit, xact_rollback, blks_read, blks_hit,
round(blks_hit * 100.0 / nullif(blks_hit + blks_read, 0), 2) as cache_hit_ratio
from pg_stat_database
where datname = current_database();
```
```sql
select checkpoints_timed, checkpoints_req, checkpoint_write_time, checkpoint_sync_time
from pg_stat_bgwriter;
```
```sql
select pid, usename, now() - xact_start as xact_age, state, query
from pg_stat_activity
where xact_start is not null
order by xact_age desc
limit 20;
```
验收标准:
- API P95 延迟稳定,没有大量 `ClientRead/ClientWrite`、锁等待或 I/O wait。
- `pb:import:validate` 无 failure。
- `checkpoints_req` 不应持续快速增长;如增长明显,优先调大 `max_wal_size` 或检查写入峰值。
- 没有 OOM、没有频繁连接耗尽、没有长时间 idle in transaction。
- 慢 SQL 日志能对应到具体接口、worker 或迁移脚本。
## pg_stat_statements
上线压测和灰度期间建议启用 `pg_stat_statements`。如果自托管 Supabase/PostgreSQL 允许修改 `shared_preload_libraries`,推荐:
```sql
alter system set shared_preload_libraries = 'pg_stat_statements';
```
该参数需要重启 PostgreSQL。重启后执行
```sql
create extension if not exists pg_stat_statements;
select pg_stat_statements_reset();
```
每轮压测后看总耗时、平均耗时和调用量最高的 SQL再决定是否新增索引、改 SQL、加预聚合或调整 API 缓存。不要把慢 SQL 明细直接提交到 Git因为其中可能包含业务表名、常量和内部路径。
## 压测闭环
调参后必须跑 API 压测,不要只凭参数表判断容量。当前仓库提供本地压测脚本:
```bash
npm run perf:api:local
```
默认压测只读路径,会自动发现真实迁移后的租户、学生、题库入口、合集、蓝图、单词和手册数据,并输出报告到已忽略的 `docs/refactor/performance-reports/`。详细环境变量、4 核 16G 阶梯并发矩阵和报告归档方式见:
- `docs/refactor/performance-benchmark-runbook.md`
4 核 16G 首次上云建议至少跑:
- `smoke`6 并发 30 秒,只读。
- `baseline-30`30 并发 5 分钟,只读。
- `baseline-50`50 并发 5 分钟,只读。
- `mixed-30`30 并发 5 分钟,开启少量练习 session 写入。
若 P95/P99 明显升高,优先结合慢 SQL、`pg_stat_activity` 和报告中的接口明细定位,再考虑索引、预聚合、连接池和 worker 调度;不要直接继续放大 `max_connections`
## 回滚方式
如果调参后出现内存压力、启动失败或查询计划异常:
```sql
alter system reset shared_buffers;
alter system reset effective_cache_size;
alter system reset work_mem;
alter system reset maintenance_work_mem;
alter system reset autovacuum_work_mem;
alter system reset wal_buffers;
alter system reset min_wal_size;
alter system reset max_wal_size;
alter system reset checkpoint_timeout;
alter system reset checkpoint_completion_target;
alter system reset effective_io_concurrency;
alter system reset random_page_cost;
alter system reset jit;
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();
```
`shared_buffers``wal_buffers``max_connections` 已改,需要重启后才算完全回滚。
## 后续优化方向
- 上线后启用 `pg_stat_statements`,按总耗时和平均耗时定位慢 SQL。
-`answer_records``practice_sessions``content_asset_access_events``video_play_events` 等大表规划按月或按租户分区。
- 对租户后台 dashboard、排行榜、销售转化、分佣报表做日/小时预聚合。
- 对真实 `pb:import:json` 增加阶段耗时统计,继续批量化 `questions/question_versions`
- 如果三套 H5、API、worker、Supabase 全部同机,优先加连接池和 worker 调度,而不是继续放大 PostgreSQL 内存参数。