⚠️ Эта страница автоматически переведена, и перевод может быть несовершенным.

О запуске, mmap, mlock и --force-preread

author image
View as markdown

Как было упомянуто в статье «Загрузка индексов при запуске », теперь все индексы (файлы атрибутов и списков слов) не загружаются физически в ОЗУ, а отображаются в память (memory-mapped). Это позволяет запускать их гораздо быстрее, но также имеет некоторые побочные последствия, которые я хочу объяснить.

Во-первых, поскольку мы используем отображение, индексы могут не быть постоянно зафиксированы в физическом ОЗУ, и вам не требуется столько ОЗУ, чтобы они все помещались. Наличие разумного объёма ОЗУ уже может обеспечить быстрые поисковые запросы во многих случаях, поскольку когда индексы кэшируются, они работают значительно быстрее.

Второе последствие — на самом деле отображение памяти занимает лишь область «виртуального адресного пространства» процесса. Пока на любой современной системе у вас есть 64-битные адреса, мы можем сказать, что можем загрузить и обслуживать индекс практически любого размера, несмотря на фактически свободное ОЗУ. Однако это относится только к данным индексов. Демону Manticore Search также нужно физическое ОЗУ для обычной работы: внутренних хэшей, буферов, массивов и т. д.

Если посмотреть на статистику памяти процесса, вы увидите некоторое число в колонке RSS (или RES), и это фактически занятое ОЗУ (в основном куча), но не загруженные индексы (если только вы не используете mlock=1). Они в основном отображаются в колонке 'VSZ'. Также если загрузить огромный индекс (примерно на весь объём ОЗУ) и затем выполнить команду 'free', вы увидите, что он практически не отражается в разделе 'used', а в основном в 'buff/cache', и, соответственно, в 'available'.

Таким образом, загруженные индексы по умолчанию НЕ зафиксированы в памяти, а лишь кэшируются. Если ОС понадобится выделить больше ОЗУ для других процессов, она просто сделает это, жертвуя кэшированными данными. Поэтому «загрузка» индекса не гарантирует, что он действительно находится в ОЗУ и будет отвечать предсказуемо быстро.

Что это значит на практике?

  1. Во-первых, по умолчанию нет гарантий. «Загрузка» индекса при старте посредством отображения памяти и последующего постраничного доступа к этой карте просто загружает страницу, в которую вы переходите. ОС не гарантирует, что после загрузки одной страницы следующий шаг «загрузки» закрепит предыдущую страницу постоянно в ОЗУ. Да, это может произойти — например, если у вас есть 128 ГБ свободного ОЗУ и загруженный индекс занимает только 30 ГБ. Но если у вас индекс размером 120 ГБ и лишь 16 ГБ ОЗУ, «загрузка» завершится успешно тем же способом, однако, поскольку индекс не помещается полностью в ОЗУ, он не будет полностью кэширован, и время отклика увеличится.
  2. Во-вторых, нет гарантии, что загруженный индекс будет постоянно сохранять одинаковое время отклика. Представьте, снова, что вы загрузили 30-ГБ индекс на системе с 50 ГБ свободного ОЗУ, и всё кажется быстрым. Затем вы запускаете ещё один процесс, жадный до ОЗУ, который занимает 40 ГБ. Это означает, что из 30 ГБ вашего индекса останется кэшировано лишь около 10 ГБ, а доступ к остальному теперь требует чтения с диска.

Итак, ни ленивый preread, ни даже параметр --force-preread не гарантируют, что весь индекс будет кэширован и будет отвечать постоянно и предсказуемо быстро. Нет гарантии, только вероятность. Чем больше у вас ОЗУ, тем выше вероятность, что весь индекс будет кэширован и будет отвечать максимально быстро. Весь этот «массаж» mmap — лишь вопрос вероятности.

Но мне нужна гарантия, а не вероятность! Это возможно?

Да! Есть один-единственный способ надёжно зафиксировать весь индекс в ОЗУ — параметр mlock . Его нужно задавать в конфигурации индекса (а не в параметрах командной строки). Для блокировки памяти нужны соответствующие привилегии (подробности — в системной справке 'man mlock'). Как это работает? Демон выполнит mmap файлов индекса, а затем вызовет для них mlock. В этот момент ОС определит, хватает ли ОЗУ, чтобы загрузить все нужные отображения, и если хватает, сразу их загрузит. Это может занять довольно много времени (возьмите скорость вашего хранилища и оцените, сколько уйдёт на последовательную загрузку нужного объёма данных).

Таким образом, мы можем достичь цели — иметь полностью зафиксированный в ОЗУ индекс, который отвечает предсказуемо быстро. Это хорошо.

Но также необходимо упомянуть некоторые моменты, связанные с mlock.

  1. Во-первых, как уже упоминалось, вам нужны привилегии для его запуска. Это отчасти связано с тем, как он работает, и может влиять на всю систему. В большинстве случаев это не проблема, если только вы не используете совместный хостинг с очень ограниченными правами.
  2. Во-вторых, блокировка отображения в памяти (mlock) — это блокирующий процесс, которым мы не можем управлять. Внутри мы просто вызываем mlock(), он делает некоторую магию и возвращается через несколько секунд/минут после завершения. Нет возможности прервать, нет возможности ограничить ввод-вывод, просто ждём. Поэтому mlock может влиять на другие операции ввода-вывода на машине.
  3. Когда система ищет ОЗУ для mlock, есть вероятность, что она вызовет OOM-killer, чтобы освободить ОЗУ, а тот может завершить другой процесс. Будьте осторожны!
  4. Даже если вы используете mlock, во многих случаях вы всё равно можете захотеть использовать --force-preread. Дилемма такова:
    • без --force-preread searchd начнёт обслуживать соединения быстрее, но индексы будут «холодными», пока они полностью не будут предварительно прочитаны в фоне. Это может быть плохо для входящих запросов.
    • с --force-preread вам придётся подождать (возможно несколько минут), но после этого вы сможете обеспечить очень хорошую производительность.

Вот как это может выглядеть с mlock, но без --force-preread:

no_force_preread И те же индексы на том же оборудовании с --force-preread:

force_preread Как видите, в этом случае имеет смысл подождать 6 минут при старте, иначе среднее время отклика становится в разы выше в течение нескольких десятков минут, а iowait также чрезвычайно высок из-за запросов, выполняющих случайные чтения с диска. Конечно, могут быть и другие случаи, и ваша балансировка нагрузки может работать иначе и обрабатывать такие ситуации более умно, или у вас просто может не хватать ОЗУ, чтобы разместить весь индекс, или ваши запросы могут быть менее требовательными. Просто рассмотрите оба подхода и выберите тот, который вам лучше подходит.

Что ещё может быть важно?

  1. Поэкспериментируйте с параметрами ОС, такими как «swappiness», или полностью отключите своп, если можете себе это позволить. Это может помочь увеличить вероятность быстрого отклика (без mlock). Учтите, что в современных ядрах Linux есть замечательная возможность — control groups (они же cgroups). Вы можете поместить ваш демон в отдельный cgroup и настроить для него любые системные параметры (например, упомянутый swappiness), не затрагивая глобальные настройки системы.
  2. Современные SSD достаточно быстры даже при случайном доступе, поэтому их использование может стереть разницу между просто «mapped» («mlocked») и «cached» данными.

Установить Manticore Search

Установите Manticore Search одной командой в Linux или macOS:

curl https://manticoresearch.com | sh

Для расширенных вариантов установки см. полное руководство по установке и документацию .

Установить Manticore Search