Тюнинг производительности Elasticsearch

Производительность Elasticsearch складывается из большого числа составляющих: правильного подбора CPU и памяти на нодах исходя из количества запросов, оптимизация настроек JVM, в частности выбор правильного размера памяти JVM, выбор правильного числа нод и расположения шардов на них, количество памяти, отдаваемое под системный кэш. Поскольку индексы хранятся на диске, то необходимо убедиться в достаточной производительности дисковой подсистемы, в частности ее iops, отключить swap. Не менее важна и архитектура решения с точки зрения количества нод и распределения Elasticsearch ролей между ними.

Обычно все эти настройки индивидуальны для каждого железа и каждой конфигурации, но есть общие подходы. Отдельно необходимо рассматривать вопросы оптимизации шардов и скорости индексации.

Железо

CPU: минимальная production конфигурация — 4 ядра, обычная расширяемая до 32 ядер.

Но нужно учитывать, что на нодах с разными ролями CPU используется по-разному. Если ваш кластер Elasticsearch в основном используется для индексации, вам может потребоваться больше ядер для обработки нагрузки индексации. С другой стороны, если ваш кластер в основном используется для поиска и агрегации данных, вам может потребоваться меньше ядер, но с более высокой тактовой частотой, чтобы обеспечить более быстрое время ответа на запросы. Для роли балансировщика требуется 2 ядра.

Память: обычно, удачным соотношением CPU к памяти является 1:4, минимум 8 ГБ, максимум обычно 64-128 ГБ.

Elasticsearch в значительной степени опирается на память кучи (heap memory) Java для хранения и управления структурами данных, кэшами и буферами. Чем больше памяти доступно, тем выше производительность кластера. Рекомендуемый размер кучи для Elasticsearch составляет 50% от доступной оперативной памяти, максимум ~30 ГБ. Выделение более ~30 ГБ памяти кучи может привести к снижению производительности из-за поведения сборки мусора Java, а также сжатых обычных указателей на объекты в JVM.

Вы можете проверить, используют ли ваши узлы сжатые обычные указатели объектов, выполнив следующую команду:

GET _nodes/_all/jvm?filter_path=**.using_compressed_ordinary_object_pointers

Если это не так (т. е. возвращаемое значение false), вы можете уменьшить объем памяти, выделенной для кучи, пока не достигнете порогового значения, обычно около 26 ГБ и 30 ГБ в зависимости от системы.

В дополнение к памяти кучи, Elasticsearch также использует память вне кучи для различных целей, таких как кэширование файловой системы и стеки потоков. Поэтому важно иметь достаточно оперативной памяти для удовлетворения требований как к памяти кучи, так и к памяти вне кучи.

Дисковая подсистема

Для индексации и поиска критичны iops. Для хранения индексов, по которым часто выполняется поиск, требуется использовать SSD или NVMe. Для хранения холодных данных, редко используемых в несложных запросах допустимо использовать HDD. Для индексации и переиндексации диски должны обеспечивать не менее 1500—2000 iops, при большом объеме данных от 3000 и выше. Объединение дисков в RAID массивы или LVM приносит рост производительности. По нашим тестам эффективно иметь до 3 блочных устройств на ноду, объединенных в LVM, при большем числе дисков разница часто несущественна. Однако, рекомендую выполнить собственные тесты в вашем окружении. Типичная скорость записи до 100 МБ/с.

Сеть

При пересылке индексов между нодами может утилизироваться до 300 Мбит/с полосы пропускания сети. Поэтому планируйте сеть с гигабитными адаптерами и внешних каналом связи более 100 Мбит/с, если вы пересылаете индексы через интернет.

Настройки оптимизации кластера

Роль мастера

В версии Elasticsearch 8.15 выделяется 11 ролей, однако многие роли могут совмещаться. Обязательными ролями является master  и data_content /data_hot.

Master нода отвечает за легкие действия в масштабе кластера, такие как создание или удаление индекса, отслеживание того, какие узлы являются частью кластера, и принятие решения о том, какие шарды каким узлам следует выделить. Для работоспособности кластера важно иметь стабильный master.

Количество индексов, которыми может управлять мастер, пропорционально размеру его кучи. Точный объем памяти кучи, необходимый для каждого индекса, зависит от различных факторов, таких как размер маппингов и количество сегментов на индекс. Как правило, у вас должно быть менее 3000 индексов на 1 ГБ кучи на мастере. Например, если ваш кластер имеет выделенные мастеры с 4 ГБ кучи каждый, то у вас должно быть менее 12000 индексов. Если роль мастера совмещенная, то необходимо резервировать память кучи под количество индексов.

 

JVM

