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

Привет, Хабр! Это Никита Турцаков и Александр Кузьмин из К2. Никита Турцаков в IT больше 20 лет. За это время успел спроектировать и сопровождать высокодоступные и отказоустойчивые системы в финансовом секторе — когда на кону были деньги, а потом в гражданской авиации — когда к деньгам добавились ещё и жизни пассажиров. Александр Кузьмин в IT 15 лет, последние 5 лет работает в K2. Начинал с системного администрирования, в багаже — сертификат Certified Kubernetes Administrator (CKA). Из необычного опыта — участвовал в проекте по внедрению CI/CD через… SMTP.

В этой статье расскажем:
— зачем и для чего нужно георезервирование для системы ВКС;
— как мы подошли к выбору архитектуры;
— с чем столкнулись при реализации;
— и что бы сделали по-другому, если бы начинали сейчас.

Готовьтесь: будет много практики и немного боли — потому что как без неё в масштабных и амбициозных задачах.

Точка отсчёта

Чтобы было понятнее, расскажем немного о продукте. Это система видеоконференцсвязи (ВКС), рассчитанная на корпоративный сегмент.

Она уже многое умеет:

  • Создавать события с количеством участников до 5 тысяч человек. Причём все участники могут подключаться одновременно, без падений и лагов, что для крупных компаний и госструктур — критично.

  • Записывать события, анализировать записи и предоставлять функцию speech-to-text. Иными словами, после встречи можно получить не только видеозапись, но и расшифровку всего сказанного — чтобы искать нужные фрагменты по тексту, а не пересматривать видео.

  • Имеет собственный видеохостинг. По сути, внутренний YouTube: записи встреч, презентации, видеоуроки — всё хранится в одном месте, доступно по правам и без внешних зависимостей.

  • Имеет чаты с мемасами и гифками. Не всё общение должно быть строго деловым. В сервис добавили поддержку эмодзи, мемов и гифок, чтобы коммуникация внутри событий оставалась живой и человечной.

  • Интегрируется с внутренней телефонией и поддерживает SIP. Это значит, что с системой можно работать не только через интернет, но и позволяет подключать традиционные телефоны, аппаратные ВКС и даже колл-центры.

  • Есть встроенная платформа для вебинаров. Всё, что нужно для проведения внутренних и внешних мероприятий: регистрация, рассылки, формы обратной связи, аналитика по участникам.

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

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

Теперь немного о нашем технологическом стеке. В основе — опенсорс и стандартный современный набор технологий:

  • Контейнеризация: используем как Kubernetes, так и отдельно Docker.

  • Базы данных: PostgreSQL, Redis, ClickHouse. Важно уточнить: Redis у нас используется в первую очередь для pub/sub, а не для key/value.

  • Очереди и сервис-дискавери: Kafka, Consul.

  • Балансировка трафика: HAProxy и Nginx.

  • Мониторинг и алертинг: Grafana, VictoriaMetrics, AlertManager, Vector.

  • Языки разработки: примерно 95% — Golang, остальные 5% — Java и JavaScript.

Проблемы и решения

Рассказываем о самом важном: как всё начиналось, какие проблемы у нас возникли и как мы их решаем до сих пор.

Начало 2022 года. Мы проводим запуск и отладку, выводим в прод PoC нашего продукта. На тот момент у нас: Standalone Redis, Standalone PostgreSQL, виртуальные маршрутизаторы VMware.

Отсутствие отказоустойчивости

После удачного PoC сразу обозначилась проблема №1 — полное отсутствие отказоустойчивости. Естественно, мы сразу начинаем её решать. Первым на очереди, для повышения надёжности, оказался PostgreSQL. Логично, что выбор пал на Patroni. А поскольку у нас уже был развёрнут кластер Consul, решаем использовать именно его в качестве DCS для Patroni. В качестве адреса подключения к базе данных используем VIP-адрес Keepalived. 

Что касается Redis — на лету перевели из Standalone в Sentinel, чтобы обеспечить отказоустойчивость.

В общем виде наша конфигурация на тот момент выглядела так. Внизу — инсталляция Redis Sentinel. Перед Redis стоял HAProxy, который проверял, на каком узле сейчас мастер, и направлял прикладной трафик именно туда. При этом приложение обращалось к VIP-адресу и подключалось к нему по протоколу Redis Standalone. Приложение про Sentinel ничего не знало — для него всё выглядело как обычный Redis.

К концу 2022 года мы смогли достичь надёжности и отказоустойчивости в рамках одного ЦОДа. Казалось бы — успех. Но… не тут-то было.

