На одном крупном проекте мы, инженеры компании «Инфосистемы Джет», столкнулись с типичной проблемой стандартных инсталляций Zabbix на больших объемах - производительностью и низкой отказоустойчивостью базы данных. Конфигурация Zabbix была следующей:

  • один Zabbix-сервер;

  • множество прокси;

  • сервер БД PostgreSQL с расширением TimescaleDB;

  • сервер Grafana для визуализации данных.

При обычной нагрузке (12000 NVPS) система работала стабильно, но стоило произойти массовой аварии на инфраструктуре или перезагрузке сервера/прокси, как производительности БД не хватало. В такие моменты очень быстро накапливались очереди обработки данных, заканчивались кэши – система фактически прекращала работу. Непростую ситуацию ухудшали еще ложные срабатывания (данные не всегда могли попасть в БД) и рассылка уведомлений ответственным администраторам, проверявшим состояние систем в WEB-интерфейсе. Для восстановления работы приходилось перезапускать компоненты друг за другом, контролируя нагрузку на БД.

Проблему оперативно решили при помощи снижения количества чанков для хранения трендов. Причина происходящего крылась в некорректном партиционировании трендовых данных. Детально о проблеме и методах решения можно почитать в баг-репорте производителя (ZBX-16347). Он помог нам в устранении аварии, но ограничиваться только им не стали – одного репорта, на наш взгляд, было недостаточно. Мы стали смотреть шире и задумались над альтернативными решениями.

А какие варианты есть?

Начнём с того, что наибольшая нагрузка на БД в Zabbix создается на операциях с историческими данными и происходящими в мониторинге событиями. Это таблицы: history, history_uint, history_text, history_str, history_log, events, problems. Производитель предлагает использовать следующие БД: MySQL, PostgreSQL и Oracle DB. Кроме того, исторические данные можно отправлять и в Elasticsearch.

Помимо перечисленных, существует еще три альтернативных варианта:

  1. Линейное увеличение ресурсов.

  2. Вынесение исторических данных.

  3. Использование распределенной БД.

Первый вариант нам неинтересен с инженерной точки зрения. Помимо этого, судя по нашему опыту, чаще всего из OLTP БД используется PostgreSQL. Поэтому мы получили такой итоговый список тестируемых вариантов:

  • PostgreSQL с расширением fdw;

  • Elasticsearch;

  • TimescaleDB multi-node instance;

  • Clickhouse.

PostgreSQL с расширением fdw

Стандартный механизм шардирования в PostgeSQL хорошо описан, как и миграция данных в него. Шардировать данные можно «вертикально» (создаем партиции в рамках одной исталляции БД) либо «горизонтально» (размещаем всё на разных машинах). В нашем случае именно горизонтальное шардирование оказалось нам полезным. Для создания такого кластера мы использовали встроенное расширение - postgres_fdw (foreign data wrapper) – набор данных на каждом сервере в виде одной большой таблицы. Критически важным для такого решения является выбор критерия шардирования – поле, значение которого будет использоваться для распределения данных по шардам.

В случае с Zabbix таблицы с историей содержат поля clock (timestamp), ns, value, itemid. И на этом этапе у нас возникли трудности: прибегать к значению clock нельзя из-за уникальности, использование itemid потребует написание инструмента для динамического изменения правил шардирования. Вывод: реализовать предложенное решение без существенной модификации кода сервера мониторинга оказалось проблематичным. Поэтому мы отказались от этого варианта.

TimescaleDB multi-node instance

В теории TimescaleDB в конфигурации Multi-node instance позволяет создавать распределённые гипертаблицы под данные и настраивать репликацию стандартными средствами этого расширения. Схематично это выглядит так:

Access нода (AN) занимается распределением данных по Data нодам (DN) и их координацией. Для репликации данных можно прибегать к HA конфигурации (High Availability), когда каждая нода имеет свой standby сервер, либо использовать «Native replication» (нативная репликация) timescaledb. Её механизм создаёт копии чанков с данными на разных дата нодах и сохраняет доступ к данным при потере узла. Количество реплик зависит от параметра replication_factor, который назначается каждой распределённой гипертаблице.

SELECT set_replication_factor(history, 2);

В отличии от обычных гипертаблиц, для их распределённой версии необходимо использовать какое-то дополнительное поле, чтобы раскладывать данные по разным шардам (разработчики timescaledb называют это space-partitioning ). В противном случае, данные будут накапливаться в одном чанке, пока он не заполнится.

Проблема данной конфигурации оказалась схожа с предыдущим вариантом. Использование itemid привело к лавинообразному росту количества пакетов и деградации производительности системы, что убило весь эффект от расчёта на разных машинах. Ко всему прочему, такие операции обычно требуют большого количества памяти.

При этом сам Zabbix прекрасно работает с tsdb в single-node инсталляции, что позволяет получать значительный прирост производительности и задействовать механизм сжатия.

Вывод: построить можно, но неразумно. Идем дальше.        

Elasticsearch

Zabbix имеет встроенную поддержку elastic, поэтому можно выбрать какой тип данных (int, float, text, log, char) будет отправляться в это хранилище, а какой храниться в реляционной СУБД. В целом, это решение доступно нам из коробки, а сам стек понятен и хорошо обкатан.

К его минусам можно отнести требовательность к вычислительным ресурсам - мы не храним и не генерируем тренды. Размер занимаемого дискового пространства резко вырастал, в отличии от других БД. Наличие метаданных в индексах тоже увеличивало их размер.

