深入的Postgres思想:Linux刺客

2021-02-10 03:46:03

如果您在生产中运行Linux的时间很长,则可能会遇到" Linux Assassin"。也就是说,OOM(内存不足)杀手。当Linux检测到系统使用了过多的内存时,它将识别终止进程,并暗杀它们。 OOM杀手在确保系统不会用完内存方面起着崇高的作用,但这会导致意想不到的后果。

多年来,PostgreSQL社区就如何设置Linux系统以使Linux刺客远离PostgreSQL进程提出了建议,我将在下面进行描述。这些建议从裸机转移到虚拟机,但是容器和Kubernetes呢?

以下是我对Linux Assassin如何与容器和Kubernetes结合工作的实验和观察结果的解释,以及使它远离环境中的PostgreSQL集群的方法。

关于该主题的第一个PostgreSQL社区邮件列表线程大约在2003年,并且第一次提交大约在同一时间进行。自那时以来,建议略过Linux OOM Killer的确切方法已经稍有更改,但是它一直是并且目前仍然是通过设置vm.overcommit_memory = 2来避免内存过量使用,即最近几年。

避免内存过量使用意味着当PostgreSQL后端进程请求内存并且无法满足该请求时,内核将返回一个错误,PostgreSQL将对此进行适当处理。因此,尽管有问题的客户端随后从PostgreSQL接收到错误,但重要的是,客户端连接不会被杀死,其他任何PostgreSQL子进程也不会被杀死(请参见下文)。

此外,或者在不可能的情况下,该指南指定为父级" postmaster"更改oom_score_adj = -1000。通过特权启动机制(例如服务脚本或systemd单元文件)启动进程,并通过在子进程启动过程中读取的两个环境变量使所有子进程的oom_score_adj = 0。这可以确保如果OOM杀手需要获得一个或多个进程,则将保护邮局主管,最有可能被杀的候选人将是客户端后端。这样,可以将损害最小化。

为了了解oom_score_adj的功能,更详细地介绍OOM Killer是值得的。但是,真正的细节是复杂的,具有悠久的肮脏历史(肯定不是全部包含在内,但有关OOM杀手的文章的很好总结请参见LWN),因此此描述仍然非常肤浅。

在主机OS级别上,当系统内存不足时,OOM杀手将加入。简而言之,它将确定哪个进程的oom_score值最高,并使用SIGKILL信号将其杀死。某个进程的oom_score的值本质上是"该进程占用的主机内存的百分比"乘以10(让我们调用记忆分数),再加上oom_score_adj。

oom_score_adj的值可以设置为-1000到+1000之间的任何值。如上所述,请注意oom_score_adj = -1000是一个神奇的值,因为OOM杀手不会使用此设置来获得进程。

