![Автор иллюстрации — Anton Gudim](https://habrastorage.org/webt/ap/2k/jc/ap2kjcsehhaliahrmgg6a3r27xw.jpeg)
«Подготовленный человек тоже наступает на грабли.
Но с другой стороны — там, где ручка.»
Elasticsearch — прекрасный инструмент, но каждый инструмент требует не только настройки и ухода, но и внимания к мелочам. Некоторые — незначительны и лежат на поверхности, а другие спрятаны так глубоко, что на поиск уйдет не один день, не один десяток кружек кофе и не один километр нервов. В этой статье расскажу про девять замечательных граблей в настройке эластика, на которые я наступил.
Я расположу грабли по убыванию очевидности. От тех, которые можно предусмотреть и обойти на этапе настройки и ввода кластера в production state, до весьма странных, но приносящих самый большой опыт (и звёзды в глазах).
Data-ноды должны быть одинаковыми
«Кластер работает со скоростью самой медленной дата-ноды» — выстраданная аксиома. Но есть ещё один очевидный момент, не связанный с производительностью: эластик мыслит не дисковым пространством, а шардами, и старается равномерно распределить их по дата-нодам. Если на какой-то из дата-нод больше места, чем на других, то оно будет бесполезно простаивать.
Deprecation.log
Может случиться, что кто-то пользуется не самым современным средством отправки данных в эластик, которое не умеет ставить Content-Type при выполнении запросов. В этом списке, например, heka, или когда логи уходят с устройств их встроенными средствами). В таком случае deprecation. log начинает расти с устрашающей скоростью, и на каждый запрос в нём появляются такие строчки:
[2018-07-07T14:10:26,659][WARN ][o.e.d.r.RestController] Content type detection for rest requests is deprecated. Specify the content type using the [Content-Type] header.
[2018-07-07T14:10:26,670][WARN ][o.e.d.r.RestController] Content type detection for rest requests is deprecated. Specify the content type using the [Content-Type] header.
[2018-07-07T14:10:26,671][WARN ][o.e.d.r.RestController] Content type detection for rest requests is deprecated. Specify the content type using the [Content-Type] header.
[2018-07-07T14:10:26,673][WARN ][o.e.d.r.RestController] Content type detection for rest requests is deprecated. Specify the content type using the [Content-Type] header.
[2018-07-07T14:10:26,677][WARN ][o.e.d.r.RestController ] Content type detection for rest requests is deprecated. Specify the content type using the [Content-Type] header.
Запросы приходят, в среднем, каждые 5-10 мс — и каждый раз в лог добавляется новая строчка. Это негативно влияет на производительность дисковой подсистемы и увеличивает iowait. Deprecation.log можно выключить, но это не слишком разумно. Чтобы собирать логи эластика в него же, но не допускать замусоривания, я отключаю только логи класса o.e.d.r.RestController.
Для этого нужно добавить в logs4j2.properties такую конструкцию:
logger.restcontroller.name = org.elasticsearch.deprecation.rest.RestController
logger.restcontroller.level = error
Она повысит логи этого класса до уровня error, и они перестанут попадать в deprecation.log.
.kibana
Как выглядит обычный процесс установки кластера? Ставим ноды, объединяем их в кластер, ставим x-pack (кому нужен), ну и конечно, Kibana. Запускаем, проверяем, что всё работает и Кибана видит кластер, и продолжаем настройку. Проблема в том, что на свежеустановленном кластере шаблон по умолчанию выглядит примерно так:
{
"default": {
"order": 0,
"template": "*",
"settings": {
"number_of_shards": "1",
"number_of_replicas": "0"
}
},
"mappings": {},
"aliases": {}
}
И индекс .kibana, где хранятся все настройки создаётся в единственном экземпляре.
Как-то был случай, когда из-за аппаратного сбоя полегла одна из дата-нод в кластере. Он довольно быстро пришел в консистентное состояние, подняв реплики шард с соседних дата-нод, но, по счастливому совпадению, именно на этой дата-ноде располагался единственный шард с индексом .kibana. Ситуация патовая — кластер живой, в рабочем состоянии, а Kibana в red-статусе, и у меня разрывается телефон от звонков сотрудников, которым срочно нужны их логи.
Решается всё это просто. Пока ещё ничего не упало:
XPUT .kibana/_settings
{
"index": {
"number_of_replicas": "<количество_дата_нод>"
}
}
XMX/XMS
В документации написано — «No more than 32 GB», и это правильно. Но также правильно и то, что не нужно устанавливать в настройках сервиса
-Xms32g
-Xmx32g
Потому что это уже больше чем 32 гигабайта, и тут мы упираемся в интересный нюанс работы Java с памятью. Выше определённого предела Java перестаёт использовать сжатые указатели и начинает потреблять неоправданно много памяти. Проверить, использует ли сжатые указатели Java-машина с запущенным Elasticsearch, очень просто. Смотрим в лог сервиса:
[2018-07-29T15:04:22,041][INFO][o.e.e.NodeEnvironment][log-elastic-hot3] heap size [31.6gb], compressed ordinary object pointers [true]
Объем памяти, который не стоит превышать, зависит, в том числе, от используемой версии Java. Чтобы вычислить точный объем в вашем случае — смотрите документацию.
У меня сейчас на всех дата-нодах эластика установлено:
-Xms32766m
-Xmx32766m
Вроде бы банальный факт, и в документации хорошо описан, но регулярно в работе сталкиваюсь с инсталляциями Elasticsearch, где этот момент упущен, и Xms/Xmx выставлены в 32g.
/var/lib/elasticsearch
Это путь по-умолчанию для хранения данных в elasticsearch. yml:
path.data: /var/lib/elasticsearch
Туда я обычно монтирую один большой RAID массив, и вот почему: указываем ES несколько путей для хранения данных, например, так:
path.data: /var/lib/elasticsearch/data1, /var/lib/elasticsearch/data2
В data1 и data2 смонтированы разные диски или raid-массивы. Но эластик не делает балансировки и не распределяет нагрузку между этими путями. Сначала он заполняет один раздел, потом начинает писать в другой, поэтому нагрузка на хранилища будет неравномерной. Зная это, я принял однозначное решение — объединил все диски в RAID0/1 и смонтировал его в путь, который указан в path.data.
available_processors
И нет, я сейчас имею в виду не процессоры на ingest-нодах. Если заглянуть в свойства запущенной ноды (через _nodes API), можно увидеть примерно такое:
"os". {
"refresh_interval_in_millis": 1000,
"name": "Linux",
"arch": "amd64",
"version": "4.4.0-87-generic",
"available_processors": 28,
"allocated_processors": 28
}
Видно, что нода запущена на хосте с 28 ядрами, а эластик правильно определил их количество и запустился на всех. Но если ядер больше 32, то иногда бывает так:
"os": {
"refresh_interval_in_millis": 1000,
"name": "Linux",
"arch": "amd64",
"version": "4.4.0-116-generic",
"available_processors": 72,
"allocated_processors": 32
}
Приходится принудительно выставлять количество доступных сервису процессоров — это хорошо сказывается на производительности ноды.
processors: 72
thread_pool.bulk.queue_size
В разделе про thread_pool.bulk.rejected прошлой статьи была такая метрика — счётчик количества отказов по запросам на добавление данных.
Я писал о том, что рост этого показателя — очень плохой признак, и разработчики рекомендуют не настраивать пулы тредов, а добавлять в кластер новые ноды — якобы, это решает проблемы с производительностью. Но правила нужны и для того, чтобы иногда их нарушать. Да и не всегда получается «забросать проблему железом», поэтому одна из мер борьбы с отказами в bulk запросах — поднять размеры этой очереди.
По умолчанию настройки очереди выглядят так:
"thread_pool":
{
"bulk": {
"type": "fixed",
"min": 28,
"max": 28,
"queue_size": 200
}
}
Алгоритм такой:
- Собираем статистику по среднему размеру очереди в течение дня (мгновенное значение хранится в thread_pool.bulk.queue);
- Аккуратно увеличиваем queue_size до размеров чуть больших, чем средний размер активной очереди — потому что отказ возникает при его превышении;
- Увеличиваем размер пула — это не обязательно, но приемлемо.
Для этого добавляем в настройки хоста что-то наподобие такого (значения у вас, конечно, будут свои):
thread_pool.bulk.size: 32
thread_pool.bulk.queue_size: 500
И после перезапуска ноды обязательно мониторим нагрузку, I/O, потребление памяти. и всё что можно, чтобы при необходимости откатить настройки.
Важно: эти настройки имеют смысл только на нодах работающих на приём новых данных.
Предварительное создание индексов
Как я говорил в первой статье цикла, мы используем Elasticsearch для хранения логов всех микросервисов. Суть проста — один индекс хранит логи одного компонента за один день.
Из этого следует, что каждые сутки создаются новые индексы по числу микросервисов — поэтому раньше каждую ночь эластик впадал в клинч примерно на 8 минут, пока создавалась сотня новых индексов, несколько сотен новых шардов, график нагрузки на диски уходил «в полку», вырастали очереди на отправку логов в эластик на хостах, и Zabbix расцветал алертами как новогодняя ёлка.
Чтобы этого избежать, по здравому размышлению был написан скрипт на Python для предварительного создания индексов. Скрипт работает так: находит индексы за сегодня, извлекает их маппинги и создаёт новые индексы с теми же маппингами, но на день вперёд. Работает по cron, запускается в те часы, когда Эластик наименее загружен. Скрипт использует библиотеку elasticsearch и доступен на GitHub.
Transparent Huge Pages
Как-то раз мы обнаружили, что ноды эластика, которые работают приём данных, начали зависать под нагрузкой в пиковые часы. Причём с очень странными симптомами: использование всех процессорных ядер падает до нуля, но тем не менее сервис висит в памяти, исправно слушает порт, ничего не делает, на запросы не отвечает и через какое-то время вываливается из кластера. На systemctl restart сервис не реагирует. Помогает только старый-добрый kill −9.
Стандартными средствами мониторинга это не отлавливается, на графиках до самого момента падения штатная картина, в логах сервиса — пусто. Дамп памяти java-машины в этот момент сделать тоже не удавалось.
Но, как говорится, «мы же профессионалы, поэтому спустя какое-то время нагуглили решение». Похожая проблема освещалась в треде на discuss.elastic.co и оказалась багом в ядре, связанным с tranparent huge pages. Решилось всё выключением thp в ядре, с помощью пакета sysfsutils.
Проверить, включены ли у вас transparent huge pages, просто:
cat /sys/kernel/mm/transparent_hugepage/enabled
always madvise [never]
Если там стоит [always] — вы потенциально в опасности.
Заключение
Это основные грабли (на деле их было, конечно, больше), на которые мне довелось наступить за полтора года работы в качестве администратора кластера Elasticsearch. Надеюсь, эта информация пригодится вам на сложном и загадочном пути к идеальному кластеру Elasticsearch.
А за иллюстрацию спасибо Anton Gudim — в его инстаграме еще много хорошего.