为掉期辩护:常见的误解

2020-06-08 23:45:16

拥有交换空间是运行良好的系统的一个相当重要的部分,没有它,正常的内存管理将更难实现。

交换通常不是为了获得紧急内存,而是让内存回收变得平等和高效。事实上,使用它作为紧急记忆通常是有害的。

禁用交换并不能防止磁盘I/O在内存争用下成为问题,它只是将磁盘I/O的颠簸从匿名页面转移到文件页面。这不仅会降低效率,因为我们可供选择回收的页数池更小,而且还可能在一开始就导致进入这种高争用状态。

在4.0之前的内核交换器有很多陷阱,并且由于它具有交换页面的主权性,导致了很多人对交换的负面看法。在内核>;4.0上,情况要好得多。

在SSD上,换出匿名页和回收文件页在性能/延迟方面基本上是等效的。在较旧的旋转磁盘上,由于随机读取,交换读取速度较慢,因此设置较低的vm.swappiness值是有意义的(请继续阅读以了解有关vm.swappness的更多信息)。

禁用掉期并不能防止近乎OOM的病态行为,尽管拥有掉期可能会延长它是真的。无论系统全局OOM杀手是在有没有交换的情况下调用的,还是迟早会调用的,结果都是一样的:您会使系统处于不可预测的状态。没有交换并不能避免这一点。

在cgroup v2中使用memory.low和Friends可以在内存紧张和防止抖动的情况下实现更好的交换行为。

作为我改进内核内存管理和cgroupv2的工作的一部分,我一直在与许多工程师讨论对内存管理的态度,特别是关于压力下的应用程序行为和用于内存管理的操作系统启发式。

这些讨论中反复出现的一个话题是互换。交换是一个争论不休、知之甚少的话题,即使是那些从事Linux多年工作的人也是如此。许多人认为它是无用的或有害的:它是记忆匮乏的时代的遗物,而磁盘是提供急需的分页空间的必要邪恶。最近几年,我仍然看到人们在相对频繁地谈论这一说法,我已经与同事、朋友和行业同行进行了多次讨论,以帮助他们理解为什么交换在可用物理内存比过去多得多的现代计算机上仍然是一个有用的概念。

对于交换的目的也有很多误解--许多人只是把它看作是一种在紧急情况下使用的缓慢的额外内存,但并不理解它在正常加载期间对整个操作系统的健康运行有何贡献。

我们很多人都听说过大多数关于内存的常见比喻:Linux使用如此多的内存,交换内存应该是物理内存大小的两倍,诸如此类。虽然这些不是很容易消除,或者近年来围绕它们的讨论变得更加微妙,但交换无用的神话更多地基于启发式和奥秘,而不是可以用简单的类比来解释的东西,需要更多的理解。

这篇文章主要是针对那些管理Linux系统的人,他们有兴趣听听在运行时使用过小/没有交换或使用vm.swappness设置为0时的对策。

如果没有对Linux内存管理中一些基本的底层机制有共同的理解,那么很难谈论为什么交换和换出页面在正常操作中是件好事,所以让我们确保我们的想法是一致的。

Linux中有许多不同类型的内存,每种类型都有自己的属性。理解这些细微差别是理解SWAP为什么重要的关键。

例如,有几页(内存块,通常为4k)负责保存您的计算机上运行的每个进程的代码。还有一些选项负责缓存与这些程序访问的文件相关的数据和元数据,以加快将来的访问速度。这些是页面缓存的一部分,我将它们称为文件内存。

还有一些页面负责在该代码内进行的内存分配,例如,当使用malloc分配的新内存被写入时,或者当使用mmap的MAP_ANONAME标志时。这些都是匿名页--之所以这么叫是因为它们没有任何支持--我会把它们称为“无记名”(Anon Memory)。

还有其他类型的内存-共享内存、片内存、内核堆栈内存、缓冲区等等-但是匿名内存和文件内存是最广为人知、最容易理解的,所以我将在我的示例中使用它们,尽管它们也同样适用于这些类型。

当思考一种特定类型的内存时,最基本的问题之一是它是否能够被回收。在这里,回收意味着系统可以在不丢失数据的情况下,从物理内存中清除该类型的页面。

