Низкая скорость поисковых запросов
Работая над поисковым движком по социальной информации (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
, в старых версиях чуть другая настройка.
Если возникли вопросы во время прочтения — буду рад обсудить.
Автор: