PostgreSQL有时会耗尽各种操作系统资源限制,尤其是在同一系统上同时运行多份服务器副本的情况下,或在非常庞大的安装中。本部分将阐述PostgreSQL所使用的内核资源,以及可以采取的解决步骤来解决与内核资源消耗相关的问题。
PostgreSQL要求操作系统提供进程间通信 (IPC) 特性,特别是共享内存和信号量。从 Unix 衍生的系统通常提供“System V”IPC, “POSIX”IPC, 或两者。 Windows有自己的这些特性的实现,这里不做讨论。
默认情况下,PostgreSQL会分配少量 System V 共享内存,以及数量大得多的匿名 mmap
共享内存。另外,也可以使用一个大的 System V 共享内存区域(见 shared_memory_type)。此外,在服务器启动时还会创建大量信号量,它们可以是 System V 或者 POSIX 样式。目前,POSIX 信号量在 Linux 和 FreeBSD 系统上使用,而其他平台则使用 System V 信号量。
System VIPC特性通常受系统范围分配限制的制约。当PostgreSQL超过这些限制之一时,服务器将拒绝启动,应该留下一个有启发性的错误消息,描述问题及其处理方法。(另见 第 18.3.1 节。)相关的内核参数在不同的系统上命名一致;表 18.1提供了概述。然而,设置方法各不相同。下面给出了针对一些平台的建议。
表 18.1. System VIPC参数
名称 | 说明 | 运行一个PostgreSQL实例所需的值 |
---|---|---|
SHMMAX |
共享内存段的最大大小(字节) | 至少 1kB,但默认值通常要高得多 |
SHMMIN |
共享内存段的最小大小(字节) | 1 |
SHMALL |
可用的共享内存总数(字节或页) | 与 SHMMAX 相同,如果以字节为单位,或者 ceil(SHMMAX/PAGE_SIZE) 如果以页为单位,加上其他应用程序的空间 |
SHMSEG |
每个进程允许的最大共享内存区段数 | 只需一个区段,但默认值高得多 |
SHMMNI |
系统范围内的共享内存区段的最大数量 | 与 SHMSEG 和其他应用程序的空间相同 |
SEMMNI |
允许的信号量标识符(即,集合)的最大数量 | 至少 ceil((max_connections + autovacuum_max_workers + max_wal_senders + max_worker_processes + 7) / 16) 加上其他应用程序的空间 |
SEMMNS |
系统范围内的信号量最大数量 | ceil((max_connections + autovacuum_max_workers + max_wal_senders + max_worker_processes + 7) / 16) * 17 加上其他应用程序的空间 |
SEMMSL |
每个集合的最大信号量数 | 至少 17 |
SEMMAP |
信号量映射中的条目数量 | 见本文 |
SEMVMX |
信号量的最大值 | 至少 1000(默认值通常为 32767;除非必要,请勿更改) |
PostgreSQL 要求每个服务器副本 48 个字节(在 64 位平台上通常为 48 字节)的系统 V 共享内存。在大多数现代操作系统上,可以轻松分配此容量。然而,如果您正在运行大量服务器副本,或者您显式将服务器配置为使用大量的系统 V 共享内存(参见 shared_memory_type 和 dynamic_shared_memory_type),可能需要增加 SHMALL
,该值是系统范围内的系统 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 个允许连接和工作程序的 1 个(请参见表 18.1中的公式)。参数SEMMNI
确定系统中可以同时存在信号量组数量的限制。因此,此参数至少应为ceil((max_connections + autovacuum_max_workers + max_wal_senders + max_worker_processes + 7) / 16)
。降低允许的连接数是针对 (semget
函数发出“设备上没有空间”这样的令人费解的话术的故障的临时规避方法。
在某些情况下,可能还需要将SEMMAP
增大到至少与SEMMNS
同量级。如果系统具有此参数(许多系统没有),它将定义信号量资源映射的大小,其中每个连续的可用信号量块都需要一个条目。释放信号量组后,它会添加到与释放块相邻的现有条目中,或者在新的映射条目下注册。如果映射已满,已释放的信号量将丢失(直到重新启动)。信号量空间碎片化可能会随着时间推移而导致可用的信号量少于应有的数量。
与“信号量撤消”相关的其他各种设置,例如 SEMMNU
和SEMUME
,不会影响PostgreSQL。
在使用 POSIX 信号量时,需要的信号量数量与 System V 相同,即一个允许连接 (max_connections)、一个允许自动清理工作进程 (autovacuum_max_workers)、一个允许 WAL 发送进程 (max_wal_senders),以及一个允许后台进程 (max_worker_processes) 所需的一个信号量。在将此选项设为首选的平台上,POSIX 信号量数量没有具体的内核限制。
默认共享内存设置通常已经足够,除非您已将 shared_memory_type
设置为 sysv
。此平台不使用 System V 信号量。
可以使用 sysctl
或 loader
接口更改默认 IPC 设置。可以使用 sysctl
设置以下参数
#
sysctl kern.ipc.shmall=32768
#
sysctl kern.ipc.shmmax=134217728
要使这些设置在重启之后依然有效,请修改 /etc/sysctl.conf
。
如果您已将 shared_memory_type
设置为 sysv
,您可能还希望配置内核以将 System V 共享内存锁定到 RAM 中,并防止将其分页到交换空间中。可以使用 sysctl
设置 kern.ipc.shm_use_phys
来实现此目的。
如果在 FreeBSD 监狱中运行,则应将其 sysvshm
参数设置为 new
,以便使其拥有自己的独立 System V 共享内存名称空间。(在 FreeBSD 11.0 之前,必须允许对主机 IPC 名称空间从监狱共同访问,并采取措施避免冲突。)
默认共享内存设置通常已经足够,除非您已将 shared_memory_type
设置为 sysv
。通常您需要增加 kern.ipc.semmni
和 kern.ipc.semmns
,因为 NetBSD 中的默认设置对于这些来说有些太小了。
可以使用 sysctl
调整 IPC 参数,例如
#
sysctl -w kern.ipc.semmni=100
要使这些设置在重启之后依然有效,请修改 /etc/sysctl.conf
。
如果您已将 shared_memory_type
设置为 sysv
,您可能还希望配置内核以将 System V 共享内存锁定到 RAM 中,并防止将其分页到交换空间中。可以使用 sysctl
设置 kern.ipc.shm_use_phys
来实现此目的。
默认共享内存设置通常已经足够,除非您已将 shared_memory_type
设置为 sysv
。通常您需要增加 kern.seminfo.semmni
和 kern.seminfo.semmns
,因为 OpenBSD 中的默认设置对于这些来说有些太小了。
可以使用 sysctl
调整 IPC 参数,例如
#
sysctl kern.seminfo.semmni=100
要使这些设置在重启之后依然有效,请修改 /etc/sysctl.conf
。
默认的共享内存设置通常足够好,除非您已将 shared_memory_type
设置为 sysv
,且仅当在内核随附的默认值较低的情况下。
可通过 sysctl
接口来更改共享内存大小设置。例如,允许 16 GB
$
sysctl -w kernel.shmmax=17179869184
$
sysctl -w kernel.shmall=4194304
若要在重启后使这些设置持久存在,请参见 /etc/sysctl.conf
。
默认的共享内存和信号量设置通常足够好,除非您已将 shared_memory_type
设置为 sysv
。
建议用于在 macOS 中配置共享内存的方法是创建一个名为 /etc/sysctl.conf
的文件,其中包含变量分配,例如
kern.sysv.shmmax=4194304 kern.sysv.shmmin=1 kern.sysv.shmmni=32 kern.sysv.shmseg=8 kern.sysv.shmall=1024
请注意,在某些 macOS 中,全部五个 共享内存参数必须在 /etc/sysctl.conf
中设置,否则该值将被忽略。
SHMMAX
只能设置为 4096 的倍数。
SHMALL
在该平台上的衡量单位为 4 kB 页。
除了 SHMMNI
,可以使用 sysctl 随时更改其他参数。但最好还是通过 /etc/sysctl.conf
设置您推荐的值,以便在重启后保留这些值。
默认的共享内存和信号量设置通常足以满足大多数 PostgreSQL 应用程序。Solaris 默认将 SHMMAX
设置为系统RAM的四分之一。若要进一步调整此设置,请使用与 postgres
用户相关的项目设置。例如,以 root
身份运行以下命令
projadd -c "PostgreSQL DB User" -K "project.max-shm-memory=(privileged,8GB,deny)" -U postgres -G postgres user.postgres
此命令添加了 user.postgres
项目,并将 postgres
的共享内存最大设置为 8GB,并在用户下次登录或重新启动 PostgreSQL(而非重新加载)时生效。上述命令假设 PostgreSQL 由 postgres
组中的 postgres
用户运行。无需重新启动服务器。
数据库服务器上其他推荐的内核设置更改将有大量的连接
project.max-shm-ids=(priv,32768,deny) project.max-sem-ids=(priv,4096,deny) project.max-msg-ids=(priv,4096,deny)
此外,如果您在区域内运行 PostgreSQL,则可能还需要提高区域资源使用限制。有关 projects
和 prctl
的详细信息,请参见 系统管理员指南 中的“第 2 章:项目和任务”。
如果使用 systemd,则必须小心,不要让操作系统过早地移除 IPC 资源(包括共享内存)。当从源代码安装 PostgreSQL 时,这一点尤其需要注意。使用 PostgreSQL 发行软件包的用户不太可能受到影响,因为通常会将 postgres
用户创建为系统用户。
logind.conf
中的设置 RemoveIPC
控制在用户完全注销时是否移除 IPC 对象。系统用户除外。此设置在库存 systemd 中默认为开启,但一些操作系统发行版将其默认为关闭。
当此设置开启时,通常会出现的影响是,用于并行查询执行的共享内存对象将在看似随机的时间被移除,导致在尝试打开和移除它们时出现错误和警告,例如
WARNING: could not remove shared memory segment "/PostgreSQL.1450751626": No such file or directory
不同类型的 IPC 对象(共享内存与信号量,System V 与 POSIX)由 systemd 以稍微不同的方式处理,因此可能会观察到一些 IPC 资源的移除方式与其他资源不同。但建议不要依赖这些细微差别。
“用户注销”可能会在维护作业中发生,或者在管理员以 postgres
用户或类似用户身份登录时手动发生,因此通常很难阻止。
“系统用户”是在 systemd 编译时从 /etc/login.defs
中的
设置确定的。
打包和部署脚本应该注意使用 useradd -r
、postgres
用户创建为系统用户。
或者,如果用户帐户创建不正确或无法更改,则建议设置
RemoveIPC=no
在 /etc/systemd/logind.conf
或其他相应的配置文件中。
必须确保这两个条件中至少有一个,否则 PostgreSQL 服务器将非常不可靠。
类 Unix 操作系统强制执行各种可能会干扰您的 PostgreSQL 服务器操作的资源限制。特别重要的是对每个用户进程数、每个进程打开文件数以及每个进程可用内存大小的限制。这些限制中,每个都有一个 “硬” 限制和一个 “软” 限制。软限制就是实际算数的限制,但用户可以在硬限制内进行更改。只有 root 用户才能更改硬限制。系统调用 setrlimit
负责设置这些参数。外壳内置命令 ulimit
(Bourne 外壳)或 limit
(csh)用于从命令行控制资源限制。在 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 的 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 后台进程(监视服务器进程)。
如果发生此情况,您会看到类似以下内容的内核消息(参阅系统文档和配置以了解在何处查找此类消息)
Out of Memory: Killed process 12345 (postgres).
这表明 postgres
进程由于内存压力而终止。虽然现有数据库连接将继续正常运行,但系统不再接受新连接。为了恢复正常运行,需要重新启动 PostgreSQL。
避免此问题的一个方法是在其他进程不会导致计算机耗尽内存的计算机上运行 PostgreSQL。如果内存紧缺,增大操作系统的交换空间可以帮助避免此问题,因为只有在物理内存和交换空间都耗尽时,才会启动内存不足 (OOM) 终结程序。
如果PostgreSQL本身导致系统内存耗尽,可以通过更改配置来避免这个问题。在某些情况下,降低与内存相关的配置参数可能会有所帮助,尤其是shared_buffers
、work_mem
和hash_mem_multiplier
。在其他情况下,问题可能是允许太多连接到数据库服务器本身。在许多情况下,最好减少max_connections
,并改用外部连接池软件。
可以修改内核的行为,使其不会“超量提交”内存。虽然此设置不会完全阻止调用OOM killer,但会大大降低其被调用的机会,因此会导致系统行为更稳健。这可以通过sysctl
选择严格超量提交模式来实现
sysctl -w vm.overcommit_memory=2
或在/etc/sysctl.conf
中放置等效条目。您还可能希望修改相关设置vm.overcommit_ratio
。有关详细信息,请参阅内核文档文件https://www.kernel.org/doc/Documentation/vm/overcommit-accounting。
另一种方法是可以与更改vm.overcommit_memory
同时使用,也可以单独使用,那就是将postmaster进程的特定于进程的OOM 分数调整值设置为-1000
,从而保证它不会成为 OOM killer 的目标。执行此操作最简单的方法是在PostgreSQL启动脚本中调用postgres
之前执行
echo -1000 > /proc/self/oom_score_adj
请注意,此操作必须以root身份执行,否则将无效;因此,由root所有启动脚本是最容易执行此操作的地方。如果您执行此操作,还应该在调用postgres
之前在启动脚本中设置以下环境变量
export PG_OOM_ADJUST_FILE=/proc/self/oom_score_adj export PG_OOM_ADJUST_VALUE=0
这些设置将导致postmaster子进程以零的正常OOM分数调整运行,以便OOM killer在需要时仍然可以将它们作为目标。如果希望子进程以其他OOM分数调整运行,可以使用其他PG_OOM_ADJUST_VALUE
值。(也可以省略PG_OOM_ADJUST_VALUE
,在这种情况下,它默认为零。)如果您没有设置PG_OOM_ADJUST_FILE
,子进程将以与postmaster相同的OOM分数调整运行,这是不明智的,因为关键在于确保postmaster具有优先设置。
当使用大块连续内存块时,使用大页面可降低开销,如 PostgreSQL 所做,尤其是在使用 shared_buffers 的大值时。要在 PostgreSQL 中使用该功能,需要带 CONFIG_HUGETLBFS=y
和 CONFIG_HUGETLB_PAGE=y
的内核。还必须将操作系统配置为提供足够所需大小的大页面。运行时计算的参数 shared_memory_size_in_huge_pages 报告了所需的大页面数。使用如下的 postgres
命令,在启动服务器前可查看此参数
$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,从而调整 shared_memory_size_in_huge_pages
计算的页面数。虽然在此示例中至少需要 3170
个大页面,但如果机器上的其他程序也需要大页面,可以设置一个更大的值。我们可以使用以下方式来设置
# sysctl -w vm.nr_hugepages=3170
别忘了将此设置添加到 /etc/sysctl.conf
,以便在重新启动后重新应用它。对于非默认大页面大小,我们可改用
# echo 3170 > /sys/kernel/mm/hugepages/hugepages-2048kB/nr_hugepages
还可使用 kernel 参数(如 hugepagesz=2M hugepages=3170
)在启动时提供这些设置。
有时,kernel 无法立即分配所需数量的大页面,原因是碎片化,因此可能需要重复该命令或重新启动。(在重新启动后,大部分机器内存都应可转换成大页面。)要验证给定大小的大页面分配情况,请使用
$ 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。