Рост трафика и ограничения

Возникла проблема №2. Начал расти трафик, и тут нас настигли ограничения. Виртуальные маршрутизаторы VMware перестали справляться с возросшей нагрузкой. Причём мы уже использовали максимально возможный сайзинг — 8 CPU и 16 GB RAM.

Стали происходить аварийные ситуации по разным причинам:

  • виртуальному маршрутизатору упирается в RPS и, соответственно CPU;

  • виртуальному маршрутизатору упирается в CPU и, соответственно страдает PPS;

  • «странные» «тяжелые» запросы в СУБД.

Action: авария!

И вот именно здесь начинается настоящий action этой статьи. Происходит авария на сетевом оборудовании ЦОДа заказчика. Вся система оказывается полностью недоступна на продолжительное время. Результат — недовольство сотрудников, недовольство владельца продукта, давление на команду. Заказчик жёстко требует проработать варианты повышения надёжности и масштабирования под растущие нагрузки, а также исключить возможность простоя при потере одного из ЦОДов.

Мы фиксируем список необходимых шагов:

  • Выдвигаем требования по ресурсам для развёртывания дополнительных площадок.

  • Заявляем необходимость открытия новых вакансий для расширения команды.

  • Совместно с сетевиками начинаем прорабатывать вариант перехода на аппаратные маршрутизаторы вместо виртуальных (vmware), которые упёрлись в лимиты.

Новый вызов

В конце 2022 года нам одобряют бюджет на всё вышеперечисленное. При этом появляется встречное требование: система должна работать в схеме ACTIVE-ACTIVE на двух ЦОДах.

В 2023-м год мы вошли с размещенной закупкой маршрутизаторов и выделенными мощностями во втором ЦОДе (у заказчика их два). Перед нами встает главная дилемма: как построить архитектуру для двух ЦОДов в схеме ACTIVE-ACTIVE, чтобы обеспечить надёжность и управляемую нагрузку.

Для этого инженеры собрали тестовый стенд, эмулирующий два ЦОДа, и начали проверять, как поведут себя прикладные компоненты в двухЦОДовой конфигурации.

В ходе тестов вскрылись важные требования к доработке приложения. Без этих изменений перейти на два ЦОДа было бы просто невозможно.

Поддержка подключения и переподключения библиотек сразу к нескольким инстансам СУБД и экземплярам приложений. Это критично, чтобы приложение умело работать не только в рамках одного ЦОДа, но и обращаться ко второму, при необходимости перебрасывая запросы.

Реализация поддержки нескольких ЦОДов в Consul. Это даёт возможность гибко управлять внутренней балансировкой между компонентами. Мы получили инструмент, чтобы на прикладном уровне контролировать, где и как обрабатываются запросы: оставить их в одном ЦОДе, либо распределить нагрузку сразу на два, с возможностью выводить часть ресурсов из-под трафика при необходимости.

Решение проблем с Redis

Но как добиться, чтобы переключение приложений между кластерами СУБД в двух ЦОДах было минимально болезненным? Наше решение: в каждом ЦОДе ставим по паре HAProxy с VIP-адресом. Приложения в каждом ЦОДе используют VIP HAProxy своего ЦОДа для работы с СУБД. Внутри бэкенды в HAProxy (в обоих ЦОДах) настроены на VIP текущего кластера-лидера. При смене роли кластера автоматизация переключает конфигурацию HAProxy — HAProxy начинает смотреть в VIP нового лидера кластера. Для приложений всё прозрачно: они продолжают стучаться в свой локальный HAProxy, а логика переключения «кто сейчас лидер» уходит внутрь инфраструктуры.

Но с Redis всё не так просто. В отличие от PostgreSQL, Redis не предоставляет из коробки встроенных механизмов для георезервирования между двумя ЦОДами.

Создали 6 виртуальных машин (по 3 в каждом ЦОДе). Установили Redis на всех 6 ВМ. Собрали их в Sentinel-кластер. Пару ВМ (по одной из каждого ЦОДа) объединили в Pacemaker-кластер. Pacemaker управляет запуском Redis так, чтобы на этой паре ВМ всегда был запущен только один экземпляр Redis (и это строго контролируется Pacemaker). Таким образом, в двух ЦОДах на 6 узлах запускается ровно 5 экземпляров Redis.

Мы искусственно снизили кворум до 3 — и получили требуемый результат: кластер способен выбрать Master даже при отказе одного ЦОДа.

Проверяем работоспособность в реальной жизни

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

