Девять граблей Elasticsearch, на которые я наступил

в 11:06, , рубрики: big data, data-node, elasticsearch, gudim, xms, xmx, Блог компании Яндекс.Деньги, кластер, Серверное администрирование, системное администрирование

Автор иллюстрации — Anton Gudim

«Подготовленный человек тоже наступает на грабли.
Но с другой стороны — там, где ручка.»

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
	}
}

Алгоритм такой:

  1. Собираем статистику по среднему размеру очереди в течение дня (мгновенное значение хранится в thread_pool.bulk.queue);
  2. Аккуратно увеличиваем queue_size до размеров чуть больших, чем средний размер активной очереди — потому что отказ возникает при его превышении;
  3. Увеличиваем размер пула — это не обязательно, но приемлемо.

Для этого добавляем в настройки хоста что-то наподобие такого (значения у вас, конечно, будут свои):

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 — в его инстаграме еще много хорошего.

Автор: adel-s

Источник

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


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