2 августа 2024
Блог
Как сделать распределенные базы данных отказоустойчивыми на примере использования MS SQL: AlwaysOn
В критически важных продуктах важно обеспечить high availability (отказоустойчивость) 24/7. Существуют разные пути для того, чтобы решить такую задачу. Мы же в своей работе использовали MS SQL: AlwaysOn. В статье рассказываем, почему выбрали это решение и как оно работает в продукте заказчика.
Почему возникла потребность в отказоустойчивом сервисе?
Один из продуктов, который мы разрабатываем для крупнейшего игрока авиаотрасли, Smart Ticketing, является одним из основных каналов продаж авиакомпании. Для бизнеса крайне важно поддерживать непрерывность продаж и минимизировать простои. Для этого можно, например, подготовить план восстановления после катастрофы (Disaster Recovery Plan). Но это не отменяет необходимость обеспечить отказоустойчивость сервиса, так как любой простой приводит к прямым потерям дохода. По запросу заказчика отказоустойчивость должна быть обеспечена в географическом смысле — при полном отказе основного кластера в Москве.
Важно было обеспечить не только отказоустойчивость самого Smart Ticketing, но и сервисов, от которых он зависит. Так, если непосредственно для этого решения на уровне кластеров в Москве и Новосибирске была реализована географическая отказоустойчивость, то в зависящем от него сервисе («Баланс») – нет. И если он становился недоступным, то продажи билетов просто блокировались.
Нашей задачей стало обеспечение отказоустойчивости интеграционных сервисов для Smart Ticketing и базы данных «Баланса».
С какими проблемами мы столкнулись?
1. Обеспечение возможности горизонтального масштабирования (дублирования) сервисов в кластерах Москвы и Новосибирска.
Для этих целей потребовалось провести определенный анализ, а также:
- выявить возможные проблемы в цепочках интеграций
- решить проблемы с кэшированием и эксклюзивной обработкой некоторых данных
- обеспечить порядок переключения и балансировки запросов к сервисам между кластерами
2. Обеспечение репликации данных и возможности автоматического переключения на доступную реплику данных.
Согласно CAP-теореме, можно обеспечить не более двух из трех следующих свойств системы:
- Consistency — согласованность
- Availability — доступность
- Partition tolerance — устойчивость к разделению. В нашем случае речь идет о географическом разделении между Москвой и Новосибирском.
Ниже мы расскажем о том, как мы обеспечили отказоустойчивость базы данных «Баланса» на MS SQL Server. Это решение имеет компромиссы, на которые мы должны были пойти исходя из теории.
Решение
Подходящим решением для нас стала технология AlwaysOn от Microsoft, основанная на платформе WSFC — Windows Server Failover Cluster. WSFC состоит из узлов, которые объединяются в группы доступности Availability Group. Наше решение подразумевает одну такую группу.
Failover cluster — это абстракция, которая включает все нужные нам географически распределенные узлы в разных городах, где каждый узел находится в своем «инфраструктурном» кластере, в нашем обычном понимании. На платформе реализован специальный механизм голосования между узлами, так называемый quorum («кворум») из узлов, который принимает решение какой узел сейчас доступен и будет главным. Встречаются разные слова в описании технологии для главного узла – master/primary. Вместе с тем технология AlwaysOn работает таким образом, где узлы — это реплики одной и той же базы данных в рамках группы доступности.
AlwaysOn обеспечивает:
- Высокую доступность MS SQL и автоматическую отработку отказа при определенных ограничениях.
- Резервное копирование с вторичных реплик. Вторичные реплики называются secondary.
- Избыточность данных. Реплики означают появление избыточности данных, которая может быть использована для балансировки нагрузки select-запросов на чтение между репликами.
Для работы технологии AlwaysOn нужно иметь кворум минимум из трех узлов. В нашей ситуации у нас всего две реплики БД — в Москве и Новосибирске. Создать кворум в случае отказа в этой ситуации не получится, нужен еще один узел. Технология позволяет добавить узел, который НЕ является репликой БД.
Для голосования, какая БД должна быть основной (обозначено как master на схеме), мы добавили узел для голосования в Питере (witness node). Есть требование для всех узлов, чтобы они были «видны» друг другу. Узел кворума вынесен в третий инфраструктурный кластер для того, чтобы его работоспособность не зависела от работоспособности кластеров Москвы и Новосибирска, и, соответственно, он мог принять участие в выборе реплики для назначения основной роли (master) в случае сбоя одной из реплик.
Итого, как устроена схема взаимодействия?
Клиент обращается к слушателю (listener). Его конфигурация представляет собой: имя в DNS, один или несколько виртуальных IP, порт. Клиенту нужно знать только DNS-имя и порт. Адреса реплик знать не нужно. Если включен режим чтения для вторичных (secondary) реплик, то технология может перенаправлять такие запросы на вторичную реплику. Разумеется, запросы, требующие изменения данных, выполняются только на master реплике. Сущность конфигурации listener одна и лежит в каждой реплике.
В случае события отказа одной из реплик запускается процесс смены master-роли путем голосования. Роль основной БД переезжает на другую реплику. Все установленные до смены роли соединения обрываются — клиентам нужно обработать ошибку и установить соединения повторно. В случае восстановления доступности исходной реплики автоматического обратного переключения роли master не происходит. Обратное переключение при необходимости можно (и нужно) делать из админки failover cluster'а. Почему нужно? Расскажем далее в частях про топологию и тестирование.
После восстановления доступности исходной реплики нужно актуализировать данные до актуального состояния текущей master-реплики. Поэтому автоматически запускается процесс синхронизации для накопившихся изменений, пока состояние исходной реплики не достигнет статуса synchronized. Синхронизация происходит через логи транзакций реплики. Статус синхронизации мы видим на дашборде админки, в Zabbix, в Grafana. Во время синхронизации текущая master-реплика БД доступна в обычном режиме. Вернуть роль master исходной реплике до завершения синхронизации без нарушения консистентности данных нельзя. Но техническая возможность есть — тогда потеря данных останется на совести человека :)
При выполнении транзакций на изменение данных реплики могут синхронизироваться в различных режимах. Эта настройка называется «режим доступности» или availability mode. Таких режимов два: синхронный и асинхронный. У нас в решении используется синхронный режим, когда клиенту нужно дожидаться завершения применения транзакции в обеих репликах. Необходимое нам автоматическое переключение основной реплики при сбое на другую обеспечивается только в синхронном режиме. Также только в этом режиме обеспечивается необходимая нам согласованность данных, поскольку речь идет об учете финансовых данных. В этой ситуации приходится жертвовать дополнительным временем клиента на ожидание завершения коммита во второй реплике для транзакций изменения данных.
В асинхронном режиме доступно только ручное переключение через админку, и никаких гарантий по согласованности данных нет. Возможна ситуация, что при сбое основной реплики во второй реплике не успели успешно примениться транзакции, которые уже успели примениться в master. Соответственно, если переключение произойдет автоматически, то мы потеряем данные, которые не успели примениться в secondary.
Вернемся к вопросу, почему может понадобиться переключить роль master на исходную реплику после сбоя, когда обе реплики стали доступны?
В исходной ситуации, когда master и клиент в Москве, то запрос-ответ в Новосибирск осуществляется один раз.
Но в ситуации, когда роль master переехала в Новосибирск, клиент делает запрос к Новосибирской реплике, а Новосибирская синхронизирует данные с Москвой. Получаем ситуацию, что запрос-ответ между Новосибирском и Москвой происходит дважды. В этой ситуации за счет удаленности узлов и умножения задержек между узлами можем получить существенное увеличение времени обработки запроса к БД. Налицо overhead – дополнительные затраты времени на обработку запроса. Это может стать проблемой, при которой потребуется вернуть роль на исходную реплику через админку WSFC.
Перейдем к проведению тестирования технологии AlwaysOn
Мы составили план, смоделировали реальную нагрузку и проработали описанные сценарии переключений роли между репликами.
Первый сценарий – отказ исходной реплики
По протоколу происходили задержки в доступности БД от двадцати секунд до двух минут. Далее запросы успешно обрабатывались. Зафиксировали, что прошла автоматическая синхронизация реплик после восстановления исходной реплики.
Второй сценарий – ручной возврат master роли исходной реплике
Важно дождаться синхронизации реплик после восстановления доступности – об этом сообщает статус в админке WSFC и другие настроенные мониторинги. При обратном переключении роли master на исходную реплику получаем тот же эффект кратковременной недоступности сервиса БД, как в предыдущем случае, несмотря на то что обе реплики доступны. На десерт изучим график обработки запросов этих сценариев, где по вертикальной шкале среднее время обработки запроса к БД у клиентского сервиса, а горизонтально – время событий.
Первый пик — это событие включения «нагрузки». Затем второй пик – отключение master-реплики, видим повышение среднего времени обработки событий. Также фиксируем в интервале между красными линиями ошибки обращения к БД в логах. Далее широкий интервал между красными линиями – это практически все время работы одной реплики (Новосибирск). Что характерно, среднее время обработки запроса к БД снизилось, так как не работает synchronous commit между репликами.
Перед третьей красной линией мы видим увеличение среднего времени обработки –включили исходную реплику в Москве, запустилась синхронизация и synchronous commit между репликами. Пик на третьей красной линии – это событие ручного переключения master-роли на исходную реплику. Также фиксируем ошибки в логах недоступности БД со стороны клиентского сервиса. Далее видим возврат к исходным средним по времени обработке запросов.
Какие выводы можно сделать и на что обратить внимание?
1. Важно отслеживать изменения в топологии системы, чтобы после сбоев можно было быстро восстановить оптимальный режим работы и избежать снижения производительности.
2. Необходимо анализировать ошибки, возникающие при сбоях, и при необходимости повторно обрабатывать проблемные события из шины.
3. Заранее убедиться, что выбранный режим доступности не привносит вам критического замедления работы системы.
4. Взвесить, что ценность дублирования и стабильности работы выше, чем фактическое снижение производительности системы.
Петр Шарапов
тимлид
В итоге мы добились отказоустойчивости базы данных «Баланса» с помощью AlwaysOn, хотя и с некоторыми ограничениями.
Мы уже много лет работаем со сложнейшими техническими решениями и технологиями, поэтому любую подобную задачу можно смело доверить нам:)