在高负载环境下,尤其是在同一系统上运行多个 PostgreSQL 实例或在大型安装环境中,PostgreSQL 有时可能会耗尽操作系统的资源限制。本文介绍了 PostgreSQL 使用的关键内核资源,以及如何解决与这些资源消耗相关的问题。
PostgreSQL 依赖操作系统提供的进程间通信(IPC)功能,特别是共享内存和信号量。基于 Unix 的系统通常提供 “System V” IPC、“POSIX” IPC,或两者兼具。Windows 则有自己特定的实现方式,本文不做讨论。
默认情况下,PostgreSQL 分配较少的 System V 共享内存,以及较多的匿名 mmap 共享内存。您也可以选择使用单个大型 System V 共享内存区域(参见 shared_memory_type)。此外,服务器启动时会创建大量信号量,这些信号量可以是 System V 风格或 POSIX 风格。目前,Linux 和 FreeBSD 系统使用 POSIX 信号量,而其他平台则使用 System V 信号量。
System V IPC 的功能通常受限于系统范围内的分配限制。当 PostgreSQL 超过这些限制时,服务器将拒绝启动,并给出错误信息,说明问题及其解决方法(详见第 19.3.1 节)。不同系统的相关内核参数名称一致,表 19.1 提供了这些参数的概览。设置方法因平台而异,本文将对部分平台提供建议。
Table 19.1. System V IPC Parameters
名称 | 描述 | 运行一个 PostgreSQL 实例所需的值 |
---|---|---|
SHMMAX | 共享内存段的最大大小(字节) | 至少 1kB,但通常默认值要高得多 |
SHMMIN | 共享内存段的最小大小(字节) | 1 |
SHMALL | 可用共享内存的总量(字节或页) | 如果是字节,则与 SHMMAX 相同,或者为 ceil(SHMMAX/PAGE_SIZE) 页,加上其他应用程序的空间 |
SHMSEG | 每个进程的最大共享内存段数 | 只需要 1 段,但默认值通常要高得多 |
SHMMNI | 系统范围内的最大共享内存段数 | 类似 SHMSEG,但要为其他应用程序留出空间 |
SEMMNI | 信号量标识符(即集合)的最大数量 | 至少为 ceil((max_connections + autovacuum_max_workers + max_wal_senders + max_worker_processes + 6) / 16),并为其他应用程序留出空间 |
SEMMNS | 系统范围内的最大信号量数 | ceil((max_connections + autovacuum_max_workers + max_wal_senders + max_worker_processes + 6) / 16) * 17,并为其他应用程序留出空间 |
SEMMSL | 每个集合的最大信号量数 | 至少 17 |
SEMMAP | 信号量映射表中的条目数 | 见正文 |
SEMVMX | 信号量的最大值 | 至少 1000(默认值通常为 32767;除非必要,不要更改) |
PostgreSQL 需要少量的 System V 共享内存(通常在 64 位平台上每个服务器实例约 48 字节)。在大多数现代操作系统上,这个数量可以轻松满足。然而,如果您运行多个服务器实例,或者显式配置服务器使用大量的 System V 共享内存(参见 shared_memory_type 和 dynamic_shared_memory_type),则可能需要增加 SHMALL,这表示系统范围内的 System V 共享内存总量。请注意,在许多系统中,SHMALL 以页而非字节为单位。
共享内存段的最小大小(SHMMIN)通常不会引起问题,因为 PostgreSQL 需要的最大值约为 32 字节(通常为 1)。系统范围内的最大段数(SHMMNI)或每个进程的最大段数(SHMSEG)也不太可能成为问题,除非您的系统将这些参数设置为零。
使用 System V 信号量时,PostgreSQL 会为每个允许的连接(max_connections)、自动清理工作进程(autovacuum_max_workers)、WAL 发送进程(max_wal_senders)和后台进程(max_worker_processes)分配一个信号量,信号量分为 16 组。每组还包含一个第 17 个信号量,用于检测与其他应用程序使用的信号量集合的冲突。系统中信号量的最大数量由 SEMMNS 设置,因此其值至少应为 max_connections + autovacuum_max_workers + max_wal_senders + max_worker_processes 加上每 16 个允许连接和工作进程所需的一个额外信号量。SEMMNI 参数决定了系统上可存在的信号量集合的数量限制。因此,该参数必须至少为 ceil((max_connections + autovacuum_max_workers + max_wal_senders + max_worker_processes + 6) / 16)。降低允许的连接数量是一个临时解决方法,以应对因 semget 函数导致的“设备上没有剩余空间”之类的错误。
在某些情况下,可能还需要将 SEMMAP 增加到至少与 SEMMNS 同一个数量级。如果系统有这个参数(许多系统没有),它定义了信号量资源映射表的大小,其中每个可用信号量的连续块需要一个条目。当一个信号量集合被释放时,它要么被添加到一个与释放块相邻的现有条目中,要么在新的映射表条目中注册。如果映射表已满,释放的信号量将丢失(直到重启为止)。信号量空间的碎片化可能会导致可用信号量数量减少。
与“信号量撤销”相关的其他设置,如 SEMMNU 和 SEMUME,对 PostgreSQL 没有影响。
使用 POSIX 信号量时,所需的信号量数量与 System V 信号量相同,即每个允许的连接(max_connections)、自动清理工作进程(autovacuum_max_workers)、WAL 发送进程(max_wal_senders)和后台进程(max_worker_processes)都需要一个信号量。在支持 POSIX 信号量的平台上,没有特定的内核限制信号量的数量。
各平台配置建议
在使用 systemd 的系统中,必须特别注意 IPC 资源(包括共享内存)的管理,防止操作系统过早移除这些资源。这一点在从源代码安装 PostgreSQL 时尤为重要。RemoveIPC
设置决定用户注销时是否移除 IPC 对象,建议将其设置为 RemoveIPC=no
,以确保 PostgreSQL 的稳定性。
如果 RemoveIPC
设置为开启状态,可能导致并行查询执行时使用的共享内存对象在意想不到的时间被移除,从而引发错误和警告,例如:
WARNING: could not remove shared memory segment "/PostgreSQL.1450751626": No such file or directory
由于 systemd 对不同类型的 IPC 对象(如共享内存与信号量,System V 与 POSIX)处理方式略有不同,可能会观察到某些 IPC 资源没有像其他资源那样被移除。
“用户注销”可能在维护作业中自动发生,也可能由管理员手动以 postgres 用户身份登录时触发,因此通常难以避免。
systemd 在编译时通过 /etc/login.defs
中的 SYS_UID_MAX
设置来确定哪些用户被视为“系统用户”。
在打包和部署脚本时,务必通过 useradd -r
、adduser --system
或类似命令将 postgres 用户创建为系统用户。如果用户帐户创建不正确或无法更改,建议在 /etc/systemd/logind.conf
或其他合适的配置文件中设置 RemoveIPC=no
。
类似 Unix 的操作系统通常会强制执行各种资源限制,这些限制可能会影响 PostgreSQL 服务器的正常运行。特别需要关注的是每个用户的进程数限制、每个进程的打开文件数限制以及每个进程的可用内存限制。这些限制通常分为“软”限制和“硬”限制。软限制是可以由用户在运行时调整的,而硬限制只能由 root 用户更改。setrlimit
系统调用用于设置这些参数。Bourne shell 的内置命令 ulimit
或 csh 的 limit
可以从命令行控制资源限制。在 BSD 系统中,/etc/login.conf 文件则用于在登录时设置各种资源限制。有关详细信息,请参阅操作系统文档。常见的参数包括 maxproc
、openfiles
和 datasize
,例如:
default:\
...
:datasize-cur=256M:\
:maxproc-cur=256:\
:openfiles-cur=256:\
...
(-cur
表示软限制,-max
则设置硬限制。)
此外,内核可能对某些资源有系统范围的限制。
在 Linux 系统中,fs.file-max
参数决定了内核可以支持的最大打开文件数。您可以通过以下命令更改该设置:
sysctl -w fs.file-max=N
为确保设置在重启后仍然有效,请将相应的配置添加到 /etc/sysctl.conf
中。每个进程的最大文件数限制在内核编译时就已确定。有关更多信息,请参阅 /usr/src/linux/Documentation/proc.txt
。
PostgreSQL 服务器通常每个连接使用一个进程,因此您需要确保系统提供足够的进程数来支持所有允许的连接数量,以及系统中的其他进程。这通常不是问题,但如果您在同一台机器上运行多个服务器,可能会面临资源紧张的情况。
默认的文件数限制通常设置为较保守的“社会友好”值,以允许多用户在一台机器上共存,而不会过度使用系统资源。如果您在专用服务器上运行多个 PostgreSQL 实例,可能需要提高此限制。
另一方面,某些系统允许单个进程打开大量文件;如果多个进程都这样做,则可能会很快达到系统范围的限制。如果您遇到这种情况,不希望更改系统范围的限制,可以通过设置 PostgreSQL 的 max_files_per_process
参数来限制每个进程的打开文件数。
另一个可能影响大量客户端连接的内核限制是最大套接字连接队列长度。如果在短时间内到达的连接请求数量超过此限制,PostgreSQL 服务器在处理这些请求之前可能会拒绝一些请求,导致客户端收到类似“资源暂时不可用”或“连接被拒绝”的错误。许多平台的默认队列长度限制为 128。您可以通过 sysctl
调整相应的内核参数来提高此限制,然后重新启动 PostgreSQL 服务器。该参数在 Linux 系统中名为 net.core.somaxconn
,在较新的 FreeBSD 系统中名为 kern.ipc.soacceptqueue
,而在 macOS 和其他 BSD 变体上则名为 kern.ipc.somaxconn
。
在 Linux 上,默认的虚拟内存设置并不完全适合 PostgreSQL。由于内核处理内存过量使用的方式,当 PostgreSQL 或其他进程的内存需求导致系统耗尽虚拟内存时,内核可能会终止 PostgreSQL 的 postmaster 进程。
如果发生这种情况,您可能会看到如下的内核错误消息:
Out of Memory: Killed process 12345 (postgres).
这意味着 postgres
进程由于内存不足而被终止。虽然现有的数据库连接可能仍然正常工作,但系统将无法接受新的连接。要恢复正常运行,您需要重新启动 PostgreSQL。
为避免这种情况,建议在内存充足的机器上运行 PostgreSQL,以确保其他进程不会导致系统内存耗尽。如果内存较为紧张,可以通过增加操作系统的交换空间来缓解,因为内核的 OOM 杀手(Out-of-Memory Killer)通常只有在物理内存和交换空间都耗尽时才会被触发。
如果 PostgreSQL 自身导致了系统内存的耗尽,可以通过调整配置来解决。在某些情况下,降低与内存相关的参数设置,如 shared_buffers
、work_mem
和 hash_mem_multiplier
,可能会有所帮助。在其他情况下,问题可能是由于允许的数据库连接数过多导致的。这时,减少 max_connections
并使用外部连接池可能是更好的解决方案。
您还可以通过启用严格的内存超支模式来修改内核行为,避免过度分配内存。虽然这不能完全防止 OOM 的触发,但可以显著降低其风险,从而提高系统的稳定性。您可以使用以下 sysctl
命令进行配置:
sysctl -w vm.overcommit_memory=2
或者,将相应的配置项添加到 /etc/sysctl.conf
,以确保设置在系统重启后依然有效。
另外,您还可以通过调整 postmaster 进程的 OOM 分数来确保其不被 OOM 杀手终止。最简单的方法是在 PostgreSQL 启动脚本中执行以下命令:
echo -1000 > /proc/self/oom_score_adj
请注意,这个操作必须由 root 用户执行。为确保子进程具有正常的 OOM 分数,可以在启动脚本中设置以下环境变量:
export PG_OOM_ADJUST_FILE=/proc/self/oom_score_adj
export PG_OOM_ADJUST_VALUE=0
这些设置将使 postmaster 子进程以默认的 OOM 分数(零)运行,以便在需要时 OOM 杀手仍然可以针对它们。如果您希望子进程使用其他 OOM 分数调整,可以更改 PG_OOM_ADJUST_VALUE
的值。若不设置 PG_OOM_ADJUST_FILE
,子进程将继承 postmaster 的 OOM 分数调整,这可能导致系统稳定性问题。
使用大页可以显著减少在处理大型连续内存块时的开销,尤其是在 PostgreSQL 中配置了大量 shared_buffers
的情况下。要启用大页功能,您需要一个支持 CONFIG_HUGETLBFS=y
和 CONFIG_HUGETLB_PAGE=y
的内核,并且需要配置操作系统以提供足够数量的大页。
要确定所需的大页数量,可以使用以下命令查看 shared_memory_size_in_huge_pages
的值。请注意,必须关闭服务器才能查看此运行时计算的参数。示例如下:
postgres -D $PGDATA -C shared_memory_size_in_huge_pages
3170
grep ^Hugepagesize /proc/meminfo
Hugepagesize: 2048 kB
ls /sys/kernel/mm/hugepages
hugepages-1048576kB hugepages-2048kB
在这个示例中,默认的大页大小为 2MB,但您也可以通过 huge_page_size
参数明确指定 2MB 或 1GB 的大页大小,以适应计算所得的页面数量。虽然在示例中至少需要 3170 个大页,但如果系统中的其他程序也需要大页,则应设置更大的值。可以通过以下命令设置大页数量:
sysctl -w vm.nr_hugepages=3170
为了确保设置在系统重启后依然生效,别忘了将配置添加到 /etc/sysctl.conf
中。对于非默认的大页大小,可以使用以下命令进行配置:
echo 3170 > /sys/kernel/mm/hugepages/hugepages-2048kB/nr_hugepages
您还可以在系统引导时通过设置内核参数来配置大页,例如:
hugepagesz=2M hugepages=3170
有时由于内存碎片化,内核可能无法立即分配所需数量的大页,这时可能需要重复命令或重新启动系统。在系统重启后,大多数内存通常可用于转换为大页。要验证某个特定大小的大页分配情况,可以使用以下命令:
cat /sys/kernel/mm/hugepages/hugepages-2048kB/nr_hugepages
此外,您可能需要通过 sysctl
设置 vm.hugetlb_shm_group
,或通过 ulimit -l
命令为数据库服务器的操作系统用户授予使用大页的权限。
在 PostgreSQL 中,大页的默认行为是当可能时使用大页,并在失败时回退到普通页面。如果要强制使用大页,可以在 postgresql.conf
中将 huge_pages
设置为 on
。请注意,如果没有足够的大页可用,PostgreSQL 将无法启动。
有关 Linux 大页功能的详细说明,请参阅如下链接:https://www.kernel.org/doc/Documentation/vm/hugetlbpage.txt