Чтобы переключить нагрузку на другой кластер, нужно выполнить несколько ручных шагов:

  1. Остановить репликацию.

  2. Перевести StandBy-кластер в роль Leader.

  3. Переключить трафик в HAProxy на новый кластер.

  4. Запустить обратную репликацию.

Вся процедура занимает примерно 10–15 минут, в придачу добавляется время на перенастройку HAProxy: коммит изменений, merge request, одобрение, запуск пайплайна. Даже при параллельной работе это приводит к заметной паузе в сервисе, которая совсем не похожа на active-active-схему.

Что с Redis?

Конфигурация Redis, о которой мы рассказывали раньше, отлично справляется при полном отказе одного из ЦОДов. Но если связь между ЦОДами рвётся, а само приложение продолжает работать в обоих ЦОДах, возникают сложности. Хотя сервис ВКС продолжает работу, страдает качество сервиса: например, у части пользователей могут не работать кнопки в интерфейсе (ещё терпимо). А в худшем случае — пользователей может «выбивать» из событий. Такое поведение не критично для самой платформы, но сильно влияет на user experience.

Kafka: те же плюсы и минусы

Конфигурация резервирования Kafka очень похожа на подход с PostgreSQL. Она унаследовала все его плюсы и минусы: репликация работает, но для переключения направления репликации и смены backend в HAProxy требуется ручное вмешательство.

Переобуваемся: от ACTIVE-ACTIVE к ACTIVE-STANDBY

Помимо всех описанных нюансов в конфигурации инфраструктурных сервисов, разработчики не успевали реализовать доработки, необходимые для полноценной поддержки мультиЦОДовой архитектуры. Это, вкупе с техническими ограничениями, вынудило нас выйти к владельцу продукта с предложением изменить целевую схему с ACTIVE-ACTIVE на ACTIVE-STANDBY. Мы подробно объяснили ситуацию, показали риски и ограничения. Владелец продукта услышал нас и согласился.

После этого мы прошли внутренние приёмо-сдаточные испытания (ПСИ), получили одобрение конфигурации, ввели систему в промышленную эксплуатацию. При этом договорились: возвращаемся к реализации схемы ACTIVE-ACTIVE в 2024 году.

Новые вызовы или дежавю

Кто внимательно читал нашу историю с самого начала, наверняка спросит: «Ребята, а как поживает ваша сеть? Вы же акцентировали внимание на производительности виртуальных маршрутизаторов и заказали аппаратные маршрутизаторы!»

Отличный вопрос. На тот момент у нас действительно уже прошла поставка новых аппаратных маршрутизаторов, но установили их только в одном из двух ЦОДов — в новом. А в старом ЦОДе так и остались программные маршрутизаторы на VMware.

Почему так получилось? Потому что к концу 2023 года у заказчика появился третий ЦОД. И новая пара маршрутизаторов сразу уехала туда. В результате — только закончили проект с двумя ЦОДами, как началось планирование переезда в три ЦОДа.

Началась новая итерация:

  • переработка архитектуры;

  • новые требования к размещению;

  • снова сжатые сроки — «успеть до конца 2024-го».

Ситуация повторялась, и мы вошли в 2024 год со стойким ощущением дежавю. Только теперь у нас два «больших» ЦОДа и один маленький — для кворума. Новые ЦОДы оснащены современным оборудованием, all-flash СХД. А старый ЦОД стал «кворумным».

И вот нам снова пришлось придумывать, как разложить инфраструктуру по трём площадкам. В первую очередь мы взялись за Redis, потому что именно он первым стал упираться в свои ограничения.

Проблема Redis: однопоточность и нагрузка

С ростом нагрузки мы увидели, что в пиках Redis уже упирается в 100% утилизации одного ядра. Как известно, Redis однопоточный: сколько ядер не выделяй — использует только одно. Да, есть io-threads, кто глубже знаком с Redis об этом сразу скажет. Но мы внедрили — и хватило буквально на несколько месяцев. Нам нужно было не только растянуть Redis на три ЦОДа, но и решить проблему однопоточности. Начали искать альтернативы.

KeyDB

И тут на сцену выходит KeyDB. KeyDB — это форк Redis, разработчики которого заявляют полную поддержку протоколов Redis — «все ваши библиотеки даже не поймут, что работают не с Redis». А ещё настоящую многопоточность и киллер-фичу — MultiMaster.

Что такое MultiMaster? В классическом Redis Sentinel мы запускаем два экземпляра Redis, один из которых настроен как replica другого: есть один Master и одна Replica.
В KeyDB можно обоим узлам указать replicaof… друг друга. Получаем MultiMaster. Приложение продолжает работать через стандартный протокол Redis Standalone и может обратиться к любому узлу. Репликации можно построить разные: последовательные, встречные, с любым количеством узлов — даже с чётным!