Помимо настройки размера кучи, также можно играться с со сборкой мусора (Garbage Collection). Используйте сборщик мусора G1 для лучшей производительности и меньшей задержки. Чтобы включить G1, добавьте следующие параметры JVM в файле $ELASTICSEARCH_HOME/config/jvm.options:

-XX:+UseG1GC
-XX:G1ReservePercent=25
-XX:InitiatingHeapOccupancyPercent=30

Также можно конфигурировать частоту и порог срабатывания GC. Например, параметр Java -XX:CMSInitiatingOccupancyFraction=85 задает порог срабатывания при старой генерации 85%.

Шарды

Распределите шарды равномерно по узлам кластера, чтобы сбалансировать нагрузку. Используйте настройки index.number_of_shards и index.number_of_replicas для управления количеством основных и реплик шардов для каждого индекса. Для задания этих параметров лучше использовать шаблоны индексов, чтобы новые индексы создавались единообразно.

Распределение шардов: в качестве верхнеуровневой стратегии, если вы создаете индекс, который планируете часто обновлять, убедитесь, что вы назначаете достаточное количество primary шардов, чтобы вы могли равномерно распределить нагрузку индексации по всем вашим узлам. Общая рекомендация — выделять по одному primary шарду на ноду в вашем кластере и, потенциально, два primary шарда на ноду, но только если у вас большая пропускная способность CPU и диска на этих узлах (лично я так не делаю). Однако имейте в виду, что избыточное распределение шардов (shard overallocation) увеличивает накладные расходы и может негативно повлиять на поиск, поскольку поисковые запросы должны попадать в каждый шард в индексе. каждый шард потреблет файловые дискрипторы, память и CPU. Задание меньшего числа primary шардов, чем число нод, будет приводить в неравномерной нагрузке.

Следует использовать настройку index.routing.allocation.total_shards_per_node, чтобы указать количество шардов на ноду в индексе.

Большинство поисков попадают в несколько шардов. Каждый шард запускает поиск в одном потоке CPU. Хотя шард может запускать несколько одновременных поисков, поиск по большому количеству шардов может истощить пул потоков поиска ноды. Это может привести к медленной скорости поиска.

По умолчанию новые индексы создаются с 5 primary шардами. Невозможно увеличить количество primary шардов существующего индекса, то есть индекс должен быть создан заново, если вы хотите увеличить количество primary шардов. Существует 2 метода, которые обычно используются в таких ситуациях: через API _reindex и API _split.

Размер шарда: должен быть в пределах от 10 до 40 ГБ для оптимальной производительности. В большинстве случаев небольшой набор крупных шардов использует меньше ресурсов, чем множество мелких шардов, поиск по больших шардам выполняется быстрее.

Сегменты шардов

Большинство шардов содержат несколько сегментов, в которых хранятся данные индекса. Elasticsearch хранит некоторые метаданные сегмента в динамической памяти, чтобы их можно было быстро извлечь для поиска. По мере роста шарда его сегменты объединяются в меньшее количество более крупных сегментов. Это уменьшает количество сегментов, что означает, что в динамической памяти хранится меньше метаданных.

Mapping — это процесс определения того, как документ и содержащиеся в нем поля хранятся и индексируются. Каждый документ представляет собой набор полей, каждое из которых имеет свой собственный тип данных. При сопоставлении данных вы создаете описание маппинга, которое содержит список полей, относящихся к документу. Описание маппинга также включает поля метаданных, такие как поле _source, которые настраивают способ обработки связанных метаданных документа.

Каждое сопоставленное (mapped) поле также несет некоторые накладные расходы с точки зрения использования памяти и дискового пространства. Детали о каждом поле маппинга также содержатся в памяти, что часто бывает излишне. По умолчанию Elasticsearch автоматически создает маппинг для каждого поля в каждом индексируемом документе, но вы можете отключить это поведение.

Более того, каждый сегмент требует небольшого объема динамической памяти для каждого сопоставленного поля. Если шарды имеют большое количество сегментов, а соответствующие сопоставления содержат большое количество полей и/или очень длинные имена полей, то это придется учитывать.

ILM

Использование политик жизненного цикла индексов — хорошая практика, но нужно помнить, что когда происходит rollover индекса между дата тирами (например из hot в cold), это создает нагрузку как на ноду источник, так и на целевую ноду, куда идет запись индекса. Иногда это может мешать поиску.

Индексация