对于某些页面类型,这通常相当琐碎。例如,在清除(未修改)页面高速缓存的情况下,为了提高性能,我们只需重新缓存磁盘上的内容,这样我们就可以删除页面,而无需执行任何特殊操作。

对于某些页面类型,这是可能的,但不是微不足道的。例如,在脏(已修改)页高速缓存内存的情况下,我们不能简单地删除该页,因为磁盘还没有我们的修改。因此,我们需要要么拒绝回收,要么首先将更改取回磁盘,然后才能删除此内存。

对于某些页面类型,这是不可能的。例如,在前面提到的匿名页面的情况下,它们只存在于内存中,而不存在于其他后备存储中,因此它们必须保存在那里。

如果你在Linux上寻找关于交换目的的描述,你将不可避免地发现很多人谈论它,好像它仅仅是在紧急情况下使用的物理RAM的一个扩展。例如,下面是我在谷歌上输入什么是SWAP的随机帖子,它是我得到的排名靠前的结果之一:(What is SWAP";What is SWAP";)。

交换本质上是紧急内存;当您的系统暂时需要比RAM中可用的物理内存更多的物理内存时留出的空间。它被认为是不好的,因为它速度慢且效率低,如果您的系统经常需要使用交换,那么它显然没有足够的内存。[…]。如果你有足够的内存来处理你所有的需求,并且不指望它会被最大限度地占用,那么你在没有交换空间的情况下运行应该是完全安全的。

需要说明的是,我完全不会因为帖子的内容而责怪这篇评论的发帖人--这是许多Linuxsysadmins的人都接受的常识,如果你邀请他们谈论交换,这可能是最有可能从他们那里听到的事情之一。然而,不幸的是,这也是对互换的目的和用途的误解,特别是在现代系统上。

在上面,我谈到了匿名页面的回收是不可能的,因为匿名页面本质上在从内存中清除时没有后备存储可供后退-因此,它们的回收将导致这些页面完全丢失数据。不过,如果我们能为这些页面创建一个这样的商店呢?

嗯,这正是掉期的目的。交换是存储这些不可回收的页面的存储区域,它允许我们按需将它们调出到存储设备中。(=。这意味着,它们现在可以被视为与它们更容易回收的朋友(如清理文件页)一样平等地有资格回收,从而允许更有效地使用可用的物理内存。

交换主要是一种平等回收的机制,不适用于紧急情况下的额外内存。交换并不是使您的应用程序变慢的原因--所有的内存争用都是使您的应用程序变慢的原因。

那么,在这种回收平等的情况下,什么样的情况下才会合法地选择回收匿名页面呢?抽象地说,下面是一些不常见的场景:

在初始化期间,长时间运行的程序可能会分配和使用Manypage。这些页面也可以用作关闭/清理的一部分,但是一旦程序启动(从特定于应用程序的意义上讲),就不需要这些页面了。这对于需要初始化的守护进程来说是相当常见的。

在程序正常运行期间,我们可以分配很少使用的内存。对于整体系统性能而言,需要按需从磁盘修复这些主要故障可能更有意义,而不是将内存用于其他更重要的事情。

让我们来看看典型的情况,以及它们在交换和不交换的情况下的表现。在我关于cgroupv2的演讲中,我谈到了有关内存争用的指标。

使用交换:我们可以选择换出很少使用的匿名内存,这些内存可能只在进程生命周期的一小部分使用,允许我们使用该内存来提高缓存命中率,或进行其他优化。

没有交换:我们不能换出很少使用的匿名内存,因为它锁在内存中。虽然这可能不会立即成为问题,但在某些工作负载中,这可能表示由于陈旧的匿名页面占用了更重要的使用空间而导致的性能显著下降。

使用交换:所有内存类型被回收的可能性都是相等的。这意味着我们有更多机会成功回收页面-也就是说,我们可以回收不会很快出错的页面再次返回(颠簸)。

没有交换:匿名页被锁定在内存中,因为它们无处可去。长期页面回收成功的机会较低,因为我们只有一些类型的内存有资格被回收。页面抖动的风险更高。普通读者可能会认为这会更好,因为它可能会避免进行磁盘I/O,但事实并非如此-我们只需将交换的磁盘I/O转移到丢弃热门页面缓存和丢弃我们很快需要的代码段即可。

有了SWAP:我们对暂时的峰值更有弹性,但在严重内存匮乏的情况下,从内存开始颤抖到OOM杀手的时间可能会延长。我们对记忆压力的始作俑者有了更多的了解,可以更合理地对其采取行动,并可以进行受控的干预。

在没有交换的情况下:OOM杀手被更快地触发,因为匿名页面被锁定在内存中并且不能被回收。我们更有可能重击记忆,但是重击和排卵之间的时间减少了。这取决于您的应用程序,这可能是更好的,也可能是更糟的。例如,基于队列的应用程序可能需要这种从拍打到杀戮的快速传输。也就是说,这仍然为时已晚,无法发挥真正的作用--OOM杀手只在严重饥饿的时刻才会被调用,而依赖这种方法来实现这种行为会更好地被更多的机会主义进程杀死所取代,因为首先会出现内存争用。

好的,我想要系统交换,但是我如何针对各个应用程序调优它呢?

你不会认为如果我不插上cgroupv2,你整个帖子都读不完吧?;-)。

