Шпаргалка по оптимизации индексирования в Elasticsearch

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

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

Производительность

Прежде чем приступить, есть смысл последовать совету из документации по настройке{:target=»blank»} Elasticsearch. В общем:

  • Установите максимальный размер открытых дескрипторов файла для пользователя от 32k до 64k
  • Если возможно, отключите свопинг памяти для процессов Elasticsearch. Обратите внимание, что в виртуальных средах это может привести к неожиданным результатам.
  • Установите значение -Xms равным -Xmx (то же самое что установить значение переменной среды ES_HEAP_SIZE).
  • Оставьте некоторое количество памяти, чтобы кеш операционной системы мог использовать его для Lucene.

Главное правило — Elasticsearch JVM не должен занимать больше половины всего объема памяти.

Маппинг

  • По умолчанию Elasticsearch хранит оригинальные данные в поле _source_field. Если вам они не нужны, то это можно отменить
  • По умолчанию Elasticsearch анализирует входные данные всех полей и хранит их в поле _all_field. Это так же можно отключить
  • Если вы используете _source_field, то нет смысла устанавливать поля _stored
  • Если вы не используете _source_field, то устанавливайте в _stored только необходимые вам поля.

Отмечу, использование _source привносит некоторые преимущества, например, доступность использования API для обновления

  • Задумайтесь, насколько вам необходимы нормы{:target=»blank»} (norms) для анализируемых полей. Если в них нет необходимости, то отключите их, установив norms.enabled в false
  • Используйте самый простой анализатор, который соответствует вашим требованиям. А может и вообще не надо анализировать?
  • Не анализируйте, не храните и вообще даже не посылайте данные в Elasticsearch, если они вам не нужны в поиске.

Запросы и клиенты

Можно получить значительное прибавление к производительности путем оптимизации способа передачи запросов к Elasticsearch:

  • Надо ли вам посылать отдельные запросы для каждого документа? Или же можно сохранять документы в буфере, а потом индексировать их одним запросом при помощи bulk API{:target=»blank»}
  • Оптимизируйте размер bulk запроса (пакетный запрос), то есть определите количество документов для одного запроса. Обычно это число получает тестовым методом. Только таким образом можно учесть все факторы, влияющие на скорость работы
  • Рассмотрите существующие клиенты{:target=»blank»}, так как они могут значительно превосходить HTTP
  • Если ваш клиент работает на Java, то попробуйте NodeClient{:target=»blank»}. Он подключается к кластеру и определяет, к каким узлам далее подключаться по определенным запросам, что может сэкономить время при ошибочном выборе узла. Если же ограничения безопасности не позволяют вам использовать NodeClient, то следующим рассмотрите TransportClient{:target=»blank»}
  • Можете распараллелить индексацию путем использования нескольких клиентов. Может именно использование одного клиента и является узким местом в работе.

Шардинг и Репликация

Рекомендованный метод для масштабирования Elasticsearch является шардинг(сегментирование) и репликация. Весь необходимый для этого функционал уже есть в Elasticsearch. Есть пара моментов, которые стоит рассмотреть:

  • Если вам не хватает одного сервера для обеспечения желаемой производительности, следует рассмотреть вариант масштабирования системы. Несколько узлов в кластере позволяют распараллелить индексирование путем сегментации. Количество сегментов должно быть установлено при создании индекса{:target=»blank»}, в дальнейшем это число неизменно. В случае, когда объем данных заранее неизвестен, можно рассмотреть перенос нескольких сегментов (но не много, так как это не бесплатная операция), чтобы иметь несколько в запасе. В качестве альтернативы можно использовать псевдонимы индексов{:target=»blank»}.
  • Репликация также является отличным методом борьбы со сбоями, но чем больше реплик вы используете, тем медленнее будет проходить индексация. Хотя для максимального быстродействия стоит полностью отказаться от реплик. В отличие от сегментов, количество реплик можно изменять в любое время, что дает нам ряд вариантов использования. В некоторых случаях, например генерация нового индекса или перенос данных из одного индекса в другой, есть смысл начать вовсе без реплик, добавляя их после окончания критических стадий индексирования.
  • Старайтесь отделить узлы данных{:target=»blank»} (которые действительно хранят и индексируют данные) от агрегатных узлов(которые отвечают за выборку данных). Когда агрегатные узлы используются только для обработки поисковых запросов, они лишний раз не обращаются к узлам с данными, таким образом последние могут больше ресурсов уделить индексированию.
  • По умолчанию индексирующий запрос считается оконченным только после того, как данные были удачно получены каждой репликой. Если установить параметр query replication в async{:target=»blank»}, то запрос будет считаться законченным, после того как данные будут получены главным сегментом.

Настройки индекса

Существует несколько уровней настроек для индекса, которые позволяют улучшить производительность:

  • По умолчанию индексный сегмент обновляет данные каждую секунду, то есть новые документы появляются в индексе с интервалом в одну секунду. Хотя с первого взгляда операция может показаться не очень ресурсоемкой, но она оказывает свое влияние на быстродействие. Таким образом, в зависимости от требований к системе, стоит рассмотреть повышение интервала обновления. Иногда имеет смысл полностью отключить обновление{:target=»blank»} на время индексации, а после её окончания включить его обратно{:target=»blank»}.
  • По сравнению с обновлением, сохранение лога транзакций выглядит действительно ресурсоемким процессом. Elasticsearch запускает процесс сохранения основываясь на ряде триггеров, которые можно изменять в процессе работы. Задерживая сохранения или отменяя его полностью, вы можете увеличить скорость индексирования. Помните о том, что в итоге задержанное сохранение займет больше времени, чем плановое.
  • Политика объединения по умолчанию поддерживает составной формат данных{:target=»blank»}, то есть данные хранятся в меньшем количестве файлов для сокращения числа открытых файлов. Но при таком подходе мы получим небольшое понижение производительности. Существует два параметра, index.compound_on_flush и index.compount_format, которые указывают должен ли такой подход быть применен к новым сегментам или объединённым соответственно. При установке обоих параметров в false мы получим прирост производительности, но будем иметь большее число открытых файлов
  • Объединение сегментов выполняется в фоновом режиме, но требует I\O операций, что влияет на производительность. Допускается увеличение числа{:target=»blank»} максимального количества байт в секунду при объединении на уровне узла или индекса. Обратите внимание, что это уже происходит по умолчанию, но может стоит изменить ограничения в зависимости от ваших потребностей.
  • Параметр indices.memory.index_buffer_size определяет процентное соотношение разрешённого к использованию объема динамической памяти для операций индексирования (оставшийся объем будет использован для операций поиска). По умолчанию это значение равно 10%, что может оказаться недостаточным при больших объемах индексации{:target=»blank»}.
  • Разогрев индекса полезен для ускорения поисковых запросов, но при индексации больших объемов данных (особенно при групповой индексации), есть смыл вовсе отключить эту функцию.
  • Увеличьте размер пула потоков на уровне узла{:target=»blank»} для обычной и групповой индексации. (Замерьте изменения производительности).
  • Параметр index.index_concurrency ограничивает количество параллельных индексирующих потоков для одного сегмента. Увеличьте это значение, особенно если в узле нет других сегментов. (Сравните результат).

Заключение

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