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 提供了概述。但是,设置它们的方法各不相同。下面给出了一些平台的建议。
表 19.1. System V IPC 参数
名称 | 说明 | 运行一个 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 + 5) / 16) ,加上其他应用程序的空间 |
SEMMNS |
系统范围内的最大信号量数 | ceil((max_connections + autovacuum_max_workers + max_wal_senders + max_worker_processes + 5) / 16) * 17 ,加上其他应用程序的空间 |
SEMMSL |
每个集合的最大信号量数 | 至少 17 |
SEMMAP |
信号量映射中的条目数 | 参见文本 |
SEMVMX |
信号量的最大值 | 至少 1000(默认值通常为 32767;除非必要,否则不要更改) |
PostgreSQL 为服务器的每个副本需要一些 System V 共享内存(通常为 48 字节,在 64 位平台上)。在大多数现代操作系统上,可以轻松分配此数量。但是,如果您正在运行服务器的多个副本,或者您明确配置服务器以使用大量 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)和允许的后台进程(max_worker_processes)使用一个信号量,以 16 个为一组。每组还将包含一个第 17 个信号量,其中包含一个 “魔术数字”,以检测与其他应用程序使用的信号量集的冲突。系统中信号量的最大数量由 SEMMNS
设置,因此它必须至少与 max_connections
加上 autovacuum_max_workers
加上 max_wal_senders
,再加上 max_worker_processes
,再加上每个 16 个允许的连接加工作进程的额外一个一样高(请参阅 表 19.1 中的公式)。参数 SEMMNI
确定系统上一次可以存在的信号量集数量的限制。因此,此参数必须至少为 ceil((max_connections + autovacuum_max_workers + max_wal_senders + max_worker_processes + 5) / 16)
。降低允许的连接数是针对故障的临时解决方法,这些故障通常会以令人困惑的措辞 “设备上没有剩余空间” 从函数 semget
中发出。
在某些情况下,可能还需要将 SEMMAP
增加到至少 SEMMNS
的数量级。如果系统具有此参数(许多系统没有),它将定义信号量资源映射的大小,其中每个连续的可用信号量块都需要一个条目。释放信号量集时,它将被添加到与释放块相邻的现有条目中,或者在新的映射条目下注册。如果映射已满,则释放的信号量将丢失(直到重新启动)。信号量空间的碎片化可能会随着时间的推移导致可用信号量少于应有的数量。
与 “信号量撤消”相关的其他各种设置,例如 SEMMNU
和 SEMUME
,不会影响 PostgreSQL。
在使用 POSIX 信号量时,所需的信号量数量与 System V 相同,即每个允许的连接 (max_connections)、允许的自动清理工作进程 (autovacuum_max_workers) 和允许的后台进程 (max_worker_processes) 一个信号量。在首选此选项的平台上,POSIX 信号量数量没有特定的内核限制。
对于 SHMMAX
等参数,不应需要进行任何特殊配置,因为这似乎已配置为允许将所有内存用作共享内存。这是其他数据库(例如 DB/2)常用的配置类型。
但是,可能需要修改 /etc/security/limits
中的全局 ulimit
信息,因为文件大小 (fsize
) 和文件数量 (nofiles
) 的默认硬限制可能太低。
默认共享内存设置通常足够好,除非你将 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
,即使在这种情况下,也仅在附带较低默认值的较旧内核版本上才会发生。此平台上不使用 System V 信号量。
可以通过 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 页面为单位进行测量。
可以使用 sysctl 随时更改除 SHMMNI
之外的所有值。但最好还是通过 /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
中的 SYS_UID_MAX
设置确定的。
打包和部署脚本应谨慎使用 useradd -r
、adduser --system
或同等命令将 postgres
用户创建为系统用户。
或者,如果用户帐户创建不正确或无法更改,建议设置
RemoveIPC=no
在 /etc/systemd/logind.conf
或其他适当的配置文件中。
必须确保至少满足这两个条件中的一个,否则 PostgreSQL 服务器将非常不可靠。
类 Unix 操作系统强制执行各种资源限制,这些限制可能会干扰您的 PostgreSQL 服务器的操作。特别重要的是对每个用户进程数、每个进程打开的文件数以及每个进程可用的内存量进行限制。每个限制都有一个 “硬” 限制和一个 “软” 限制。软限制是实际计数的限制,但用户可以将其更改为硬限制。硬限制只能由 root 用户更改。系统调用 setrlimit
负责设置这些参数。Shell 的内置命令 ulimit
(Bourne shell)或 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 杀手,但它会大大降低可能性,从而导致更健壮的系统行为。这是通过 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 杀手针对。执行此操作的最简单方法是在调用 postgres
之前在 PostgreSQL 启动脚本中执行
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 杀手仍然可以在需要时针对它们。如果您希望子进程以其他 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
的内核。您还必须配置操作系统以提供足够所需大小的大页。要确定所需的大页数量,请使用 postgres
命令查看 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,以调整 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
还可以使用内核参数(如 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。