Зачем нам три сервера
Во-первых, есть формула кворума кластера 2N + 1, где N > 0 и означает количество серверов в кластере, которые могут одновременно выйти из строя. Если у вас N = 0, то выживет ли ваш прод – вопрос риторический. Он точно умрет, вопрос только в том, когда.
Можно, конечно, обойти эти кластерные системы или вообще не настраивать кластер, а ограничиться физической репликацией между двумя серверами и считать, что у вас отказоустойчивая система. Но нет, у вас будет система с резервированием, а не отказоустойчивая. Почему? Потому что если один из этих серверов выйдет из строя, N станет равно нулю. И по закону подлости это случится в самый неподходящий момент: во время закрытия месяца сломается один сервер и через три минуты – следующий. И вы останетесь без системы.
Чем опасен Split brain
Существует более опасная проблема, чем отказ сервера. Это сплитбрейн – разделение «мозга» на две части (термин из психологии). Если 1С начнет записывать на два мастера, открывая сессии на разных серверах, вы не сможете восстановить целостность данных. Базу придется удалить с обоих серверов и восстановить из последней резервной копии. Другого варианта нет. При сплитбрейне будет потеря данных. Масштаб катастрофы будет зависеть от того, как часто вы делаете бэкап и как быстро вы обнаружили сам факт споитбрейна.
Надежной защиты от сплитбрейна, когда у вас всего два сервера, не существует. Если система настроена некорректно и соединение между мастером и репликой разрывается, каждый из них посчитает, что он теперь мастер, и оба будут принимать соединения. Пойдут на них соединения или не пойдут – неизвестно. Это будет зависеть от того, как у вас настроена 1С.
При правильной настройке «неправильного» кластера, состоящего всего из двух нод, оба сервера должны переключиться в режим Read-only. В итоге для пользователя 1С перестанет работать, но вы не допустите катастрофы с данными и уже в ручном режиме восстановите работоспособность 1С, решив, что важнее в данный момент – восстановить резервирование или временно запустить систему даже на одном сервере.
Как работает отказоустойчивость в Postgres
Все механизмы отказоустойчивости в Postgres основаны на технологии физической потоковой репликации.
Система, которая управляет кластерами, должна определить, что мастер мертв, и переключиться так, чтобы не было сплитбрейна. И это сложно. Очень сложно выключить мастер, который посчитали умершим.
Patroni, Corosync+Pacemaker, Corosync+Pacemaker+Virtual IP – классические системы кластеризации Postgres. В них внешние агенты непрерывно опрашивают серверы («ты жив?»), друг друга («а у тебя кто жив?») и обмениваются этими данными между собой.
Проблема в том, что Postgres может заикнуться и не ответить внешней системе. Вернее, он может ответить чуть позже, с задержкой. Внешняя система тем временем принимает решение «он умер, переключаемся». У всех внешних систем одна большая боль: огромное количество ложноположительных срабатываний смерти мастера, когда ее на самом деле не было. Он не умирал. Он чихнул, зевнул, моргнул, но не умирал. А его признали мертвым и все переключили.
И так происходит постоянно. Очень много надо сломать копий на том, чтобы именно на вашем железе, именно на вашей нагрузке подобрать параметры определения «жив/мертв» так, чтобы ложноположительные срабатывания, с одной стороны, свести к минимуму, а с другой стороны, чтобы реальный отказ сервера определился как можно быстрее. Этот баланс очень сложно найти.
Built-in High Availability
Что предлагает энтерпрайзный Postgres? В нем появилась BiHA, Built-in High Available – высокая доступность из коробки. Когда ее разрабатывали, в уме держали три задачи.
-
Первая задача была упростить настройку кластера,
-
Вторая – упростить взаимодействие в целом,
-
Третья – предотвратить сплитбрейн.
Классической системе очень сложно отключить мастер в том случае, когда у мастера отключается сеть. Внешняя система не может на него влиять. Мастер не понимает, что связи нет. Он работает, у него все хорошо, ему легко стало, прекрасно, никакие запросы не надо обрабатывать. Обходят это наличием агентов систем мониторинга на мастере, которые при отсутствии связи с системой мониторинга либо выключают мастер, либо переводят его в режим «только чтение». Но даже тут мы с вами понимаем, что теперь мы зависим еще и от сервера системы мониторинга кластера: получается, что теперь и его надо делать отказоустойчивым и т.д.
А тут админ запустил уборщицу со шваброй в серверную, она дернула шнур, сервер отключился. Админ шнур воткнул, и у вас появился второй мастер. Все, сплитбрейн. Внешним системам очень сложно с этим бороться. Есть куча кода, куча взаимодействий, куча обвесного ПО, чтобы отключить мастер, который кластером признан мертвым по разным причинам.
BiHa может сделать так, чтобы мастер сам понимал, что он умер и без всякого внешнего воздействия выключался.
На каждом сервере 1С стоит HAProxy, если сервер на линуксе. Или Nginx, если сервер на винде. Либо можно поставить кроссплатформенный PgBouncer.
И HAProxy каждый раз у каждой ноды BiHA спрашивает «ты лидер или последователь?» (раньше мы говорили «мастер» и «слейв/реплика», теперь «лидер» и «последователь»). Каждая нода BiHA сама опрашивает все остальные ноды кластера. Она в курсе, какой состав у кластера, кто мастер, кто не мастер. На основе этой информации HAProxy направляет запросы на нужный сервер. Если произойдет сбой – HAProxy автоматически переключит 1С на другой сервер.
Если сейчас нужно опрашивать каждый сервер, то скоро можно будет опрашивать только одну ноду, а между остальными распределять запросы будет сам встроенный прокси. Он автоматически определит, какие ноды активны, и направит запросы по алгоритму round-robin.
P.S. На момент выхода статьи эта технология уже вышла в релиз PostgresPRO Enterprise.
Есть еще один вариант работы с кластером – в строке подключения к СУБД (через драйвер libpq) можно перечислить ноды через запятую. Это позволяет убрать внешний HAProxy из системы – остается только СУБД и платформа. Но так делать можно только в тестовой среде.
Нужно указать read-write, чтобы драйвер libpq подключался только к нодам, которые могут и писать, и читать. Реплики не подходят – они могут только читать. Если основная нода отключится, драйвер автоматически переключится на другую без участия 1С.
Если нода внезапно станет репликой или отключится, запросы могут замедляться. Прокси пока не решает эту проблему – система не всегда успевает мгновенно переключиться. Но в тестовой среде это не страшно.
Когда BiHa будет работать через прокси, можно будет указать «any» вместо «read-write». Прокси сам определит, кто главный.
Проблема временных таблиц в 1С
1С обожает переиспользовать временную таблицу. Напомню, что такое переиспользовать временную таблицу, и как 1С понимает, что таблицу можно переиспользовать.
Есть менеджер временных таблиц. Это специальный сервис. Он запоминает следующую информацию: имя таблицы, состав ее полей и индексов, типы ее данных и номер соединения, в котором она была создана.
Когда Postgres создает временную таблицу, о ней знает только тот бэкенд, который ее создал. В MS SQL ситуация с временными таблицами похожа, там тоже про ее существование знает только тот поток, который ее создал.
Допустим, у вас кластер PostgreSQL с несколькими серверами. Если происходит переключение на другую ноду СУБД, то новая нода не знает о временных таблицах, созданных на старой. Менеджер временных таблиц 1С не понимает, что соединение больше не существует. В результате появляется ошибка и все запросы падают.
Что делать? Необходимо, чтобы при переключении на новую ноду соединения со старой разрывались. Прокси должен это понимать – тогда 1С тоже поймет, что временной таблицы больше нет и корректно создаст ее заново.
На данный момент Платформа 1С написана в парадигме прямого подключения к СУБД, поэтому вам самим необходимо будет протестировать поведение выбранного вами прокси для кластера с учетом вышесказанного (HAproxy и BiHA Proxima точно разрывают соединения, про остальные у меня информации нет).
Узел-рефери
Что еще есть в BiHa? Узел-рефери. Это когда бизнес не дает денег, нет дисков, ничего нет, но система должна быть отказоустойчивой и не иметь риска сплитбрейна. Узел-рефери не содержит данных. На нем нет базы. На него поступают все логи транзакций. И вы можете его использовать как хранилище логов транзакций.
Из логов транзакций узел-рефери себе в базу вычитывает только информацию, нужную для того, чтобы BiHa жила. Больше там ничего нет. Ни одного байта, никакого регистра сведений. Это такая машина для голосования.
Узел-рефери никогда не сможет выиграть выборы в кластере. Он просто голосует «я жив, этот сервер тоже жив, а тот умер».
Переключение на BiHa
Есть два режима запуска. Создание нового кластера с нуля и переход с физической репликации на BiHa на основе существующего мастера.
Как запустить BiHa с нуля
-
Инициализируем кластер с узлом-лидером:
bihactlinit--biha-node-id=1 --host=узел_1 --port=5432 --biha-port=5433 --nquorum=2 --minnodes=2 --pgdata=каталог_PGDATA_лидера
nquorum – это минимальное число голосов для кворума. Например, при 3 узлах кворум = 2 (большинство). Если сеть разрывается и кворума нет, кластер переходит в режим «только чтение».
-
Запускаем лидера:
pg_ctlstart-D каталог_PGDATA_лидера
-
Добавляем последователя:
bihactladd--biha-node-id=2 --host=узел_2 --port=5434 --biha-port=5435 --use-leader"host=адрес_узла_лидераport=порт_лидераbiha-port=порт_biha_лидера" --pgdata=каталог_PGDATA_последователя
-
Запускаем последователя:
pg_ctlstart-D каталог_PGDATA_последователя
Как перейти на BiHa с существующей репликации
-
Останавливаем наш узел лидера:
pg_ctlstop-D каталог_PGDATA_лидера
-
Инициализируем кластер с узлом-лидером и параметром --convert:
bihactlinit--convert --biha-node-id=1 --host=узел_1 --port=5432 --biha-port=5433 --nquorum=2 --minnodes=2 --pgdata=каталог_PGDATA_лидера
-
Запускаем лидера:
pg_ctlstart-D каталог_PGDATA_лидера
-
Останавливаем наш узел последователя:
pg_ctlstop-D каталог_PGDATA_последователя
-
Добавляем последователя с параметром --convert-standby:
bihactladd--convert-standby --biha-node-id=2 --host=узел_2 --port=5434 --biha-port=5435 --use-leader"host=адрес_узла_лидераport=порт_лидераbiha-port=порт_biha_лидера" --pgdata=каталог_PGDATA_последователя
-
Запускаем последователя:
pg_ctlstart-D каталог_PGDATA_последователя
Кто лидер?
Чтобы рассказать НАProxy, что BiHA живая, указываем порт 5432. Добавляем опцию ExternalCheck – это внешняя проверка. И прописываем имя скрипта, который будет выполняться:
haproxy.cfg
listen biha_stand
bind *:5432
option external-check
external-check command /check-node.sh
server node-1 node-1:5432 check inter 5s on-marked-down shutdown-sessions
server node-2 node-2:5432 check inter 5s on-marked-down shutdown-sessions
server node-3 node-3:5432 check inter 5s on-marked-down shutdown-sessions
Если BiHA уже работает через прокси, вместо скриптов проверки используется round-robin. Достаточно просто перечислить ноды в конфигурации. Больше ничего настраивать не нужно:
haproxy.cfg
listen biha_stand
bind *:5432
balance roundrobin
server node-1 check
server node-2 check
server node-3 check
Отработка отказа
Для примера возьмем кластер из трех узлов: 1 лидер + 2 реплики.
Связь лидера с репликами разрывается. Лидер через заданный таймаут – например, через 3 секунды – переходит в режим Read-only, чтобы избежать сплитбрейна. Реплики запускают выборы нового лидера и переходят во второе поколение. Выигрывает реплика с минимальным отставанием от мастера. Новый лидер сначала работает в режиме Read-only, пока не накатит все валидные транзакции. Старый лидер, при восстановлении связи, обнаруживает новое поколение и автоматически становится репликой, откатывая свои изменения.
Отсутствие сплитбрейна обеспечивается самим движком без какого-либо внешнего вмешательства. Ничего не надо придумывать, ничего не надо контролировать. Это самый главный плюс новой системы.
Кластера – это сложно, но если ваша система назвалась продом, пусть полезает в кластер.
*************
Статья написана по итогам доклада (видео), прочитанного на конференции INFOSTART TECH EVENT.