«Чётное количество? Архитектор, а как же split-brain?!» — спросите вы. Отвечаем: KeyDB утверждает, что каждая запись сопровождается timestamp'ом. При восстановлении split-brain синхронизация данных происходит по timestamp'у — «побеждает» свежая запись.

В нашем прикладном кейсе это выглядело вполне рабочим вариантом.
Мы решили попробовать.

Тестовый стенд KeyDB

Мы подняли три узла KeyDB. В DNS прописали три A-записи:

KeyDB.test.stand A 10.10.10.10  

KeyDB.test.stand A 10.10.10.20  

KeyDB.test.stand A 10.10.10.30  

и начали тестировать.

Для проверки выбрали наш предрелизный нагрузочный тест, включающий:

  • 1 час «прогрева» (пользователи входят в конференции);

  • 3 часа теста (пользователи включают/выключают микрофоны и камеры, создают шум, переходят из комнат). Имитируем поведение живой ВКС.

В качестве третьего участника теста добавили DragonFly — ещё один форк Redis с многопоточностью, но без MultiMaster.

Мы протестировали:

  • все три решения в Standalone-режиме;

  • Redis и KeyDB в режиме Sentinel;

  • KeyDB в режиме MultiMaster.

Напоминаем: наш кейс — это не key-value, а Pub/Sub.

Что показали тесты

В Standalone все три решения показывают схожее время ответа приложения — 290–350 мс. Задержки Redis — ~10 мс, с «ёлочкой» на отдельных ручках (связываем с переключениями контекста и накладками многопоточности). В режиме Sentinel Redis и KeyDB тоже дают схожие результаты — ~350 мс времени ответа приложения. В KeyDB снова видим «заборчик» на отдельных ручках, связанный с многопоточностью.

И наконец, гвоздь программы — Redis Sentinel vs. KeyDB MultiMaster. Среднее время ответа не меняется (340–360 мс). Ёлочка задержек Redis сохраняется почти на всех ручках — ожидаемо: MultiMaster и многопоточность требуют синхронизации между узлами.

В итоге ни одного критичного сбоя не зафиксировали. Даже с чётным количеством узлов всё отработало стабильно. Результаты мы передали в разработку и QA. Вердикт: все значения укладываются в допустимые границы, любой из тестов KeyDB прошёл бы внутреннюю оценку качества и был бы допущен в прод.

Ложка дёгтя

Но, как водится, не обошлось без ложки дёгтя. После всех тестов мы столкнулись с одним важным недостатком: на тот момент последний релиз KeyDB выходил более года назад, в 2023 году. А для нас, учитывая масштаб и критичность системы, такой «замороженный» релизный цикл — существенный минус, который перевешивает все технические плюсы KeyDB.

Поэтому, скрепя сердце, мы отказались от KeyDB. Вместо этого приняли решение разделить существующий Redis Sentinel кластер на три отдельных кластера: один только для Pub/Sub, два остальных — для key-value хранилищ разных компонентов.

Так, из одного 100%-загруженного Redis Sentinel кластера сделали три отдельных Redis Sentinel кластера, каждый из которых загружен примерно на 30–35%. Вероятнее всего, мы и дальше будем развиваться в этом направлении, чтобы выдерживать рост нагрузки.

Consul вместо ETCD

Следующий шаг: протянуть кластер Patroni на три ЦОДа, используя ETCD. И тут встаёт задача:  между ЦОДами нет L2, а значит, использовать Keepalived мы не можем.

Возникает вопрос, как направлять запросы приложений на Master-кластер без Keepalived и при этом сохранить надёжность, отказоустойчивость и высокую доступность?

Решение пришло откуда не ждали: Consul, благодаря своей агентской архитектуре. В каждом ЦОДе у нас есть свои прикладные компоненты. В конфигурации приложений прописан адрес балансировщика СУБД, который ведёт на локальный VIP балансировщика в своём ЦОДе.

Но возникает новый вопрос: как балансировщик узнаёт, где сейчас Master? Ответ: через Consul-агент, установленный на балансировщике.

В local resolver (локальном DNS-резолвере) указывается адрес Consul-агента. При запросе fqdn master.cluster.db локальный резолвер обращается к Consul-агенту. А Consul-агент запрашивает актуальные данные у Consul-сервера, который получает адрес Master от Patroni. В результате каждый балансировщик через Consul-агент всегда знает, где находится Master PostgreSQL, и направляет запросы приложений именно туда.

