Масштабируем Elasticsearch на примере кластера с индексами в несколько терабайт

в 11:01, , рубрики: big data, elasticsearch, Администрирование баз данных, Серверное администрирование

Низкая скорость поисковых запросов

Работая над поисковым движком по социальной информации (ark.com), мы остановили свой выбор на Elasticsearch, так как по отзывам он был очень легок в настройке и использовании, имел отличные поисковые возможности и, в целом, выглядел как манна небесная. Так оно и было до тех пор, пока наш индекс не вырос до более-менее приличных размером ~ 1 миллиарда документов, размер с учетом реплик уже перевалил за 1,5 ТБ.

Даже банальный Term query мог занять десятки секунд. Документации по ES не так много, как хотелось бы, а гуглинг данного вопроса выдавал результаты 2х-летней давности по совсем не актуальным версиям нашего поискового движка (мы работаем с 0.90.13 — что тоже не достаточно старая вещь, но мы не можем позволить себе опустить весь кластер, обновить его, и запустить заново на текущий момент — только роллинг рестарты).

Низкая скорость индексации

Вторая проблема — мы индексируем больше документов в секунду (порядка 100к), чем Elasticsearch может обрабатывать. Тайм-ауты, огромная нагрузка на Write IO, очереди из процессов в 400 единиц. Все выглядит очень страшно, когда смотришь на это в Marvel.

Как решать эти проблемы — под катом

Масштабируем кластер Elasticsearch

Исходная ситуация:

  • 5 data nodes, http enabled:
    • 100 GB RAM
    • 16 cores
    • 4 TB HDD (7200 RPM, seagate)

  • Индексы:
    • от 500 до 1 млрд документов, всего 5 штук
    • количество primary шардов от 50 до 400 (здесь мы тестировали разные стратегии индексирования — эта настройка очень важна)
    • реплики — от 2 до 5
    • размер индекса до 1,5 терабайт

Увеличиваем скорость индексирования в Elasticsearch

Эта проблема оказалось не такой сложной и информации в интернете по ней чуть больше.

Чеклист, который нужно проверить:

  • refresh_interval — как часто обновляются данные для поиска, чем чаще, тем больше Write IO вам требуется
  • index.translog.flush_threshold_ops — через сколько операций скидывать данные на диск
  • index.translog.flush_threshold_size — сколько данных должны быть добавлены в индекс перед скидыванием на диск

Подробная документация здесь: www.elasticsearch.org/guide/en/elasticsearch/reference/current/indices-update-settings.html

В первую очередь мы увеличили refresh_interval до 30 секунд, и фактически увеличили пропускную способность практически до 5000 документов в секунду. Позже поставили flush_threshold_ops в 5000 операций, а размер до 500 мб. Если хотите, то можно поиграться с количеством реплик, шардов и так далее, но это не будет давать настолько большой разницы. Так же обратите внимание на threadpool, если вам необходимо увеличить количество параллельных запросов к базе, хотя чаще всего этого не требуется.

Увеличиваем скорость запросов в Elasticsearch

Теперь переходим к сложной части. Зная размер нашего индекса и постоянные потребности в перезагрузке кластера (обновления версий, мейнтенанс машин), а также принимая во внимание посты вроде этого: gibrown.wordpress.com/2014/02/06/scaling-elasticsearch-part-2-indexing/ мы решили, что размер шарда в нашем индексе не будет превышать 1-2 ГБ. С учетом RF3, наш индекс (мы рассчитываем на 1,5 млрд документов), учитывая что 0,5 млрд наших документов занимают порядка 300 ГБ без учета реплик, мы создали в индексе 400 шардов и посчитали что все будет хорошо — скорость ребута будет достаточно высока: нам не нужно будет читать блоки данных по 50-60 ГБ, а также реплицировать их, блокируя таким образом восстановление маленьких шардов, да и скорость поиска по маленьким шардам выше.

По началу, количество документов в индексе было небольшим (100-200 млн) и скорость запроса составляла всего 100-200 мс. Но как только практически все шарды были заполнены хотя бы небольшим количеством документов, мы начали значительно терять в производительности запросов. Комбинируя все это с высокой нагрузкой на IO из-за постоянной индексации, мы могли и вообще не выполнить его.

В данном случае мы совершили 2 ошибки:

1. Создали очень много шардов (идеальная ситуация 1 ядро — 1 шард)
2. Наши дата ноды были и нодами-балансерами с включенным http — сериализация и десериализация данных занимает достаточно много времени

Поэтому мы начали экспериментировать.

Добавялем ноды-балансировщики в Elaticsearch

Первым и очевидным шагом для нас было добавлением, так называемых, balancer nodes в Elasticsearch. Они могут производить агрегированние результатов запросов по другим шардам, у них никогда не будет перегружен IO, так как они не выполняют чтения и записи на диск, и мы разгрузим наши data nodes.

Для деплоя мы используем chef и соответствующий elasticsearch cookbook, поэтому создав всего пару дополнительных ролей, со следующими настройками:

name "elasticsearch-balancer"
description "Installs and launches elasticsearch"

default_attributes(
	"elasticsearch" => {
		"node" => {
			"master" => false,
			"data" => false
		}
	}
)

run_list("services::elasticsearch")

Мы благополучно запустили 4 балансировщика. Картина немного улучшилась — мы больше не наблюдали перегруженных нод с дымящимися жесткими дисками, но скорость запросов была все еще низка.

Увеличиваем количество data nodes в Elasticsearch

Теперь мы вспомнили, что количество шардов, которое было у нас (400) никоим образом не сказывается на улучшении производительности, а лишь усугубляет ее, так как слишком больше количество шардов находится на 1 машине. Проведя простые вычисления мы получаем, что 5 машин адекватно поддержат только 80 шардов. Учитывая количество реплик, то их у нас вообще 1200.

Так как наш общий парк машин (80 нод) позволяет добавление достаточно большого количества нод и основная проблема в них — это размер HDD (всего 128гб), то мы решили добавить сразу порядка 15 машин. Это позволит работать с еще 240 шардами более эффективно.

Помимо этого мы наткнулись на несколько любопытных настроек:

* index.store.type — по умолчанию ставится в niofs, а по бенчмаркам производительность ниже чем у mmapfs — мы переключили его на mmapfs (дефолтный стор в 1.x)
* indices.memory.index_buffer_size — увеличили до 30%, а количество RAM под Java Heap наоборот уменьшили до 30 ГБ (было 50%), так как с mmapfs нам нужно намного больше оперативки для кеша операционной системы

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

curl -XPUT localhost:9200/_cluster/settings -d '{
    "transient" : {
        "cluster.routing.allocation.disk.threshold_enabled" : true
    }
}'

После пары дней переноса шардов и перезапуска старых серверов с новыми настройками, мы провели тесты и не кешированные запросы (Term Query, не фильтры) выполнялись не более 500 мс. Данная ситуация все еще не идеальна, но мы видим, что добавление дата нод и подгон количества ядер под количество шардов исправляет ситуацию.

Что еще следует учесть при масштабировании кластера

При роллинг рестарте кластера, обязательно выключайте возможность переноса шардов: cluster.routing.allocation.enable = none, в старых версиях чуть другая настройка.

Если возникли вопросы во время прочтения — буду рад обсудить.

Автор:

Источник

* - обязательные к заполнению поля


https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js