显然,通用的启发式算法很难总是正确的,所以能够给内核提供指导是很重要的。从历史上看,您唯一能做的调整是在系统级别,使用vm.swappness。这有两个问题:vm.swappity非常难以解释,因为它只作为较大的启发式系统的一小部分输入,而且它是系统范围的,而不是细化到一组较小的进程。

您也可以使用mlock将页面锁定到内存中,但这需要修改程序代码、使用LD_PRELOAD取乐,或者在运行时使用adebugger做一些可怕的事情。在基于VM的语言中,这也不能很好地工作,因为您通常无法控制分配,最终不得不mlockall,这对您实际关心的页面没有精确性。

cgroup v2有一个可调的内存.low形式的每cgroup,它允许用户告诉内核优先使用低于某个内存使用阈值的其他应用程序进行回收。这允许我们不阻止内核换出我们的应用程序的一部分,而是更愿意在内存争用的情况下从其他应用程序中回收。在正常情况下,内核交换逻辑通常相当好,并且允许它随意换出页面通常会提高系统性能。在严重的内存争用下进行交换并不理想,但它更多的是完全耗尽内存的属性,而不是交换器的问题。在这些情况下,当内存压力开始增加时,您通常希望通过自毁非关键进程来快速失败。

为此,您不能简单地依赖OOM杀手。OOM杀手只有在严重失败的情况下才会被调用,此时我们已经进入了系统严重不健康的状态,而且很可能已经有一段时间了。在考虑OOM杀手之前,你需要自己机会主义地处理这种情况。

不过,使用传统的LinuxMemory计数器确定内存压力有点困难。我们有一些看似相关的东西,但实际上只是切题--内存使用率、页面扫描等等--仅从这些指标很难区分有效的内存配置和倾向于内存争用的内存配置。在Facebook有一群人,以约翰尼斯为首,致力于开发新的度量标准,以便更容易地暴露记忆压力,这应该会在未来对此有所帮助。如果您有兴趣了解更多这方面的信息,我将详细介绍我在cgroupv2上的演讲中正在考虑的一个指标。

通常,优化内存管理所需的最小交换空间量取决于固定在内存中且未被应用程序重新访问的匿名页的数量,以及回收这些匿名页的价值。后者主要是一个问题,即哪些页面不再被清除,以便为这些不经常访问的匿名页面让路。

如果您有大量的磁盘空间和最新的(4.0+)内核,那么交换空间越多几乎总是比交换越少越好。在较旧的内核中,kswapd是负责管理交换的内核进程之一,在历史上,它总是非常急切地想要在交换越多的情况下积极地耗尽内存。最近,当有大量交换空间可用时,交换行为已得到显著改善。如果您正在运行内核4.0+,在现代内核上进行更大的交换应该不会导致过度的交换。因此,如果您有足够的空间,那么几GB的交换大小可以让您在现代内核上有选择余地。

如果您更受磁盘空间的限制,那么答案实际上取决于您必须做出的权衡,以及环境的性质。理想情况下,您应该有足够的交换空间,以使您的系统在正常和峰值(内存)负载下以最佳方式运行。我推荐的是设置一些具有2-3 GB或更多交换空间的测试系统,并监控一周左右在不同(内存)负载条件下发生的情况。只要你在那一周内没有遇到严重的内存匮乏-在这种情况下,测试就不会很有用-你最终可能会占用一些MB的交换空间。因此,除了为变化的工作负载留出一点缓冲区之外,至少有那么多可用交换空间可能是值得的。在日志记录模式之上还可以在SWAPSZ列中显示哪些应用程序的页面正在被清除,因此如果您还没有在服务器上使用它来记录服务器的历史状态,那么您可能希望将其设置在带有日志记录模式的测试机器上,作为本实验的一部分。这还会告诉您应用程序何时开始换出页面,您可以将这些页面与日志事件或其他关键数据联系起来。

另一件值得考虑的事情是掉期介质的性质。交换Readstend是高度随机的,因为我们不能可靠地预测哪些页面会出错,什么时候会出错。在固态硬盘上,这并不重要,但在旋转磁盘上,随机I/O非常昂贵,因为它需要物理移动才能实现。另一方面,重新设置文件页的随机性可能较小,因为与运行时单个应用程序的操作相关的文件往往较少碎片化。这可能意味着,在旋转磁盘上,您可能希望Tobias更多地回收文件页面,而不是换出匿名页面,但您还需要测试和评估这种平衡

对于想要休眠交换的笔记本电脑/台式机用户,这也需要考虑到-在这种情况下,交换文件至少应该是您的物理RAM大小。

首先,重要的是要了解vm.swappness的作用。vm.swappinesl是一个sysctl,它将内存回收偏向于匿名页面的回收,或者偏向于文件页面的回收。它使用两个不同的属性来实现这一点:FILE_PRIO(我们愿意回收文件页)和ANON_PRIO(我们愿意回收匿名页)。vm.swappness在这方面发挥了作用,因为它成为了ANON_PRIO的默认值,并且还从FILE_PRIO的默认值200中减去了它,这意味着对于vm.swappness=50的值,结果是ANON_PRIO是50,FILE_PRIO是150(确切的数字并不重要,因为它们之间的相对权重是一样的)。

这意味着,一般而言,vm.swappness仅仅是回收和重新故障匿名内存与硬件和工作负载的文件内存的成本之比。该值越低,您就越能告诉内核,在您的硬件上换出和换入不经常访问的匿名页的成本很高。该值越高,您就越能告诉内核,在您的硬件上交换匿名页和文件页的成本是相似的。内存管理子系统仍将主要根据内存的热度来决定是否交换文件或匿名页,但交换的成本计算要么更倾向于交换,要么更倾向于丢弃文件系统缓存(无论哪种方式都可以)。在SSD上,这些基本都是一样昂贵的,因此设置vm.swappness=100(完全相等)可能效果很好。在旋转磁盘时,交换的成本可能要高得多,因为交换本身需要随机读取,因此您可能希望更倾向于较低的值。

现实情况是,大多数人并不真的感觉到他们的硬件需要什么,所以根据本能来调整这个值并不是一件微不足道的事--这是你需要用不同的值来测试的。您还可以花时间评估系统和核心应用程序的内存组成,以及它们在轻度内存回收下的行为。

在谈到vm.swappness时,最近要考虑的一个极其重要的变化是,Satoru Moriya在2012年对vmscan所做的更改,它极大地改变了处理vm.swappness=0的方式。

从本质上讲,这个补丁使得我们对扫描(从而回收)任何vm.swappness=0的匿名页面非常有偏见,除非我们已经遇到严重的内存争用。正如之前在这篇文章中提到的,这通常不是你想要的,因为这会阻止在极端记忆压力发生之前相等地回收,而这实际上可能首先导致这种极端记忆压力。vm.swappness=1是您在不调用。

..