Как мигрировать Zabbix с MySQL на PostgreSQL с минимальным downtime +15



image


В свете того, что Zabbix с некоторых пор поддерживает TimescaleDB, а тут еще и вышел новый LTS релиз Zabbix, то наверняка многие заинтересовались, как осуществить миграцию с MySQL на PostgreSQL.


Несмотря на текст на картинке, вполне можно просто так взять и мигрировать Zabbix с MySQL на PostgreSQL. В интернете есть немало рецептов такой миграции, например:


Доклад с Zabbix Meetup
Видео с Youtube канала Dmitry Lambert


Но я не нашел ни в одном из таких докладов информации, как же минимизировать downtime вашего мониторинга в случае, если полная миграция занимает несколько часов, что конечно зачастую является неприемлемым.


Ниже я опишу свое решение данной проблемы и те подводные камни, которые пришлось обходить по пути.


Важно упомянуть, что я до сих использую Zabbix 4.0. Возможно, в новых версиях схема БД поменялась и поэтапная миграция, описанная ниже, там невозможна.


Основная идея


Как известно, основной объем базы данных Zabbix сервера занимают исторические данные: таблицы истории и трендов (далее для краткости я буду называть эти данные историей). При этом сам Zabbix сервер способен без проблем запуститься и без этих данных. Сразу же возникает мысль: а что если при миграции для начала проигнорировать историю, запустить мониторинг, а уже затем разобраться с историей?


Подготовка


Я не буду по ходу статьи описывать все команды, например, как установить PostgreSQL. Думаю, в этой статье оно ни к чему, да и каждый сам может с этим справиться.


Установим нужное ПО:


  • PostgreSQL, в моем случае версию 12.
  • PgLoader 3.6.2. На версии 3.6.1 я мигрировать не смог из-за возникающих проблем.

Необходимо настроить PostgreSQL сервер и операционную систему, на которой он установлен.
Я также не буду подробно останавливаться на этой теме, так как пост не о тюнинге производительности, скажу лишь:



Также нужно до начала миграции настроить мониторинг PostgreSQL.
Так как я еще не переехал с версии Zabbix 4.0, то новеньким официальным шаблоном воспользоваться не могу, но мне отлично подошел вот этот:
https://github.com/lesovsky/zabbix-extensions/tree/master/files/postgresql


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


Ну и обязательно делайте резервные копии и проверяйте весь процесс на тестовом окружении. Собственно, все приключения я получил именно на нем.


Последний момент подготовки перед миграцией — останавливаем Zabbix server:


systemctl stop zabbix-server

Первый шаг миграции


Для начала, как и в любом другом мануале по миграции Zabbix, разделим файл schema.sql, расположенный в директории database/postgresql исходников zabbix, на 2 части:
в одной у нас будут CREATE, в другой ALTER.


На выходе имеем 2 файла: create.sql, alter.sql


Применим файл create.sql к нашей БД:


cat create.sql | psql -Uzabbix zabbix 

Подготовим файл для pgloader — zabbix.load.config


LOAD DATABASE
 FROM mysql://zabbix:zabbix-password@localhost/zabbix
 INTO postgresql://zabbix:zabbix-password@192.168.1.1:5432/zabbix
WITH include no drop,
truncate,
create no tables,
create no indexes,
no foreign keys,
reset sequences,
data only
SET MySQL PARAMETERS
 max_execution_time = '0'
SET PostgreSQL PARAMETERS
 maintenance_work_mem TO '1024MB', work_mem to '128MB'
EXCLUDING TABLE NAMES MATCHING ~/history.*/, ~/trend.*/
ALTER SCHEMA 'zabbix' RENAME TO 'public';

Обратите внимание на строку


EXCLUDING TABLE NAMES MATCHING ~/history.*/, ~/trend.*/

с ее помощью мы пропускаем таблицы истории


Запустим миграцию:


pgloader zabbix.load.config

Ждем буквально минуту, проверяем, что ошибок при миграции нет (символ галки во втором столбце):


image


Применим файл alter.sql к нашей БД:


cat alter.sql | psql -Uzabbix zabbix 

Полагаю, важно, что в файле alter.sql нет никаких упоминаний таблиц trend и history. По крайней мере в версии 4.0 именно так обстоят дела. Наверное, при ином раскладе были бы проблемы, т.к. в существующих в сети мануалах по миграции данные сначала загружаются в БД, а потом применяется alter.sql


Удалим пакеты Zabbix, связанные с MySQL и заменим их теми, что нужны для PostgreSQL. На этот раз я приведу примеры для CentOS 7, чтобы было понятно, какие пакеты нам нужны:


yum remove zabbix-server-mysql zabbix-web-mysql
yum install zabbix-server-pgsql zabbix-web-pgsql php-pgsql

А лучше смотрите официальную документацию по установке для вашей ОС:
https://www.zabbix.com/documentation/4.0/manual/installation/install_from_packages


Сбросим настройки Web интерфейса Zabbix, чтобы заново пройти его настройку:


rm /etc/zabbix/web/zabbix.conf.php