Когда вы индексируете документ, Elasticsearch сохраняет этот документ и делает его доступным для поиска. Документ — это просто объект JSON, содержащий пары ключ-значение данных. При каждом запросе PUT или POST через API эластика происходит запись документа и его индексация. Elasticsearch использует структуру данных, называемую инвертированным индексом, которая разработана для обеспечения очень быстрого полнотекстового поиска. Инвертированный индекс перечисляет каждое уникальное слово, которое встречается в любом документе, и идентифицирует все документы, в которых встречается каждое слово. В процессе индексации Elasticsearch сохраняет документы и создает инвертированный индекс, чтобы сделать данные документа доступными для поиска практически в реальном времени. Индексация инициируется с помощью API индекса, с помощью которого можно добавлять или обновлять документ JSON в определенном индексе.

Важно помнить, что обновление документов приводит к деградации производительности. Более предпочтительно удалять и пересоздавать документы.

Elasticsearch оптимизирован для массовой индексации, которая значительно эффективнее индексации отдельных документов. Вместо того, чтобы отправлять отдельные запросы для каждого документа, вы можете объединить несколько документов в один запрос.

Отключение merge throttling: троттлинг объединения — это автоматическая тенденция Elasticsearch троттлить запросы на индексацию, когда он обнаруживает, что объединение отстает от индексации. Имеет смысл отключить троттлинг, установив index.store.throttle.type в none, если вы хотите оптимизировать производительность индексации, а не поиска. Вы можете сделать это изменение постоянным (persistent, то есть оно сохранится после перезапуска кластера) или временным (transient, сбросится к значению по умолчанию после перезапуска) в зависимости от вашего варианта использования.

Увеличьте размер буфера индексации: параметр indices.memory.index_buffer_size определяет, насколько может заполниться буфер, прежде чем его документы будут записаны в сегмент на диске. Параметр по умолчанию ограничивает это значение 10% от общей кучи, чтобы зарезервировать больше кучи для обслуживания поисковых запросов, что не поможет вам, если вы используете Elasticsearch в первую очередь для индексации.

Сначала индексируйте, потом реплицируйте: при инициализации индекса укажите 0 реплик в настройках индекса и добавляйте реплики после завершения индексации, если это возможно, конечно. Если индексы создаются автоматически и часто, то такой вариант очень затруднителен с точки зрения автоматизации. Это повысит производительность индексации, но может быть рискованно, если нода, содержащийся единственную копию данных, выйдет из строя до того, как у вас появится возможность ее реплицировать.

Обновляйте реже: увеличьте интервал обновления индекса (операция, которая заключается в том, чтобы сделать изменения видимыми для поиска). По умолчанию процесс обновления индекса происходит каждую секунду, но в периоды интенсивной индексации уменьшение частоты обновления может помочь облегчить часть нагрузки.

Кеш файловой системы

При индексировании очень важны задержки при обращении к системе ввода-вывода. Кэш файловой системы будет использоваться для буферизации операций ввода-вывода. Убедитесь, что вы отдали не менее половины памяти машины, на которой запущен Elasticsearch, для кэша файловой системы.

Для оптимизации кэширования файловой системы мы можем изменить несколько параметров:

  • vm.dirty_background_ratio
  • vm.dirty_background_bytes
  • vm.dirty_ratio
  • vm.dirty_bytes
  • vm.dirty_writeback_centisecs
  • vm.dirty_expire_centisecs

Эти параметры управляют процентом общей системной памяти, которую мы можем использовать для кэширования. Они регулируют кэширующую память до того, как ядро ​​запишет грязные (dirty) страницы в хранилище. Важно, что грязные страницы — это страницы памяти, которые еще не записаны во вторичную память.

Грязные страницы можно посмотреть командой:

sysctl -a | grep dirty

Параметр vm.dirty_background_ratio — это объем системной памяти в процентах, который может быть заполнен грязными страницами перед их записью на диск. Например, если мы установим значение параметра vm.dirty_background_ratio системы с 64 ГБ ОЗУ равным 10, это означает, что 6,4 ГБ данных (грязных страниц) могут оставаться в ОЗУ перед их записью в хранилище.

В частности, vm.dirty_ratio — это абсолютный максимальный объем системной памяти в процентах, который может быть заполнен грязными страницами перед их записью на диск. На этом уровне все новые операции ввода-вывода останавливаются до тех пор, пока грязные страницы не будут записаны в хранилище.

Можно начать с того, что установить эти параметры следующим образом:

sudo sysctl -w vm.dirty_background_ratio=20
sudo sysctl -w vm.dirty_ratio=40

Также можно отредактировать системный файл /etc/sysctl.conf и после этого применить изменения:

sudo sysctl -p

Ну и на последок, не забудьте отключить swap:

swapoff -a

Был ли наш пост полезен?

Нажмите на звезду, чтобы оценить мои труды!

Средний рейтинг: 0 / 5. Количество голосов: 0

Пока голосов нет. Проголосуй первым!

Мне жаль, что пост вам не помог 🙁

Позвольте мне исправиться.

Поделитесь, что можно улучшить?

Похожие посты