8 GB VPS 上的 PostgreSQL:最关键的十项设置

shared_buffers、work_mem、检查点、autovacuum 和连接池,附上我们使用的取值,以及每一项能解决的症状。

C工CheapServ 工程团队作者 8 分钟阅读
一个带有调节旋钮的数据库圆柱
本页目录7
  1. 内存:四项设置
  2. 检查点:消除写入尖峰
  3. 让规划器知道磁盘是 NVMe
  4. Autovacuum:保持激进
  5. 连接:使用连接池
  6. 完整配置文件
  7. 改动前后都要测量

PostgreSQL 的默认值,是按 2005 年那种与其他所有程序共用一块磁盘的机器来设定的。在配有 8 GB 内存和 NVMe 的 VPS-4 上,十项设置就能让它从保守变得飞快,而且没有一项是冷僻的。下面是我们团队所运维的 PostgreSQL 环境使用的取值,并附上了每一项对应的症状,方便您判断自己究竟需要调整哪几项。

内存:四项设置

shared_buffers = 2GB

这是 PostgreSQL 自己管理的缓存。取内存的四分之一是久经验证的经验法则,在 Linux 上依然成立:超过这个比例之后,内核的页缓存会以更少的双重缓冲完成同样的工作。默认值(128 MB)的症状:本该能放进内存的表,读取 IOPS 却很高。

effective_cache_size = 6GB

它不是一次内存分配,而是一个提示:查询规划器可以假定有多少内存可用于缓存,包括内核的页缓存。请将它设为内存的 75% 左右。症状:规划器在中等规模的表上更倾向于顺序扫描而不是索引扫描,因为它认为读取代价很高。

work_mem = 32MB

这个值是按每个查询中的每次排序或哈希操作分别计算的,所以一个复杂查询可能用到它的好几倍。在 50 个连接的情况下,8 GB 内存配 32 MB 是安全的;对于报表类查询,可以用 SET work_mem = '256MB' 按会话单独调高。默认值(4 MB)的症状:EXPLAIN ANALYZE 在排序操作上显示“external merge Disk”。

maintenance_work_mem = 512MB

供 VACUUM、CREATE INDEX 和 ALTER TABLE 使用。取值越大,建索引和 vacuum 就越快,而且同一时间很少会有多个进程同时用到它。症状:在大表上创建索引,耗时远远超出按行数推测的结果。

检查点:消除写入尖峰

checkpoint_completion_target = 0.9 和 max_wal_size = 4GB

PostgreSQL 会先把变更写入 WAL,再在检查点时把脏页刷到数据文件中。在默认配置下,写入负载较高时,大约每分钟就会出现一次检查点,并且是一次性集中刷盘,随之而来的就是延迟尖峰。让检查点的写入分散在间隔时间的 90% 内完成,并允许两次检查点之间累积 4 GB 的 WAL,就能把尖峰变成平稳的细水长流。症状:每隔几分钟出现一次周期性的延迟尖峰,且与日志中的 checkpoint starting 相吻合;checkpoints_req(位于 pg_stat_bgwriter 中)增长得比 checkpoints_timed 更快。

wal_compression = on

压缩 WAL 中的整页镜像。会多消耗一点 CPU,却能省下大量写入带宽;对于有 IOPS 配额的 VPS,这笔交换永远划算。

让规划器知道磁盘是 NVMe

random_page_cost = 1.1 和 effective_io_concurrency = 200

默认的 random_page_cost 为 4,意味着告诉规划器一次随机读的代价是顺序读的四倍,这对机械硬盘成立,对 NVMe 则不成立。设为 1.1 之后,规划器会在索引扫描更划算的地方选择索引扫描。effective_io_concurrency 让位图扫描可以预取数据;对 NVMe 来说,200 是合适的数量级。症状:带有高选择性 WHERE 子句的查询,仍然选择顺序扫描。

Autovacuum:保持激进

autovacuum_vacuum_scale_factor = 0.05 和 autovacuum_vacuum_cost_limit = 1000

默认值要等到表中 20% 的数据都变成死元组才开始清理,对于一张 5000 万行的表,这意味着 1000 万个死元组和膨胀的索引。5% 能让表保持紧凑;调高 cost limit,能让 vacuum 在 NVMe 上真正跑完,而不是把自己限速到 2005 年的磁盘速度。对于非常热的表,可以用 ALTER TABLE ... SET (autovacuum_vacuum_scale_factor = 0.01) 按表单独设置 scale factor。症状:表在磁盘上的体积不断增大,行数却没有增加;n_dead_tup(位于 pg_stat_user_tables 中)达到数百万。

连接:使用连接池

max_connections = 100,再加 pgbouncer

PostgreSQL 的每个连接都是一个拥有独立内存的进程。一个 Web 应用用 20 个 worker 打开 200 个连接,就是把内存耗在空闲进程上,把 CPU 耗在上下文切换上。请把 max_connections 保持在 100,一旦连接数超过 50 左右,就在前面加一层事务池化模式的 pgbouncer:应用依然向连接池保持 200 个连接,而数据库看到的只有 20 个。症状:FATAL: sorry, too many clients already,或者 pg_stat_activity 中大多是空闲连接,却出现内存压力。

完整配置文件

# /etc/postgresql/16/main/conf.d/tuned.conf  (8 GB VPS, NVMe)
shared_buffers = 2GB
effective_cache_size = 6GB
work_mem = 32MB
maintenance_work_mem = 512MB
checkpoint_completion_target = 0.9
max_wal_size = 4GB
wal_compression = on
random_page_cost = 1.1
effective_io_concurrency = 200
autovacuum_vacuum_scale_factor = 0.05
autovacuum_vacuum_cost_limit = 1000
max_connections = 100

修改 shared_buffers 或 max_connections 之后需要重启 PostgreSQL;其余各项执行 SELECT pg_reload_conf() 即可生效。对于 16 GB 内存的 VPS-8,把前两行的值翻倍;对于 4 GB 内存的套餐,则减半。其余各项与内存大小无关。

改动前后都要测量

启用 pg_stat_statements,并在改动任何配置之前,先看一看按总耗时排序靠前的查询;没有这个视角的调优,只是在瞎猜。改动之后,需要关注的数字有:pg_stat_bgwriter.checkpoints_req(应接近于零)、磁盘读取速率(缓存预热后应当下降),以及应用数据库调用的 p99。在我们自己的服务器集群上,光是这一个配置文件,通常就能让读密集型 Web 应用的 p99 减半,并彻底消除周期性的写入尖峰。

C工
CheapServ 工程团队

负责构建开通流水线、控制面板和存储层的工程师。

一分钟内部署您的第一台服务器。

以 BTC、ETH、XMR 或 USDT 充值,$25 起。余额永不过期,未使用的部分可退款。

立即注册