Укажем часовой пояс в файле:


/etc/httpd/conf.d/zabbix.conf

Настроим новый zabbix_server.conf: укажем нужные значения в директивах DBHost, DBPort, DBUser, DBName, DBPassword.


Настала пора запустить наш Zabbix Server!


systemctl start zabbix-server

Идем по адресу web-интерфейса zabbix (http(s)://ip/zabbix), проходим по шагам мастера.


После настройки web-интерфейса вы увидите, что все ваши узлы, шаблоны, группы и т.п. будут на месте. Не будет только исторических данных. Вы можете в этом убедиться, посмотрев любые графики за прошлые периоды — там пусто.


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


Второй шаг миграции


Теперь надо просто запустить миграцию истории, но чтобы этот процесс не сильно нагружал СУБД.


Так думал я, но все оказалось интереснее.


Перед вторым шагом миграции я решил создать в моем тестовом окружении нагрузку на Zabbix сервер. В тестовом окружении отсутствовали Zabbix прокси (это логично, так как мои основные прокси с тестовым сервером работать не намерены), так что нагрузку я решил создавать на самом сервере. Для этого с помощью API я создал около 200 хостов и попросил Zabbix сервер совершать проверки icmp и web checks к этим хостам каждую секунду. Получил около 1000 NVPS.


Очереди не росли, Zabbix server и PostgreSQL легко справлялись с такой нагрузкой.


Пора мигрировать историю.


Подготовим файл zabbix.load.data для pgloader:


LOAD DATABASE
 FROM mysql://zabbix:zabbix-password@localhost/zabbix
 INTO postgresql://zabbix:zabbix-password@192.168.1.1:5432/zabbix
WITH include no drop,
no truncate,
create no tables,
create no indexes,
no foreign keys,
reset sequences,
data only,
prefetch rows = 5000,
multiple readers per thread

SET MySQL PARAMETERS
 max_execution_time = '0',
 net_read_timeout = ‘86400’,
 net_write_timeout = ‘86400’
SET PostgreSQL PARAMETERS
 maintenance_work_mem TO '1024MB', work_mem to '128MB'
INCLUDING ONLY TABLE NAMES MATCHING ~/history.*/, ~/trend.*/
ALTER SCHEMA 'zabbix' RENAME TO 'public';

Обратите внимание на строки:


no truncate

говорит о том, что при начале миграции не будут очищены все мигрируемые таблицы. Ведь в истории уже появляются данные, так как мониторинг запущен.


INCLUDING ONLY TABLE NAMES MATCHING ~/history.*/, ~/trend.*/

говорит о том, что мигрировать в этот раз будут только таблицы истории.


Запускаем миграцию


pgloader zabbix.load.data

Миграция истории началась, процесс этот может быть долгим. В моем случае при размере базы около 150 Гб и не самом сильном железе миграция истории занимает 4-5 часов.


Поначалу все шло хорошо. Потом я обнаружил, что загрузка CPU на PostgreSQL сервере растет линейно, и со временем ресурсов для нормальной работы уже не хватало (на графике load average на 1 ядро):


image


В логи Zabbix сервера сплошным полотном начали сыпаться медленные запросы SELECT. Как я понял, для того, чтобы совершить web check, нужно для начала сделать SELECT.


Я попробовал снизить интенсивность миграции. Для этого изменил следующие параметры в файле zabbix.load.data:


prefetch_rows = 1000,
workers = 1,
concurrency = 1,
single reader per thread

Но с такими настройками не поменялось ничего, кроме скорости миграции. Нагрузка росла также линейно со временем.


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


image


А также с количеством tuples, которые возвращала PostgreSQL клиентам в ответ на их запросы:


image


Идем в интернет, ищем информацию о том, как ведет себя PostgreSQL при длинных транзакциях. И находим вот такой замечательный доклад с конференции HighLoad:
https://www.youtube.com/watch?v=3h48iowNbwo


Советую посмотреть доклад, но если вкратце, то при наличии длинной транзакции в PostgreSQL не срабатывает механизм внутристраничной очистки (single-page cleanup). И это приводит к росту тех самых возвращаемых tuple. СУБД приходится разбираться, какой tuple ей нужен, что ведет к росту загрузки CPU.


Я проверил мою ситуацию — действительно, достаточно открыть транзакцию, создать в ней таблицу, а затем НЕ закрыть транзакцию:


begin;
create table x(n numeric);

У спустя некоторое время видим рост загрузки CPU:


image


Падение загрузки в конце графика — это закрытая вручную транзакция.
Видимо, с этой особенностью придется мириться. Что можно сделать? Например, мигрировать историю отдельно, тренды отдельно, чтобы хоть немного уменьшить продолжительность транзакции.


Но это не всё. У меня все таки получилось сделать так, чтобы эта особенность PostgreSQL не мешала.


Немного о моей реальной инсталляции Zabbix. На ней почти все задачи сбора данных делегированы на Zabbix прокси. Сервер занимается лишь приемом данных и всякими триггерами/препроцессингом. Но как я написал выше, на тестовой инсталляции все проверки исполнял сам сервер.


Я вынес все задачи сбора данных на прокси в тестовой инсталляции, сильно снизив нагрузку на Zabbix Server и PostgreSQL. И оказалось, что это помогло! После этого никакая длинная транзакция не создавала сверхвысокой нагрузки на CPU, не мешала мониторингу нормально работать. Ну либо по какой-то причине внутристраничная очистка PostgreSQL при таком сценарии заработала. Может быть знатоки PostgreSQL подскажут?


Заключение


Как оказалось, произвести миграцию Zabbix с MySQL на PostgreSQL с минимизацией downtime лишь немного сложнее стандартной миграции. Делайте тестовую инсталяцию, экспериментируйте, и все получится.


Спасибо всем, кто дочитал пост до конца. Надеюсь, он кому-то поможет спланировать и оттестировать миграцию.


Ну и конечно жду feedback, может быть более опытные коллеги укажут на недочеты.




Комментарии (13):

  1. awsswa59
    /#21915036

    Я по этой статья прикручивал TimescaleDB к Zabbix
    awsswa.livejournal.com/45940.html

  2. Tellamonid
    /#21915646

    Я, может, что-то пропустил, но я не понял, зачем переходить с mySQL на PostgreSQL? mySQL чем-то не устраивал?

    • niklep
      /#21915726

      Верно, я не стал пытаться объять необъятное и включать в одну статью еще и причины выбора СУБД, и вопросы ее тюнинга. Миграция на PostgreSQL нужна была для того, чтобы в последующем применить TimescaleDB. О преимуществах TimescaleDB для Zabbix уже неоднократно писали в том числе здесь на Хабре. Базы данных временных рядов в целом больше подходят для систем мониторинга.
      Например, одна из самых назойливых проблем при работе Zabbix на реляционной БД — медленный Housekeeper. Хотя бы для решения этой проблемы уже стоит мигрировать.
      Нашел статью:
      habr.com/ru/company/oleg-bunin/blog/470902

    • Amikko
      /#21915742

      Скорее просто раньше было всё равно, а тут Постгрес стал устраивать больше, ибо


      "В свете того, что Zabbix с некоторых пор поддерживает TimescaleDB"

      Если верно понимаю, TimescaleDB — это расширение Постгреса для оптимизации операций с временными рядами. Если в Zabbix завезли интеграцию с этой штукой, то почему бы не переехать на Постгрес.

      • Jammarra
        /#21916954

        Так можно докатиться и до перехода на промитей)

        • shamanis
          /#21917780

          у нас Zabbix перекатили на ClickHouse недавно. Каким чудом не знаю, но его еще пару недель штормило после этого.

    • norguhtar
      /#21916388

      При большой нагрузке MySQL просто умирает. Достаточная причина?

  3. myz0ne
    /#21915788

    Интересно, а если мигрировать исторические данные pgloader'ом в отдельный инстанс постгреса, а потом сделать dump/restore в основной, не получилось бы избежать проблемы? По идее восстановление дампа может оказаться эффективнее.

    • blind_oracle
      /#21915870

      Проще было бы заставить PGLoader заливать данные небольшими транзакциями по-моему. Есть ли там такая фича или нет — не знаю, но необходимость её очевидна. Указал там батч в 100к строк и коммитишь после него.

      • niklep
        /#21916354

        Я искал такую возможность в PgLoader, но не обнаружил. Оно и логично, т.к. миграция в одной транзакции позволяет легко откатить все изменения, если что-то пошло не так при миграции. У меня лично было много проблем на PgLoader 3.6.1. И без отката всех изменений было бы очень неудобно.

    • niklep
      /#21916360

      Расскажите, пожалуйста, чем эффективнее? Если отсутствием длинной транзакции, то как я написал коллеге чуть ниже, миграция одной транзакцией позволяет легко откатить изменения при проблемах. Это такая архитектурная необходимость.

      • myz0ne
        /#21916862

        Потенциально эффективнее тем, что не надо считывать данные из mysql и ждать его. Восстановление одной таблицы это одна команда COPY, так что она либо восстановится, либо нет.


        Заббиксо-специфичный хинт: можно восстанавливать только таблицы с тендами, т.к. сами данные как правило все равно удаляются через 7-14 дней. На этом периоде графики были бы видны при масштабе более 1 дня. Таблицы с трендами как правило небольшие.


        Сюда же вброшу postgres-специфичный хинт: можно восстанавливать в таблицу с другим именем, а затем подключить её через inherits к основной. Вам бы потенциально помогло с экспериментами, т.к. в эту таблицу писали бы только вы.


        Плюс по доке: пишут что pgloader грузит данные батчами по умолчанию, и при неудаче старается выпилить плохие строки и повторить вставку. Если там нет создания хитрых функций чтобы избегать отмены транзакии при ошибке, то получается что таки он грузит не в одной транзакции. (но это не точно)


        https://pgloader.readthedocs.io/en/latest/pgloader.html#batches-and-retry-behaviour