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

About startup, mmap, mlock and --force-preread

Как было упомянуто в статье " Indexes load at startup ", теперь все индексы (файлы атрибутов и списков слов) не загружаются физически в ОЗУ, а отображаются в память (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. Во‑вторых, кэширование карты (mlocking) — это блокирующий процесс, которым мы не можем управлять. Внутри мы просто вызываем mlock(), он делает некоторую магию и возвращается через несколько секунд/минут после завершения. Нет возможности прервать, нет возможности ограничить ввод‑вывод, просто ждём. Поэтому процесс mlocking может влиять на другие операции ввода‑вывода на машине.
  3. Когда система ищет ОЗУ для mlocking, есть вероятность, что она вызовет OOM‑killer, чтобы освободить ОЗУ, что может завершить другой процесс. Будьте осторожны!
  4. Даже если вы используете mlock, в многих случаях вы всё равно можете захотеть использовать --force-preread. Дилемма такова:
    • без --force-preread searchd начнёт обслуживать соединения быстрее, но индексы будут "холодными", пока они полностью не будут предварительно прочитаны в фоне. Это может быть плохо для входящих запросов.
    • с --force-preread вам придётся подождать (возможно несколько минут), но после этого вы сможете обеспечить очень хорошую производительность.

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

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

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

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

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

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

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