将内核琐事的这两位组合在一起,会导致oom_score的值在0到2000之间。例如,使用oom_score_adj = -998的进程使用了​​100%的主机内存(即&samp; score&#34 of 1000)具有oom_score等于2(1000 + -998),并且使用oom_score_adj = 500的进程使用了​​50%的主机内存(即,内存得分为500)的oom_score等于1000(500 + 500)。显然,这意味着一个消耗大量系统内存且oom_score_adj较高的进程位于或接近OOM杀手列表的顶部。

OOM杀手的作用在CGroup级别几乎相同,除了一些小的但重要的区别。

首先,当cgroup进程消耗的内存总和超过分配的cgroup内存限制时,将触发OOM杀手。在容器中运行Shell时,前者可以从/sys/fs/cgroup/memory/memory.usage_in_bytes中读取,后者可以从/sys/fs/cgroup/memory/memory/limit.in_bytes中读取。

其次,仅针对有问题的cgroup中的进程。但是具有最高oom_score的cgroup进程仍然是第一个使用的进程。

丢失的已提交事务:如果邮局主管(或在HA设置中控制Patroni进程)被杀死,并且复制是异步的(通常是这种情况),则当数据库集群发生故障时,已经在主数据库上提交的事务可能会完全丢失到副本。

丢失的活动连接:如果客户端后端进程被杀死,则邮局主管会认为共享内存可能已损坏,结果,它会杀死所有活动的数据库连接并进入崩溃恢复(自上一个检查点以来在事务日志中向前滚动)。

丢失的正在进行中的事务:杀死客户端后端进程后,已启动但尚未提交的事务将完全丢失。那时,客户端应用程序是运行中数据的唯一来源。

停机时间:PostgreSQL集群只有一个可写的主节点。如果出现故障,则至少会导致一些应用程序停机时间。

重置统计信息:崩溃恢复过程导致重置收集的统计信息(即归零)。这会影响诸如自动真空和自动分析之类的维护操作,继而会导致性能下降,或者在严重的情况下(例如由于磁盘空间不足而导致)中断。它还会影响在PostgreSQL上收集的监视数据的完整性,从而可能导致丢失警报。

在Kubernetes下运行PostgreSQL时,有一些与OOM杀手有关的问题:

Kubernetes主动设置vm.overcommit_memory = 1。这导致混杂的过量使用行为,并且与PostgreSQL最佳实践形成直接对比。这大大增加了OOM Killer收割有必要的可能性。

更糟糕的是,即使主机节点没有任何内存压力,也可能发生OOM终止。当cgroup(pod)的内存使用量超过其内存限制时,OOM杀手将获得cgroup中的一个或多个进程。

oom_score_adj值几乎完全不受PostgreSQL pod的控制,从而避免了遵循上述已建立的最佳实践的任何尝试。为此,我在Kubernetes github上创建了一个问题,但不幸的是它并没有吸引太多人。

Kubernetes默认设置为强制禁用交换。这直接与Linux内核开发人员的建议相反。例如,请参阅克里斯·唐纳(Chris Down)的优秀博客,了解为何不应禁用交换功能。特别是,当我从I / O占主导地位的工作负载切换到匿名内存密集型工作负载时,我观察到了内存受限cgroup中的功能异常行为。在讨论交换需求的这篇文章中可以看到其他遇到此问题的人的证据:

"内存cgroup,缓冲区缓存和OOM杀手也存在一个已知问题。如果您不使用cgroups且内存不足,则内核可以开始刷新脏的和干净的缓存,回收其中的一些内存,然后将其分配给需要的人。对于cgroups,由于某种原因,没有用于清理缓存的回收逻辑,并且内核更喜欢触发OOM杀手,然后OOM杀手摆脱了一些有用的过程。

Kubernetes github上还有一个针对此问题的问题,三年后仍在辩论中。

Kubernetes定义了3个服务质量(QoS)级别。它们不仅影响OOM杀手行为,而且对于本文而言,仅解决OOM杀手行为。级别是:

保证:对于容器中的所有容器,内存限制和请求均已设置且相等。

可突发:没有内存限制,但是对容器中的所有容器都有内存请求。

使用保证的QoS荚,oom_score_adj的值几乎是所需的; PostgreSQL在主机内存不足的情况下可能没有针对性。但是cgroup会在超过内存限制时杀死它。行为是不可取的。相关特征如下:

已记录的环境变量能够为邮局主管孩子成功重置oom_score_adj = 0,这也很好。

使用Burstable QoS Pod,可以将oom_score_adj的值设置得很高,并且具有令人惊讶的语义(请求的较小内存导致oom_score_adj更高)。如果/当主机节点处于内存压力下时,这使PostgreSQL成为主要目标。如果主机节点具有vm.overcommit_memory = 2,则这种情况将是可以容忍的,因为即使不是不可能,OOM杀死也不太可能。但是,如上所述,Kubernetes建议/设置vm.overcommit_memory = 1。相关特征如下:

oom_score_adj =(1000-10 *(请求的可用内存百分比))(这是一个略微的简化-强制最小值为2,最大值为999):这导致非常小的豆荚获得更高的分数调整值比很大的一个例如。一请求1%可用内存的pod将获得oom_score_adj = 990;而请求50%可用内存的一个pod将获得oom_score_adj = 500。这反过来意味着,如果较小的Pod处于空闲状态,则基本上不使用任何资源,例如可能具有oom_score =(0.1 * 10)+ 990 = 991;而较大的Pod可能正在使用40%的系统内存并获得oom_score = { 40 * 10)+ 500 = 900。

理想的解决方案是,如果内核将提供一种机制,以允许等效于vm.overcommit_memory = 2的行为,除了在cgroup级别起作用。换句话说,允许在cgroup中发出过多内存请求的进程接收到内存不足的消息。错误,而不是使用OOM Killer强制执行约束。这将是理想的解决方案,因为大多数用户似乎都希望使用保证的QoS Pod,但是当前通过OOM杀手实施内存限制是一个问题。

Kubernetes的另一个期望的变化是提供一种机制,以允许某些Pod(具有适当的RBAC控制)可以覆盖当前基于QoS启发式设置的oom_score_adj值。这将允许PostgreSQL Pod主动将oom_score_adj设置为推荐值。因此PostgreSQL postmaster进程可以具有推荐的oom_score_adj = -1000,PostgreSQL子进程可以设置为oom_score_adj = 0,并且Burstable QoS Pod将是一个更合理的选择。

最后,在启用了交换功能的情况下运行Kubernetes绝非易事。进行了一些挖掘,但我尚未亲自对其进行测试,但是在前面讨论的很长的GitHub问题中提到了一种解决方法。

在典型的生产场景中,上面描述的OOM杀手级语义可能永远不会成为问题。 本质上,如果您的Pod大小合适,希望基于测试和经验,并且不允许执行任意SQL,则OOM杀手er可能永远不会出击。 在开发系统上,OOM杀手级操作可能更可能发生,但可能不经常发生而成为真正的问题。 但是,如果OOM杀手在您的环境中造成了困扰或困扰,请使用以下建议的解决方法。 确保您的Pod是保证的QoS(内存限制和内存请求大小设置相同)。 监视cgroup内存使用情况,并以相当保守的阈值发出警报,例如 内存限制设置的50%。 确保您的Pod是Burstable QoS(有内存请求,但没有内存限制)。 监控Kubernetes主机内存使用情况并在相当保守的阈值上发出警报,例如 物理内存的50%。

接受某些OOM Killer事件将发生的事实。 监视历史将告知统计可能性和预期的发生频率。 根据实际的工作负载和使用模式,OOM杀手事件。 频率可以等于或几乎等于零。 Crunchy Data正在与PostgreSQL,Kubernetes和Linux Kernel社区积极合作,以改善OOM杀手的行为。 一些可能的长期解决方案包括: 可怕的Linux刺客已经存在了很多年,并且没有任何即将退休的迹象。 但是您可以通过仔细的计划,配置,监视和警报来避免成为目标。 容器和Kubernetes的世界带来了新的挑战,但是对勤奋的系统管理的要求却保持不变。