После тщательного тестирования этой конфигурации на Stage-стенде мы внедрили её в прод: сейчас у нас уже несколько кластеров PostgreSQL работают по этой схеме. Мы поделились этим решением на митапах внутри компании, и коллеги с других проектов успешно адаптировали и приняли схему в эксплуатацию.

А как же метрики и события?

Пару слов о сборе метрик и событий. У нас классический стек: Elasticsearch — для событий, VictoriaMetrics + Grafana + vmagent — для метрик.

Когда мы жили в одном ЦОДе, всё было понятно и просто. Когда переехали в два ЦОДа, то просто во втором ЦОДе развернули ещё один кластер для сбора и хранения метрик и событий. Не особо задумывались об удобстве: у каждого ЦОДа был свой интерфейс логов и свой кластер хранения метрик.

А сейчас у нас три ЦОДа.  И тут логичный вопрос: «Только не говорите, что теперь их будет три?» Нет!  Такой подход в трёхЦОДовой конфигурации оказался бы слишком неудобным.

Мы подошли иначе: создали Kafka-кластер, растянутый по трём ЦОДам. Все сборщики метрик и событий в каждом из трёх ЦОДов пишут данные в этот Kafka-кластер. Таким образом, Kafka стала транспортным слоем для всех логов и метрик.

В каждом ЦОДе развернули кластеры VictoriaMetrics и Elasticsearch. Агенты в каждом ЦОДе вычитывают события и метрики из Kafka и складывают их в локальные хранилища.

Благодаря этому подходу в каждом ЦОДе есть логи и метрики всей системы целиком. Не нужно думать, в интерфейс какого ЦОДа зайти, чтобы посмотреть логи конкретного компонента. И не надо переключаться между интерфейсами, чтобы отследить выполнение сценария, который затрагивает компоненты сразу в нескольких ЦОДах.

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

Итоги

В целом, мы решили задачи резервирования и надёжности. Сдали заказчику 99.99% SLA в 2024 году. Закрыли вопросы с сетью — сейчас общий трафик на маршрутизаторах (внутренний, межЦОДовый, внешний) превысил 15 Гбит/с, и оборудование даже не чихает. По ТТХ — запас прочности есть: оборудование держит до 80 Гбит/с UDP.

Но есть и нерешённые вопросы:

  • У нас нет GSLB, мы работаем над внедрением такого типа балансировки. Сейчас используем BGP AnyCast со всеми его минусами.

  • Мы смотрим в сторону региональных ЦОДов, что требует смены прикладного подхода и перехода на асинхронную событийную архитектуру.

  • Всё ещё хотим заменить Redis. Присматриваемся к Valkey, но решение пока не финальное.

  • Не можем закрыть вакансии, открытые ещё в начале 2023 года. Нам не хватает 1–2 инженеров в команду сопровождения при минимальной текучке.

Что хочется сказать напоследок:

  • Старайтесь избегать георезервирования в два ЦОДа. С нынешними технологиями это очень сложно! Схема с двумя ЦОДами съела кучу времени и была самой «короткой» итерацией, которой хотелось бы избежать. Не пришлось бы придумывать кастомную схему для Redis, использовать связку Pacemaker на две ВМ в разных ЦОДах — это хоть и рабочее решение, но имеет свои нюансы. Также пришлось отказаться от ручного переключения PostgreSQL и Kafka. А полноценный Active-Active остаётся под большим вопросом.

  • Даже с трёмя ЦОДами остаются нюансы:

    • Сетевая связность. Нужна низко-латентная сеть между ЦОДами, чтобы репликация и взаимодействие компонентов оставались эффективными.

    • Равномерное распределение нагрузки между компонентами.

    • Централизованный мониторинг и логирование, агрегирующий данные со всех ЦОДов и умеющий правильно оповещать об инцидентах.

    • Регулярное резервное копирование и хранение резервов в разных ЦОДах.

    • Тестирование отказоустойчивости — моделирование выхода из строя целого ЦОДа, чтобы быть уверенными в надёжности системы.

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


  1. vindy
    04.08.2025 14:49

    Пожалуй, новый рекорд по УДИВИТЕЛЬНОМУ соотношению мгновенных плюсиков и комментариев в корпоративных блогах) 36/0, совсем без палева. А статья хорошая сама по себе. @dolgonosic вы вроде живой и разумный, может, можете намекнуть, как у вас это делается, через "коллеги, переходим по ссылочке и ставим лайк", или таки у хабра есть услуга буста для корпоратов? Абсолютно детский интерес к наблюдаемому системно явлению, без моральных оценок.