正如在文章" 启动时加载索引 “中提到的,现在所有的索引(属性和词汇表文件)不再是物理加载到RAM中,而是通过内存映射的方式。这使得它们在启动时能够更快地加载,但也带来了一些我想要解释的副作用。
首先,由于我们进行了映射,索引可能不会永久锁定在物理RAM中,并且你不需要有足够的RAM让所有索引都适配。拥有合理数量的RAM在许多情况下已经可以为你提供快速的查询,因为当索引被缓存时,它们的工作速度会显著提高。
第二个后果 - 实际上内存映射只占用进程的一个“虚拟地址空间”区域。在任何现代系统上,你都有64位用于地址,我们可以说我们可以加载和服务几乎任何大小的索引,尽管实际的可用RAM可能有限。但请注意,这仅与索引数据有关。Manticore搜索守护进程也需要物理RAM来正常工作,比如内存哈希、缓冲区、数组等等。
如果你查看进程的内存统计信息,你会看到RSS(或RES)列中有一个数字,那实际上是占用的RAM(主要是堆),但不是加载的索引(除非你设置mlock=1)。它们在“VSZ”列中反映得更多。此外,如果你加载一个巨大的索引(几乎占满整个RAM空间),然后执行“free”命令,你会看到它几乎没有反映在“已使用”空间中,而主要在“buff/cache”中,因此在“可用”中也是如此。
因此,加载的索引默认情况下并不在内存中被锁定,而只是被缓存。如果操作系统需要为其他进程分配更多的RAM,它将这样做,牺牲缓存的数据。因此,“加载”索引并不能保证它实际上在RAM中并且会快速响应。
这在实践中意味着什么?
- 首先,默认情况下没有保证。“加载”索引时通过内存映射,然后逐页进入该映射,只会加载你进入的页面。操作系统不保证在跨越一页之后,下一步的“加载”会持久地锁定之前的一页在RAM中。是的,它可能会 - 如果说你有128GB的可用RAM,而加载的索引只有30GB。但如果你有一个大小为120GB的索引,而只有16GB的RAM,‘加载’将以相同的方式成功,但由于索引无法完全适配到RAM中,它将不会被完全缓存,其响应时间将会增加。
- 第二,没有保证加载的索引会永久保持相同的响应时间。想象一下,你在一个有50GB可用RAM的系统上加载一个30GB的索引,一切看似运行良好。但随后你还加载了另一个占用RAM的进程,它占用了40GB。这意味着,你的30GB索引中只有约10GB仍然被缓存,访问其余的则需要从磁盘读取。
因此,懒惰的预读,甚至--force-preread
选项都不保证整个索引被缓存并会永久且可预测地快速响应。没有保证,只有概率。你拥有的RAM越多,整个索引被缓存并以最大速度响应的概率就越大。所有这些mmap“按摩”只是关于概率。
但我需要的是保证,而不是概率!这可能吗?
是的!唯一(也是唯一)的方法来确保整个索引锁定在RAM中的是使用 mlock 选项。 它应在索引配置中设置(而不是在命令行选项中)。这要求你有执行此操作的权限(有关详细信息,请参阅系统的“man mlock”)。它是如何工作的?守护进程将映射索引文件,然后调用’mlock’。此时,操作系统将识别其是否有足够的RAM来加载所需的所有映射,如果有,它将立即执行加载。这可能是一个相对漫长的操作(只需考虑你的存储速度并估算加载所需数据量的顺序所需的时间)。
因此,我们可以达成目标 - 使索引完全锁定在RAM中,并能够快速响应。这很好。
但还需要提及一些与mlock相关的事情。
- 首先,正如之前提到的 - 你需要有运行它的权限。这部分源于它的工作方式,并可能影响整个系统。然而在大多数情况下这不是大问题,除非你使用共享主机且权限非常有限。
- 其次,映射的缓存(mlocking)是一个我们无法管理的阻塞过程。在内部,我们只是调用
mlock()
,它在内部进行一些魔法,并在一切完成后在几秒钟/分钟后返回。没有办法中断,也没有办法限制I/O,只能等待。因此,mlocking过程可能会影响机器上的其他I/O操作。 - 当系统寻找RAM进行mlocking时,可能会调用OOM杀手以释放RAM,这可能会导致另一个进程被终止。请谨慎!
- 即使你使用mlock,在许多情况下你仍然可能希望使用
--force-preread
。这里的两难是:- w/o
--force-preread
,searchd将在更早处理连接,但索引会保持较冷,直到它们在后台完整预读。这可能对输入查询不利。 - with
--force-preread
,你将必须等待(也许几分钟),但之后你将能够提供非常好的性能。
- w/o
这可能在mlock的情况下看起来,但没有--force-preread
:
而同样的索引在同样的硬件上使用
--force-preread
:
如你所见,在这种情况下,等待6分钟在开始是合理的,否则平均响应时间会在数十分钟内显著增加,且由于随机磁盘读取查询,iowait也会极高。当然可能有其他情况,你的负载均衡可能以不同的方式工作,并更智能地处理这样的情况,或者你可能根本没有足够的RAM来容纳整个索引,或者你的查询可能较轻。只需考虑这两种方法,并选择最适合你的那种。
还有其他可能重要的内容吗?
- 玩弄操作系统参数,如 ‘swapinness’ 或者如果您可以承受的话完全禁用交换。这可以帮助增加快速响应的可能性(不使用 mlocking)。 请注意,在现代 Linux 内核中,您有这样一个很棒的东西,即控制组(aka cgroups)。您可以将您的守护进程放入专用的 cgroup 中,并为其调整任何系统参数(如前面提到的 swapinness),而不影响全局系统设置。
- 现代 SSD 对于随机访问非常快,因此使用它们可能会消除“映射”(‘mlocked’)数据和“缓存”数据之间的差异。