Как упоминалось в статье “ Indexes load at startup ”, теперь все индексы (файлы атрибутов и словарей) не загружаются физически в ОЗУ, а вместо этого отображаются в память. Это позволяет загружать их значительно быстрее при запуске, но также имеет некоторые побочные последствия, которые я хочу объяснить.
Прежде всего, поскольку мы делаем отображение, индексы могут не быть постоянно заблокированными в физической ОЗУ, и вам не нужно иметь столько ОЗУ, чтобы все они вмещались. Наличие разумного объема ОЗУ может уже обеспечить вам быстрое выполнение поисковых запросов во многих случаях, так как когда индексы закэшированы, они работают значительно быстрее.
Второе последствие - на самом деле отображение в память занимает лишь область “виртуального адресного пространства” процесса. Поскольку на любой современной системе у вас есть 64 бита для адресов, можно сказать, что мы можем загружать и обслуживать индекс практически любого размера, несмотря на фактическое свободное ОЗУ. Однако, обратите внимание, что это относится только к данным индексов. Демон поиска Manticore также нуждается в физической ОЗУ для обычной работы, такой как внутренние хэш-таблицы, буферы, массивы и т.д.
Если вы посмотрите на статистику памяти процесса, вы увидите какое-то число в столбце RSS (или RES), и это на самом деле занятая ОЗУ (в основном куча), но не загруженные индексы (если только вы не включите mlock=1). Они в основном отражены в столбце “VSZ”. Также, если вы загрузите огромный индекс (примерно на всю область ОЗУ), а затем выполните команду ‘free’, вы увидите, что это практически не отражается в “используемом” пространстве, а скорее в “buff/cache”, и, таким образом, также в “доступно”.
Таким образом, загруженные индексы по умолчанию не заблокированы в памяти, а просто закэшированы. Если ОС нуждается в выделении большего объема ОЗУ для других процессов, она просто сделает это, жертвуя закэшированными данными. Таким образом, “загрузка” индекса не дает никаких гарантий того, что он фактически находится в ОЗУ и будет отвечать предсказуемо быстро.
Что это значит на практике?
- Во-первых, никаких гарантий по умолчанию. “Загрузка” индекса при запуске путем отображения в память и затем проход по страницам внутри этой карты просто загружает страницу, на которую вы переходите. ОС не гарантирует, что после перехода на одну страницу следующий шаг “загрузки” заблокирует предыдущую страницу постоянно в ОЗУ. Да, это может быть так - если, скажем, у вас 128 ГБ свободной ОЗУ, а загруженный индекс составляет всего 30 ГБ. Но если у вас индекс размером 120 ГБ и только 16 ГБ ОЗУ, “загрузка” пройдет точно так же, но поскольку индекс не сможет вместиться в ОЗУ, он не будет полностью закэширован, и время его отклика увеличится.
- Во-вторых, нет гарантии, что загруженный индекс будет сохранять такое же время отклика постоянно. Представьте себе, опять же, что вы загружаете индекс размером 30 ГБ на систему с 50 ГБ свободной ОЗУ, и все кажется работающим быстро. Но затем вы также запускаете другой процесс, требующий много ОЗУ, и он занимает 40 ГБ. Это значит, что из 30 ГБ вашего индекса только ~10 ГБ все еще будет закэшировано, а доступ к остальным теперь будет требовать чтения с диска.
Итак, ни “ленивая предварительная загрузка”, ни даже опция --force-preread
не гарантируют, что весь индекс закэширован и будет отвечать постоянно и предсказуемо быстро. Никаких гарантий, только вероятность. Чем больше у вас ОЗУ - тем больше вероятность, что весь индекс закэширован и будет отвечать максимально быстро. Все это “массаж” mmap - это всего лишь о вероятности.
Но мне нужна гарантия, а не вероятность! Возможно ли это?
Да! Единственный (и единственный) способ надежно заблокировать весь индекс в ОЗУ - это использование опции mlock . Это должно быть установлено в конфигурации индекса (не в параметрах командной строки). Это требует, чтобы у вас были привилегии для этого (см. системный ‘man mlock’ для получения подробной информации). Как это работает? Демон будет отображать файлы индексов в память, а затем вызывать ‘mlock’ на них. В этот момент ОС определит, достаточно ли у нее ОЗУ, чтобы загрузить все необходимые отображения, и если да, она немедленно выполнит загрузку. Это может быть относительно длительной операцией (просто возьмите скорость вашего хранилища и оцените время, необходимое для последовательной загрузки требуемого объема данных).
Таким образом, мы можем достичь поставленной цели - иметь индекс, полностью заблокированный в ОЗУ, который отвечает предсказуемо быстро. Это хорошо.
Но также необходимо упомянуть некоторые вещи, касающиеся mlock.
- Во-первых, как уже упоминалось, вам нужны привилегии для его выполнения. Это частично связано с тем, как это работает, и может повлиять на всю систему. Это не большая проблема, однако, в большинстве случаев, если только вы не используете совместный хостинг с очень ограниченными разрешениями.
- Во-вторых, кэширование карты (mlocking) - это блокирующий процесс, который мы не можем контролировать. Внутренне мы просто вызываем
mlock()
, он делает магию внутри и возвращается через несколько секунд/минут после завершения. Нет возможности прервать, нет способа регулировать i/o, просто ждите. Поэтому процесс mlocking может повлиять на другие операции ввода-вывода на машине. - Когда система ищет ОЗУ для mlocking, есть вероятность, что она вызовет OOM-killer, чтобы освободить ОЗУ для вас, что может убить другой процесс. Будьте внимательны!
- Даже если вы используете mlock, вам все равно может понадобиться использовать
--force-preread
во многих случаях. Здесь дилемма:- без
--force-preread
searchd начнет обслуживать подключения быстрее, но индексы будут холоднее, пока они не будут полностью предварительно загружены в фоновом режиме. Это может быть плохо для входящих запросов. - с
--force-preread
вам придется подождать (возможно, несколько минут), но после этого вы сможете обеспечить очень хорошую производительность.
- без
Вот как это может выглядеть с mlock, но без --force-preread
:
И те же индексы на том же оборудовании с
--force-preread
:
Как вы можете видеть, в этом случае имеет смысл подождать 6 минут при запуске, в противном случае среднее время отклика становится в разы выше на десятки минут, а iowait также чрезвычайно высок из-за запросов, которые выполняют случайные чтения с диска. Конечно, могут быть и другие случаи, и ваш баланс нагрузки может работать иначе и обрабатывать такие ситуации более умным образом, или у вас просто может не хватать ОЗУ, чтобы вместить целый индекс, или ваши запросы могут быть легче. Просто учтите оба подхода и выберите тот, который вам больше всего подходит.
Что еще может быть важно?
- Играйте с параметрами ОС, такими как ‘swapinness’, или полностью отключите своп, если можете себе это позволить. Это может помочь увеличить вероятность быстрого ответа (без mlocking). Обратите внимание, что на современных ядрах linux у вас есть такая замечательная вещь, как контрольные группы (также известные как cgroups). Вы можете поместить свой демон в выделенную контрольную группу и настроить любые системные настройки (такие как упомянутый swapinness) для него, не затрагивая глобальные системные настройки.
- Современные SSD довольно быстры даже для случайного доступа, поэтому их использование может устранить разницу между просто ‘mapped’ (‘mlocked’) и ‘cached’ данными.