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

16 KiB
Raw Blame History

PostgreSQL 4 核 16G 生产调参基线

更新时间2026-07-01

这份文档用于后续把 Supabase/PostgreSQL 自托管到 4 核 16G 云服务器时做生产起步配置。目标是先给题库 SaaS 一个安全、可回滚、可观测的基线,而不是追求一次性压满硬件。

参考来源:

说明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.1SSD 云盘下可以鼓励索引扫描,但官方文档也提示 planner cost 没有统一理想值,不能只凭单个查询实验盲调。上线后以真实慢 SQL、缓存命中和查询计划为准。
  • max_connections=804 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

证据采集脚本

仓库已补上线前证据采集脚本:

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,不要提交真实证据文件。

生产上线门禁使用严格模式:

PG_TUNING_PROFILE=shared-host npm run perf:postgres:evidence -- --strict --json

严格模式会按 scripts/lib/postgres-tuning-profile.js 检查 4 核 16G profile

  • jit=off
  • statement_timeoutidle_in_transaction_session_timeoutlock_timeout 不得为 0。
  • track_io_timing=ontrack_wal_io_timing=onlog_checkpoints=on
  • temp_file_limitlog_temp_filesmax_parallel_workers_per_gather 必须落在 profile 范围内。
  • 所有 profile 参数必须在允许范围内。
  • pending_restart 必须为 0。
  • pg_stat_statements 必须可用。

如果 PostgreSQL 是独立数据库主机,可改用:

PG_TUNING_PROFILE=dedicated-db npm run perf:postgres:evidence -- --strict --json

应用连接池边界

4 核机器的关键不是把 max_connections 拉大,而是控制同时活跃 SQL 的数量。

建议:

  • apps/api 主进程连接池:DB_POOL_MAX=1020
  • apps/worker 每类 worker 连接池:DB_POOL_MAX=48
  • 导入、导出、对账、公共题库同步等 worker 不要全部高并发同时跑。
  • H5/Taro 前端永远不直连数据库,只走 Supabase Auth 或 apps/api
  • 生产如使用 Supabase pooler应限制事务池大小避免 API 扩容后数据库连接被打满。

ALTER SYSTEM 示例

上线前先保存当前值:

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 后人工复核再执行:

npm run perf:postgres:sql -- --profile=shared-host

等价的 shared-host 起步 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_connectionsshared_bufferswal_bufferstrack_io_timingtrack_wal_io_timinglog_checkpointslog_temp_filestemp_file_limitmax_parallel_workers_per_gather 通常可 reload但仍以后续 pending_restart 检查为准。执行后用下面语句确认:

select name, setting, unit, pending_restart
from pg_settings
where pending_restart = true
order by name;

导入脚本会话级覆盖

真实迁移导入、批量导入、建索引可能超过普通 API 的 statement_timeout。不要为了导入放大生产全局超时,应该在导入连接上局部设置:

set statement_timeout = '30min';
set lock_timeout = '30s';
set maintenance_work_mem = '1GB';

正式切换仍以 pb:import:dry-run --profile=productionpb:import:jsonpb:import:validate 和业务抽样为准。

观察和验收

上线后至少观察 24 到 72 小时:

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;
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();
select checkpoints_timed, checkpoints_req, checkpoint_write_time, checkpoint_sync_time
from pg_stat_bgwriter;
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,推荐:

alter system set shared_preload_libraries = 'pg_stat_statements';

该参数需要重启 PostgreSQL。重启后执行

create extension if not exists pg_stat_statements;
select pg_stat_statements_reset();

每轮压测后看总耗时、平均耗时和调用量最高的 SQL再决定是否新增索引、改 SQL、加预聚合或调整 API 缓存。不要把慢 SQL 明细直接提交到 Git因为其中可能包含业务表名、常量和内部路径。

压测闭环

调参后必须跑 API 压测,不要只凭参数表判断容量。当前仓库提供本地压测脚本:

npm run perf:api:local

默认压测只读路径,会自动发现真实迁移后的租户、学生、题库入口、合集、蓝图、单词和手册数据,并输出报告到已忽略的 docs/refactor/performance-reports/。详细环境变量、4 核 16G 阶梯并发矩阵和报告归档方式见:

  • docs/refactor/performance-benchmark-runbook.md

4 核 16G 首次上云建议至少跑:

  • smoke6 并发 30 秒,只读。
  • baseline-3030 并发 5 分钟,只读。
  • baseline-5050 并发 5 分钟,只读。
  • mixed-3030 并发 5 分钟,开启少量练习 session 写入。

若 P95/P99 明显升高,优先结合慢 SQL、pg_stat_activity 和报告中的接口明细定位,再考虑索引、预聚合、连接池和 worker 调度;不要直接继续放大 max_connections

回滚方式

如果调参后出现内存压力、启动失败或查询计划异常:

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_bufferswal_buffersmax_connections 已改,需要重启后才算完全回滚。

后续优化方向

  • 上线后启用 pg_stat_statements,按总耗时和平均耗时定位慢 SQL。
  • answer_recordspractice_sessionscontent_asset_access_eventsvideo_play_events 等大表规划按月或按租户分区。
  • 对租户后台 dashboard、排行榜、销售转化、分佣报表做日/小时预聚合。
  • 对真实 pb:import:json 增加阶段耗时统计,继续批量化 questions/question_versions
  • 如果三套 H5、API、worker、Supabase 全部同机,优先加连接池和 worker 调度,而不是继续放大 PostgreSQL 内存参数。