4. 操作系统与内核调优

PostgreSQL 的性能并不只由数据库参数决定。它运行在操作系统之上,内存分配、页表管理、文件描述符、I/O 调度和网络协议栈都会直接影响数据库的吞吐与延迟。很多“数据库参数已经调好但性能仍不达标”的案例,根因往往在操作系统层。本节围绕五类内核资源,说明它们与 PostgreSQL 的匹配关系以及实际调整方法。

共享内存:PostgreSQL 的命脉

PostgreSQL 采用多进程架构,每个后端进程通过共享内存交换数据。共享内存中最重要的部分是 shared_buffers,此外还有 WAL 缓冲区、锁表、事务状态等。操作系统必须允许 PostgreSQL 申请足够大的共享内存段,否则实例可能启动失败或被迫使用较小的值。

在 Linux 上,共享内存相关参数主要有三个:kernel.shmmax 表示单个共享内存段的最大字节数,kernel.shmall 表示系统范围内可分配的共享内存总页数,kernel.shmmni 表示共享内存段的最大数量。对于现代 PostgreSQL,通常建议把 shmmax 设置为物理内存的一半或更大,shmall 设置为物理内存除以页大小。例如 64GB 内存、4KB 页大小,shmall 可设为 16777216。

# 查看当前值
sysctl kernel.shmmax kernel.shmall kernel.shmmni

# 临时调整
sysctl -w kernel.shmmax=34359738368
sysctl -w kernel.shmall=16777216

# 持久化写入 /etc/sysctl.d/99-postgresql.conf
kernel.shmmax = 34359738368
kernel.shmall = 16777216
kernel.shmmni = 4096

调整后需重启 PostgreSQL 使共享内存重新分配。判断是否生效,可查看 pg_postmaster_start_time() 与启动日志中是否有共享内存相关报错。

大页:减少页表开销

当 shared_buffers 达到数十 GB 时,普通 4KB 页会产生大量页表项,TLB 命中率下降,内存访问延迟上升。透明大页(THP)由内核自动管理,但可能带来不可预测的延迟抖动,生产环境通常建议关闭 THP,改用显式大页(HugePages)。

显式大页需要预先在操作系统层预留,PostgreSQL 通过 huge_pages = try 或 on 使用。计算所需大页数量的方法是:shared_buffers 加上少量开销,再除以大页大小(通常 2MB)。例如 shared_buffers = 32GB,则至少需要 32GB / 2MB = 16384 个大页,建议预留 16500 个。

# 查看大页状态
grep Huge /proc/meminfo

# 预留 16500 个 2MB 大页
sysctl -w vm.nr_hugepages=16500

# 持久化
vm.nr_hugepages = 16500

设置 huge_pages = try 时,若大页不足,PostgreSQL 会回退到普通页并在日志中提示;设置为 on 则启动失败。生产环境建议先用 try 验证,再改为 on 确保稳定使用。

文件句柄:连接与文件的硬上限

PostgreSQL 每个连接对应一个后端进程,每个进程会打开数据文件、WAL 文件、套接字等。如果 max_connections 较高,或存在大量分区表、外部表,文件描述符消耗会迅速上升。操作系统默认的 nofile 限制(常见 1024)往往不够。

建议将 PostgreSQL 运行用户的 nofile 软硬限制都提高到 65536 以上。可通过 /etc/security/limits.conf 或 systemd 服务单元的 LimitNOFILE 设置。修改后需重新登录或重启服务生效。

# /etc/security/limits.conf
postgres soft nofile 65536
postgres hard nofile 65536

# 或 systemd override
[Service]
LimitNOFILE=65536

验证方式:以 postgres 用户执行 ulimit -n,或在数据库中查询 pg_stat_activity 连接数,结合 lsof -p <pid> | wc -l 观察实际占用。

磁盘调度:让 I/O 更可预测

PostgreSQL 的 I/O 模式以随机读和顺序写 WAL 为主。对于 SSD,通常建议使用 none(即 noop)调度器,减少内核层不必要的排序;对于传统机械盘,mq-deadline 或 bfq 可能更合适。此外,vm.dirty_ratio 和 vm.dirty_background_ratio 控制脏页回写,设置过高会导致突发大量写盘,引起延迟尖峰。

# 查看当前调度器
cat /sys/block/nvme0n1/queue/scheduler

# 临时切换为 none
echo none > /sys/block/nvme0n1/queue/scheduler

# 调整脏页比例
sysctl -w vm.dirty_background_ratio=5
sysctl -w vm.dirty_ratio=10

同时应确保 wal_sync_method 与文件系统匹配。在 Linux 上通常使用 fdatasync,若使用 open_datasync 需确认文件系统支持。

网络参数:高并发连接的关键

PostgreSQL 客户端连接、流复制、连接池都依赖 TCP。默认内核参数在高并发下容易出现连接排队、端口耗尽或重传过多。重点参数包括:net.core.somaxconn 控制 accept 队列长度,net.ipv4.tcp_max_syn_backlog 控制 SYN 队列,net.ipv4.ip_local_port_range 决定客户端可用端口范围,net.ipv4.tcp_tw_reuse 允许复用 TIME_WAIT 连接。

net.core.somaxconn = 4096
net.ipv4.tcp_max_syn_backlog = 8192
net.ipv4.ip_local_port_range = 10000 65535
net.ipv4.tcp_tw_reuse = 1
net.ipv4.tcp_keepalive_time = 600
net.ipv4.tcp_keepalive_intvl = 30
net.ipv4.tcp_keepalive_probes = 3

这些参数需与 PostgreSQL 的 max_connections、连接池大小以及 listen_backlog(默认 128)协同考虑。若 somaxconn 小于 listen_backlog,实际生效值取较小者。

操作系统内核资源与 PostgreSQL 匹配关系 PostgreSQL 多进程 · 共享内存 · WAL 共享内存 kernel.shmmax kernel.shmall shared_buffers 大页 HugePages vm.nr_hugepages huge_pages = try/on 2MB 页 · TLB 命中 文件句柄 nofile 软/硬限制 LimitNOFILE max_connections 磁盘调度与脏页 scheduler = none vm.dirty_ratio wal_sync_method 网络参数 somaxconn · backlog port_range · tw_reuse

参数协同与验证

这些参数不是孤立的。例如,shared_buffers 增大后,必须同步检查 shmmax 和大页数量;max_connections 提高后,必须同步提高 nofile 和网络 backlog。建议按以下顺序调整:先确定 shared_buffers,再配置共享内存与大页;然后根据连接规模调整文件句柄和网络参数;最后根据存储类型选择磁盘调度器与脏页比例。

验证时,可结合 pg_stat_database 的缓存命中率、pg_stat_bgwriter 的检查点与后台写、pg_stat_activity 的连接状态,以及操作系统层的 sar、iostat、vmstat 观察内存、I/O 和网络变化。若调整后缓存命中率上升、检查点写盘更平滑、连接建立失败减少,说明内核参数与 PostgreSQL 的匹配方向正确。

生产环境调整内核参数前,应在测试环境验证,并记录调整前后的基线指标。大页和共享内存的修改通常需要重启实例,建议安排在维护窗口执行。