关于启动时的 mmap、mlock 和 --force-preread

正如在文章 " Indexes load at startup " 中提到的,现在所有的索引(属性和词汇表文件)并不是物理加载到 RAM 中,而是使用内存映射。这使得它们在启动时能够更快地加载,但也带来了一些我想要解释的副作用。

首先,由于我们进行映射,索引可能不会永久锁定在物理 RAM 中,并且您不需要拥有足够的 RAM 以容纳所有索引。拥有合理数量的 RAM 在许多情况下可能已经为您提供了快速的搜索查询,因为当索引被缓存时,它们的工作速度显著更快。

第二个后果 - 实际上,内存映射仅占用进程的“虚拟地址空间”的一部分。只要在任何现代系统上,您有 64 位的地址,我们可以说我们可以加载和服务几乎任何大小的索引,尽管实际的可用 RAM 可能有限。然而,请注意,这仅与索引数据相关。Manticore 搜索守护进程还需要物理 RAM 来进行常规工作,例如内部哈希、缓冲区、数组等。

如果您查看进程的内存统计信息,您会在 RSS(或 RES)列中看到一些数字,这实际上是占用的 RAM(主要是堆),而不是加载的索引(除非您设置了 mlock=1)。它们主要反映在“VSZ”列中。此外,如果您加载一个巨大的索引(大约占用整个 RAM 空间),然后执行“free”命令,您会看到它在“used”空间中几乎没有反映,但主要在“buff/cache”中,因此,也在“available”中。

因此,默认情况下加载的索引并没有被锁定在内存中,而只是被缓存。如果操作系统需要为其他进程分配更多的 RAM,它将这样做,牺牲缓存的数据。因此,“加载”索引并不能保证它实际上在 RAM 中并且会以可预测的速度响应。

这在实践中意味着什么?

  1. 首先,默认情况下没有保证。“加载”索引时通过内存映射,然后逐页进入该映射,只会加载您进入的页面。操作系统并不保证在加载一个页面后,下一步的“加载”会将之前的页面持久地锁定在 RAM 中。是的,它可能会 - 如果说您有 128GB 的可用 RAM,而加载的索引只有 30GB。但如果您有一个大小为 120GB 的索引,而只有 16GB 的 RAM,“加载”将以同样的方式成功,但由于索引无法完全适应 RAM,它将不会被完全缓存,并且响应时间将增加。
  2. 其次,没有保证加载的索引会永久保持相同的响应时间。再想象一下,您在一个有 50GB 可用 RAM 的系统上加载一个 30GB 的索引,一切似乎都运行得很快。但随后您还加载了另一个占用 RAM 的进程,它占用了 40GB。这意味着,您索引的 30GB 中只有大约 10GB 仍然被缓存,而访问其余部分现在需要从磁盘读取。

因此,无论是懒惰的预读,还是甚至 --force-preread 选项,都不能保证整个索引被缓存并且会永久且可预测地快速响应。没有保证,只有概率。您拥有的 RAM 越多,整个索引被缓存并且以最大速度响应的概率就越大。所有这些 mmap “按摩”只是关于概率。

但我需要保证,而不是概率!这可能吗?

是的!唯一(也是唯一)可以确保将整个索引锁定在 RAM 中的方法是使用 mlock 选项。 它应该在索引配置中设置(而不是在命令行选项中)。这需要您拥有执行此操作的权限(有关详细信息,请参见系统的 'man mlock')。它是如何工作的?守护进程将 mmap 索引文件,然后在它们上调用 'mlock'。此时,操作系统将识别是否有足够的 RAM 来加载所有所需的映射,如果有,它将立即执行加载。这可能是一个相对较长的操作(只需考虑您的存储速度并估算加载所需数据的时间)。

因此,我们可以实现目标 - 使索引完全锁定在 RAM 中并以可预测的速度响应。这很好。

但也有必要提到一些与 mlock 相关的事项。

  1. 首先,正如提到的 - 您需要权限来运行它。这部分来自于它的工作方式,可能会影响整个系统。不过在大多数情况下这并不是什么大问题,除非您使用的是权限非常有限的共享主机。
  2. 其次,映射的缓存(mlocking)是一个我们无法管理的阻塞过程。我们内部只是调用 mlock(),它在内部做一些魔法,并在所有完成后几秒钟/几分钟后返回。无法中断,无法限制 I/O,只能等待。因此,mlocking 的过程可能会影响机器上的其他 I/O 操作。
  3. 当系统寻找 RAM 进行 mlocking 时,可能会调用 OOM-killer 来为您释放 RAM,这可能会杀死另一个进程。请注意!
  4. 即使您使用 mlock,在许多情况下您仍然可能希望使用 --force-preread。这里的两难是:
    • 没有 --force-preread,searchd 将更早开始服务连接,但索引在后台完全预读之前会比较冷。这可能对即将到来的查询不利。
    • 使用 --force-preread,您将不得不等待(可能几分钟),但之后您将能够提供非常好的性能。

这看起来可能是使用 mlock,但没有 --force-preread

no_force_preread同样的索引在相同的硬件上使用 --force-preread

force_preread正如您所看到的,在这种情况下,等待 6 分钟是有意义的,否则平均响应时间在几十分钟内会变得高得多,并且由于随机磁盘读取的查询,iowait 也极高。当然,可能还有其他情况,您的负载均衡可能以不同的方式工作并更智能地处理这种情况,或者您可能根本没有足够的 RAM 来容纳整个索引,或者您的查询可能更轻。只需考虑这两种方法,并选择最适合您的方法。

还有什么可能重要的?

  1. 玩弄操作系统参数,比如'swapinness',或者如果你能承受的话完全禁用交换。这可以帮助提高快速响应的概率(不使用mlocking)。请注意,在现代Linux内核中,你有这样一个奇妙的东西,称为控制组(即cgroups)。你可以将你的守护进程放入一个专用的cgroup中,并为其调整任何系统设置(如提到的swapinness),而不影响全局系统设置。
  2. 现代SSD即使在随机访问时也相当快速,因此使用它们可能会消除“映射”('mlocked')和“缓存”数据之间的差异。

安装Manticore Search

安装Manticore Search