PostgreSQL某些时候会耗尽操作系统的各种资源限制,当同一个系统上运行着多个拷贝的服务器或在一个非常大的安装中时尤其如此。本节解释了PostgreSQL使用的内核资源以及你可以采取的用于解决内核资源消耗相关问题的步骤。
PostgreSQL要求操作系统提供进程间通信(IPC)特性,特别是共享内存和信号量。Unix系的系统通常会提供“System V” IPC、“POSIX” IPC或者两者都提供。Windows对这些特性有自己的实现,在这里不讨论它们。
完全缺少这些功能通常表现为服务器启动时的Illegal system call错误。这种情况下,除了重新配置内核之外别无选择。PostgreSQL没有它们就不能工作。 不过,在现代操作系统中这种情况是罕见的。
在启动服务器时,PostgreSQL通常会分配非常少量的System V共享内存以及多得多的POSIX(mmap
)共享内存。此外,还有数量可观的信号量(可以是System V或者POSIX风格)在服务器启动时被创建。当前,在Linux和FreeBSD系统上使用POSIX信号量,而在其他平台上使用System V信号量。
在PostgreSQL 9.3 之前,只使用System V共享内存,因此启动服务器所要求的 System V 共享内存的量要更大。如果你在运行着一个老版本的服务器,请参考该服务器版本的文档。
System V IPC特性通常受到系统范围的分配限制。当PostgreSQL超过这些限制之一时,服务器将拒绝启动并且留下一条有指示性的错误消息描述该错误以及如何处理的信息(另见Section 18.3.1)。相关的内核参数在不同的系统中的命名是一致的,Table 18.1给出了一份综述。不过,设置它们的方法是多变的。下面给出了对一些平台的建议。
Table 18.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_worker_processes + 5) / 16) |
SEMMNS | 系统范围内的最大信号量数目 | ceil((max_connections + autovacuum_max_workers + max_worker_processes + 5) / 16) * 17 外加其他应用的空间 |
SEMMSL | 每个集合中信号量的最大数目 | 至少 17 |
SEMMAP | 信号量映射中的项数 | 见文本 |
SEMVMX | 信号量的最大值 | 至少 1000 (默认值常常是 32767,如非必要不要更改) |
PostgreSQL要求少量字节的 System V 共享内存(在 64 位平台上通常是 48 字节)用于每一个服务器拷贝。在大多数现代操作系统上,这个量很容易得到。但是,如果你运行了很多个服务器拷贝,或者其他应用也在使用 System V 共享内存,可能需要增加SHMALL
,它是系统范围内 System V 共享内存的总量。注意在很多系统上SHMALL
是以页面而不是字节来度量。
不太可能出问题的是共享内存段的最小尺寸(SHMMIN
),对PostgreSQL来说应该最多大约是 32 字节(通常只是1)。而系统范围(SHMMNI
)或每个进程(SHMSEG
)的最大共享内存段数目不太可能会导致问题,除非你的系统把它们设成零。
在使用System V信号量时,PostgreSQL对每个允许的连接(max_connections)、每个允许的自动清理工作者进程(autovacuum_max_workers)和每个允许的后台进程(max_worker_processes)使用一个信号量, 以16个为一个集合。每个这种集合还包含第 17 个信号量, 其中存储一个“magic number”,以检测和其它应用使用的信号量集合的冲突。 系统里的最大信号量数目是由SEMMNS
设置的, 因此这个值必须至少和max_connections
加autovacuum_max_workers
再加max_worker_processes
一样大, 并且每 16 个连接外加工作者还要另外加一个(见Table 18.1中的公式)。参数SEMMNI
决定系统中同一时刻可以存在的信号量集合的数目限制。因此这个参数必须至少为ceil((max_connections + autovacuum_max_workers + max_worker_processes + 5) / 16)
。降低允许的连接数目是一种临时的绕开失败(来自函数semget
)的方法,通常使用让人混乱的措辞“No space left on device”。
在某些情况下可能还有必要增大SEMMAP
,使之至少与SEMMNS
相近。 这个参数定义信号量资源映射的尺寸,在其中每个连续的可用信号量块都需要一项。 每当一个信号量集合被释放,那么它要么会被加入到该与被释放块相邻的一个现有项,或者它会被注册在一个新映射项中。如果映射被填满,被释放的信号量将丢失(直到重启)。因此信号量空间的碎片时间长了会导致可用的信号量比应有的信号量少。
各种其他的与“semaphore undo”相关的设置(例如SEMMNU
和SEMUME
)不影响PostgreSQL。
在使用POSIX信号量时,需要的信号量数目与System V相同,即每个允许的连接(max_connections)、每个允许的autovacuum工作者进程(autovacuum_max_workers)以及每个允许的后台进程(max_worker_processes)都需要一个信号量。在更喜欢这个选项的平台上,在POSIX信号量的数量上没有特定的内核限制。
至少到版本 5.1 为止,不再需要对这些参数(例如SHMMAX
)做任何特殊的配置,这看起来就像是被配置成允许所有内存都被用作共享内存。这是一种通常被用于其他数据库(DB/2)的配置。
但是,可能需要修改/etc/security/limits
中的全局ulimit
信息,默认的文件尺寸硬限制(fsize
)和文件数量(nofiles
)可能太低。
可以使用sysctl
或loader
接口来改变默认配置。下列参数可以使用sysctl
设置:
#
sysctl kern.ipc.shmall=32768
#
sysctl kern.ipc.shmmax=134217728
#
sysctl kern.ipc.semmap=256
要让这些设置在重启之后也保持,请修改/etc/sysctl.conf
。
对于sysctl
所关心的来说这些信号量相关的设置都是只读的,但是可以在/boot/loader.conf
中设置:
kern.ipc.semmni=256 kern.ipc.semmns=512 kern.ipc.semmnu=256
修改这些值后需要一次重启让新设置能生效。
(注意:FreeBSD 不使用SEMMAP
。较老的版本
会接受并且忽略kern.ipc.semmap
的设置,而较新的
版本会完全拒绝它)。
你可能也希望你的内核将共享内存锁定在 RAM 中并且防止它被换页到交换分区。这可以使用sysctl
的设置
kern.ipc.shm_use_phys
来完成。
如果通过启用sysctl的security.jail.sysvipc_allowed
运行在 FreeBSD jail 中,运行在不同 jail 中的postmaster应当被不同的操作系统用户运行。这可以提高安全性,因为它阻止非 root 用户干涉不同 jail 中的共享内存或信号量,并且它允许 PostgreSQL IPC 清理代码正确地工作(在 FreeBSD 6.0 及其后的版本中,IPC 清理代码不能正确地检测到其他 jail 中的进程,也不能阻止不同 jail 中的 postmaster 运行在相同的端口)。
FreeBSD 4.0 之前的版本的工作与OpenBSD相似(见下文)。
在NetBSD 5.0 及其后的版本中,IPC 参数可以使用sysctl
调整。例如:
$
sysctl -w kern.ipc.shmmax=16777216
要使这些设置在重启后保持,请修改/etc/sysctl.conf
。
你可能也希望你的内核将共享内存锁定在 RAM 中并且防止它被换页到交换分区。这可以使用sysctl
的设置
kern.ipc.shm_use_phys
来完成。
NetBSD 5.0 以前的版本的工作与OpenBSD相似(见下文),除了那些参数应该用关键词options
设置而不是option
。
当内核被编译时,选项SYSVSHM
和SYSVSEM
需要被启用(它们默认值)。共享内存的最大尺寸由选项SHMMAXPGS
(以页面计)决定。下面展示了一个如何设置多个参数的例子:
option SYSVSHM option SHMMAXPGS=4096 option SHMSEG=256 option SYSVSEM option SEMMNI=256 option SEMMNS=512 option SEMMNU=256
默认的设置可以满足正常的安装。在HP-UX 10 上,SEMMNS
的出厂默认值是 128,这可能对大型数据库站点太低。
IPC参数可以在System Administration Manager(SAM)中被设置。当你完成时选择 。
→ 下的
默认的最大段尺寸是 32 MB,并且默认的最大总尺寸是 2097152 个页面。一个页面几乎总是 4096 字节,除了在使用少见“huge pages”的内核配置中(使用getconf PAGE_SIZE
来验证)。
共享内存尺寸设置可以通过sysctl
接口来更改。例如,要允许 16 GB:
$
sysctl -w kernel.shmmax=17179869184
$
sysctl -w kernel.shmall=4194304
另外在重启之间这些设置可以被保存在文件/etc/sysctl.conf
中。我们强烈推荐这样做。
古老的发型可能没有sysctl
程序,但是可以通过操纵/proc
文件系统来得到等效的更改:
$
echo 17179869184 >/proc/sys/kernel/shmmax
$
echo 4194304 >/proc/sys/kernel/shmall
剩下的默认值都被设置得很宽大,并且通常不需要更改。
在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
中设置,否则值将会被忽略。
注意近期的macOS版本会忽略把SHMMAX
设置成非 4096 倍数值的尝试。
在这个平台上,SHMALL
以 4kB 的页面度量。
在更老的macOS版本中,你将需要重启来让共享内存参数的更改生效。到了 10.5,可以使用sysctl随时改变除了SHMMNI
之外的所有参数。但是最好还是通过/etc/sysctl.conf
来设置你喜欢的值,这样重启之后这些值还能被保持。
只有在macOS 10.3.9 及以后的版本中才遵循/etc/sysctl.conf
文件。如果你正在使用 10.3.x 之前的发布,你必须编辑文件/etc/rc
并且在下列命令中改变值:
sysctl -w kern.sysv.shmmax sysctl -w kern.sysv.shmmin sysctl -w kern.sysv.shmmni sysctl -w kern.sysv.shmseg sysctl -w kern.sysv.shmall
注意/etc/rc
通常会被macOS的系统更新所覆盖,因此你应该在每次更新之后重做这些编辑。
在macOS 10.2 及更早的版本中,应该在文件/System/Library/StartupItems/SystemTuning/SystemTuning
中编辑这些命令。
相似的设置可以在/etc/system
中更改,例如:
set shmsys:shminfo_shmmax=0x2000000 set shmsys:shminfo_shmmin=1 set shmsys:shminfo_shmmni=256 set shmsys:shminfo_shmseg=256 set semsys:seminfo_semmap=256 set semsys:seminfo_semmni=512 set semsys:seminfo_semmns=512 set semsys:seminfo_semmsl=32
你需要重启来让更改生效。关于更老版本的 Solaris 下的共享内存的信息请见http://sunsite.uakom.sk/sunworldonline/swol-09-1997/swol-09-insidesolaris.html。
在 Solaris 10 及以后的版本以及 OpenSolaris 中,默认的共享内存和信号量设置已经足以应付大部分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
的信息请见System Administrator's Guide中的 "Chapter2: Projects and Tasks"。
如果使用的是systemd,必须要当心IPC资源(共享内存和信号量)不会被操作系统过早的移除。在从源码安装PostgreSQL时,这一点特别重要。PostgreSQL发行包的用户不太可能受到影响,因为postgres
用户是作为系统用户创建的。
logind.conf
中的设置RemoveIPC
控制着当一个用户完全登出时是否移除IPC对象。系统用户可以豁免。这个设置在现有的systemd上默认为打开,但有些操作系统发行版把它默认为关闭。
当这个设置为on时,观察到的一种典型的效果是PostgreSQL使用的信号量对象会在明显是随机时刻被移除,导致服务器崩溃并且给出这样的日志消息:
LOG: semctl(1234567890, 0, IPC_RMID, ...) failed: Invalid argument
systemd会略有差别地对待不同类型的IPC对象(共享内存 vs. 信号量,System V vs. POSIX),因此人们可能会观察到一些IPC资源没有按与其他相同的方式被移除。但是不建议依赖于这种微小的差别。
“用户登出”可能会作为维护任务的一部分发生,或者是当一个管理员以postgres
用户或者其他类似用户登入后手工执行,因此通常很难阻止这种事情发生。
什么是“系统用户”是在systemd编译时从/etc/login.defs
的SYS_UID_MAX
设置确定的。
打包和部署脚本应该谨慎地通过使用useradd -r
、adduser --system
或者等效的命令将postgres
用户创建为系统用户。
另一种选择是,如果用户账户创建得不正确或者无法被更改,推荐在/etc/systemd/logind.conf
或者另一个合适的配置文件中做这样的设置:
RemoveIPC=no
至少要确保这两件事情之一,否则PostgreSQL服务器将会非常不可靠。
Unix类操作系统强制了许多种资源限制,这些限制可能干扰你的PostgreSQL服务器的操作。尤其重要的是对每个用户的进程数目的限制、每个进程打开文件数目的限制以及每个进程可用的内存的限制。这些限制中每个都有一个“硬”限制和一个“软”限制。实际使用的是软限制,但用户可以自己修改成最大为硬限制的数目。而硬限制只能由root用户修改。系统调用setrlimit
负责设置这些参数。 shell的内建命令ulimit
(Bourne shells)或limit
(csh)被用来从命令行控制资源限制。 在 BSD 衍生的系统上,/etc/login.conf
文件控制在登录期间设置的各种资源限制。详见操作系统文档。相关的参数是maxproc
、openfiles
和datasize
。例如:
default:\ ... :datasize-cur=256M:\ :maxproc-cur=256:\ :openfiles-cur=256:\ ...
(-cur
是软限制。增加-max
可设置硬限制)。
内核也可以在某些资源上有系统范围的限制。
在Linux上,/proc/sys/fs/file-max
决定内核可以支持打开的最大文件数。你可以通过往该文件写入一个不同的数值修改此值, 或者通过在/etc/sysctl.conf
中增加一个赋值来修改。 每个进程的最大打开文件数限制是在编译内核的时候固定的;更多信息请见/usr/src/linux/Documentation/proc.txt
。
PostgreSQL服务器为每个连接都使用一个进程, 所以你应该至少和允许的连接同样多的进程,再加上系统其它部分所需要的进程数目。 通常这个并不是什么问题,但如果你在一台机器上运行多个服务器,资源使用可能就会紧张。
打开文件的出厂默认限制通常设置为“socially friendly”的值, 它允许许多用户在一台机器上共存,而不会导致不成比例的系统资源使用。 如果你在一台机器上运行许多服务器,这也许就是你想要的,但是在专门的服务器上, 你可能需要提高这个限制。
在另一方面,一些系统允许独立的进程打开非常多的文件;如果不止几个进程这么干,那系统范围的限制就很容易被超过。如果你发现这样的现像, 并且不想修改系统范围的限制,你就可以设置PostgreSQL的 max_files_per_process配置参数来限制打开文件数的消耗。
在 Linux 2.4 及其后的版本中,默认的虚拟内存行为对PostgreSQL不是最优的。由于内核实现内存过量使用的方法,如果PostgreSQL或其它进程的内存要求导致系统用光虚拟内存,那么内核可能会终止PostgreSQL的 postmaster 进程(主服务器进程)。
如果发生了这样的事情,你会看到像下面这样的内核消息(参考你的系统文档和配置,看看在哪里能看到这样的消息):
Out of Memory: Killed process 12345 (postgres).
这表明postgres
进程因为内存压力而被终止了。尽管现有的数据库连接将继续正常运转,但是新的连接将无法被接受。要想恢复,PostgreSQL应该被重启。
一种避免这个问题的方法是在一台你确信其它进程不会耗尽内存的机器上运行PostgreSQL。 如果内存资源紧张,增加操作系统的交换空间可以帮助避免这个问题,因为内存不足(OOM)杀手(即终止进程这种行为)只有当物理内存和交换空间都被用尽时才会被调用。
如果PostgreSQL本身是导致系统内存耗尽的原因,你可以通过改变你的配置来避免该问题。在某些情况中,降低内存相关的配置参数可能有所帮助,特别是shared_buffers
和work_mem
两个参数。在其他情况中,允许太多连接到数据库服务器本身也可能导致该问题。在很多情况下,最好减小max_connections
并且转而利用外部连接池软件。
在 Linux 2.6 及其后的版本中,可以修改内核的行为,这样它将不会“过量使用”内存。尽管此设置不会阻止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 score adjustment值设置为-1000
,从而保证它不会成为 OOM 杀手的目标。 这样做最简单的方法是在 postmaster 的启动脚本中执行
echo -1000 > /proc/self/oom_score_adj
并且要在调用 postmaster 之前执行。请注意这个动作必须以 root 完成,否则它将不会产生效果。所以一个被 root 拥有的启动脚本是放置这个动作最容易的地方。如果这样做,你还应该在调用 postmaster 之前在启动脚本中设置这些环境变量:
export PG_OOM_ADJUST_FILE=/proc/self/oom_score_adj export PG_OOM_ADJUST_VALUE=0
这些设置将导致 postmaster 子进程使用普通的值为零的 OOM score adjustment 运行,所以 OOM 杀手仍能在需要时把它们作为目标。如果你想要子进程用某些其他 OOM score adjustment 值运行,可以为PG_OOM_ADJUST_VALUE
使用其他的值(PG_OOM_ADJUST_VALUE
也能被省略,那时它会被默认为零)。如果你没有设置PG_OOM_ADJUST_FILE
,子进程将使用和 postmaster 相同的 OOM score adjustment 运行,这是不明智的,因为重点是确保 postmaster 具有优先的设置。
更老的 Linux 内核不提供/proc/self/oom_score_adj
,但是可能有一个具有相同功能的早期版本,它被称为/proc/self/oom_adj
。这种方式工作起来完全相同,除了禁用值是-17
而不是-1000
。
有些厂商的 Linux 2.4 内核被报告有着 2.6 过量使用sysctl
参数的早期版本。不过,在没有相关代码的 2.4 内核里设置vm.overcommit_memory
为 2 将会让事情更糟。我们推荐你检查一下实际的内核源代码(见文件mm/mmap.c
中的vm_enough_memory
函数),验证一下这个是在你的内核中是被支持的, 然后再在 2.4 安装中使用它。文档文件overcommit-accounting
的存在不能当作是这个特性存在的证明。如果有疑问,请咨询一位内核专家或你的内核厂商。
使用大页面能降低使用大量连续内存块时的开销,PostgreSQL就会这样,尤其是在使用比较大的shared_buffers值时。要在PostgreSQL中使用这一特性,你需要一个CONFIG_HUGETLBFS=y
和CONFIG_HUGETLB_PAGE=y
的内核。你还将不得不调整内核设置vm.nr_hugepages
。要估计所需的大页面数量,可以在不启用大页面时启动PostgreSQL并且检查postmaster的匿名共享内存段尺寸以及系统的大页面尺寸(使用/proc
文件系统)。看起来可能像这样:
$head -1 $PGDATA/postmaster.pid
4170 $pmap 4170 | awk '/rw-s/ && /zero/ {print $2}'
6490428K $grep ^Hugepagesize /proc/meminfo
Hugepagesize: 2048 kB
6490428
/ 2048
的值大约是3169.154
,因此在这个例子中我们需要至少3170
个大页面,我们可以用下面的命令设置:
$ sysctl -w vm.nr_hugepages=3170
如果该机器上的其他程序也需要大页面,更大的设置会合适些。不要忘记把这个设置加在/etc/sysctl.conf
,这样在重启后才会继续生效。
有时内核无法立刻分配所需要的大页面,因此可能有必要重复该命令或者重启(在重启后,机器内存中的大部分应该都可以转换成大页面)。要验证大页面分配情况,可以用:
$ grep Huge /proc/meminfo
还可能有必要给数据服务器的操作系统用户权限来使用大页面(通过用sysctl设置vm.hugetlb_shm_group
来实现),并且给予权限来使用ulimit -l
锁定内存。
PostgreSQL中大页面的默认行为是在可能的时候使用它们但在失败的时候回退到普通页面。要强制使用大页面,你可以在postgresql.conf
中把huge_pages设置为on
。注意通过这个设置,PostgreSQL会在没有足够的大页面可用时启动失败。
Linux大页面特性的详细描述可见https://www.kernel.org/doc/Documentation/vm/hugetlbpage.txt.