Кроме этого, при построении стенда мы столкнулись некорректным отображением данных в Grafan’е. Дело в том, что в Zabbix API метод history.get в случае с Elasticsearch отдает int и float типы вместо string, которые не могут быть интерпретированы плагином интеграции между Zabbix и Grafana. Эту проблему мы обошли, модифицировав API вызов и добавив принудительное преобразование полученных значений в строки.

Что же отличает Elasticsearch от других исследуемых решений при распределении данных по шардам кластера? Всё достаточно просто. Для того, чтобы понять, в какой шард «положить» полученный документ, Elasticsearch применяет hash функцию к _id каждого создаваемого документа и делит по модулю полученный результат на количество шардов. Таким образом он однозначно определяет, на каком шарде он будет находиться. Эта же формула используется и при поиске данных (именно поэтому мы не можем динамически менять количество шардов в уже созданных индексах).

routing_factor = num_routing_shards / num_primary_shards

shard_num = (hash(_routing) % num_routing_shards) / routing_factor

На тестах с Elasticsearch мы смогли вынести в отдельную систему всю историю и значительно «разгрузить» нашу основную БД.

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

Сlickhouse

Готовой интеграции с Zabbix нет, но нас привлекли возможности этой СУБД. Она легковесная, столбцовая; поддерживает сжатие данных, hot , warn зоны, SQL-подобный язык; обрабатывает запросы на многих серверах; имеет встроенные механизмы шардирования и может реплицировать данные через zookeeper (красота, не правда ли!). Помимо перечисленных достоинств, синтетические тесты clickhouse демонстрировали наилучшие результаты при работе с дисковыми устройствами, в отличии от своих «конкурентов».

Тем не менее такая производительность сопровождается существенными недостатками:

  • низкая производительность операций вставки при работе с маленькими объёмами данных (менее 300 строк за один раз);

  • поддерживаются не все конструкции языка SQL или используется нестандартный синтаксис;

  • отсутствуют механизм транзакций, хранимые процедуры и пользовательские функции;

  • низкая производительность операций модификации и удаления данных из колонок[1];

  • невозможно сделать консистентный бекап без полной остановки БД.

Несмотря на недостатки этой БД в контексте нашей проблемы, возможности системы показались нам привлекательными, поэтому мы разработали интеграцию CH с Zabbix и провели сравнительное тестирование с другими вариантами.

Что получилось?

Мы собрали три стенда: трехузловой кластер ClickHouse, трехузловой кластер Elasticsearch и PostgreSQL c расширением TimescaleDB. На стенд заливались исторические данные с реальной инсталляции Zabbix, датасет составил чуть больше 1 ТБ. Количество генерируемых значений в секунду достигало 10322 NVPS. На таких объёмах мы увидели, что CH создал примерно в 5 раз меньше дисковых операций по сравнению с Elasticsearch и при этом немного обогнал TimescaleDB. Стоит учитывать, что это средние показатели и в процессе тестирования наблюдались пики: до 20к IOps для TSDB, 16к для CH и примерно 45к для Elasticsearch. Такие значения мы фиксировали кратковременно, и в основном они возникали при активной работе с данными внутри кластеров.

Затем мы протестировали компрессию: чем ниже значения, тем лучше для нас, потому что данные занимают меньше места. Из таблицы ниже видно, что CH продемонстрировал лучшие результаты, но TimescaleDB не критично отстал от него. TimescaleDB сжимал данные на уровне с CH, однако у TimescaleDB индексы оказались чересчур большими, что не позволило ему обогнать CH. Для CH и Elasticsearch мы прибегали к сжатию LZ4, для TSDB использовали разные алгоритмы в зависимости от данных.

«Сырые данные»

Clickhouse

Elasticsearch

TSDB

1ТБ

1,1ТБ

1,8ТБ

1,4ТБ

Дополнительно мы замерили среднее время ответа БД со стороны Zabbix сервера. В данном тесте большое влияние оказал размер патча данных, которые записывались/считывались из системы. Стоит отметить, что на небольших значениях (100-300 строк) tsdb обошел CH по задержкам.

В результате мы получили полноценную интеграцию Zabbix и clickhouse, рабочее API и возможность управлять системой из web интерфейса. Кроме того, это стало хорошим опытом по интеграции новых хранилищ данных, и у нас появились планы по подключению других БД.

P.S. Наш выбор БД был продиктован доступными решениями на тот момент. Поэтому проводить прямое сравнение OLAP, OLTP и документно-ориентированных БД некорректно. Очевидный, но такой актуальный совет: выбирайте решение под конкретную задачу.

[1] Для удаления лучше использовать пачки данных

Комментарии (3)


  1. dnbstd
    26.09.2023 10:36
    +3

    Коллеги, а вы не рассматривали https://glaber.io/ ? Тот же заббикс с СH. Только еще допиленный.


    1. JetHabr Автор
      26.09.2023 10:36

      Рассматривали, не совсем понравилась реализация. Когда изучали вопрос, не увидели реализации буфера под данные перед записью. На больших инсталляциях это приводило бы к высокой нагрузке на стороне СУБД в процессе объединения засечек.


      1. makurov
        26.09.2023 10:36
        +2

        В документации прямо в настройке коннектора клика есть настройка буфферизации:

        https://docs.glaber.io/ru/setup/history/clickhouse/#_2

        HistoryModule=clickhouse;{"url": "http://127.0.0.1:8123?user=default&password=XXXX", "batch":100000, "flush":"30", "disable_reads":100, "timeout":5, "write_types":"dbl, str, uint, text", "max_calls":10000000 }

        Опции batch и flush управляют размером буффера.

        По опыту, даже при 400-500к Nvps существеноой нагрузки на клик не будет