Миф: балансировщик и два сервера — это отказоустойчивость
«У нас балансировщик и два сервера — если один упадёт, второй подхватит» — фраза, после которой обычно наступает спокойствие в команде и внимание переключается на что-то другое. Проблема в том, что эта схема описывает только один слой инфраструктуры, а отказоустойчивость либо есть у всей цепочки от пользователя до данных, либо её нет вообще — слабое звено определяет реальную живучесть сервиса, а не самое сильное. Разберём, какие точки отказа обычно остаются за кадром у такой схемы и что нужно проверить, прежде чем называть её отказоустойчивой.
Содержание
- Что на самом деле решает связка «балансировщик + два сервера»
- Балансировщик как единственная точка входа
- Общая база данных: узкое место, которое остаётся за кадром
- Sticky sessions и состояние приложения: почему второй сервер не спасает пользователя
- Общая инфраструктура и дата-центр как скрытая зависимость
- Как на самом деле проверить отказоустойчивость всей цепочки
Что на самом деле решает связка «балансировщик + два сервера»
Схема с балансировщиком перед двумя веб-серверами закрывает ровно одну задачу: если один из бэкендов перестаёт отвечать на health-check, балансировщик перестаёт слать на него трафик и продолжает слать на второй. Это реальная и полезная отказоустойчивость — но только на уровне процесса приложения на конкретной ноде. Она защищает от падения одного из двух процессов nginx/Node.js/PHP-FPM, от зависшего воркера, от исчерпания памяти на одном сервере.
Дальше начинаются вопросы, на которые схема ответа не даёт:
- Кто балансирует трафик, если упадёт сам балансировщик?
- Куда пишут и откуда читают данные оба сервера — и что будет, если это единая база без своей отказоустойчивости?
- Что произойдёт с пользователем, у которого была активная сессия или файл в процессе загрузки на упавшем сервере?
- Что если оба сервера физически стоят в одной стойке одного дата-центра и пропадает электричество или сеть на уровне здания?
Каждый из этих вопросов — отдельная точка отказа, которая не решается фактом наличия балансировщика. Ниже — по порядку, с чем конкретно это выглядит на практике и как проверить, есть ли у вас эта проблема.
Балансировщик как единственная точка входа
Самая частая и самая недооценённая проблема: сам балансировщик почти всегда один. Мы ставим его именно для того, чтобы устранить единую точку отказа на уровне приложения — и в процессе создаём новую единую точку отказа на уровне входа в систему. Если это единственный nginx или HAProxy на одном сервере, а сервер выключился, ушёл в перезагрузку или потерял сеть — недоступны становятся оба бэкенда одновременно, хотя формально оба живы и готовы принимать трафик.
Проверить это просто: посмотрите, сколько инстансов балансировщика реально обслуживают продакшн-трафик и что произойдёт с IP-адресом, на который смотрят клиенты, если этот единственный инстанс перестанет отвечать. Если ответ «ничего, трафик продолжит идти на мёртвый IP, пока кто-то руками не переключит» — балансировщик и есть узкое место всей схемы.
Устраняется это на нескольких уровнях, и они не взаимозаменяемы:
- Резервный инстанс балансировщика + плавающий IP (VRRP/keepalived). Два сервера с nginx или HAProxy, между ними keepalived держит виртуальный IP на активном узле; при падении активного VRRP-адрес за секунды переезжает на резервный. Пример базового конфига keepalived на резервном узле:
vrrp_instance VI_1 {
state BACKUP
interface eth0
virtual_router_id 51
priority 90
advert_int 1
authentication {
auth_type PASS
auth_pass secretpass
}
virtual_ipaddress {
10.0.0.100/24
}
}
Здесь важна деталь, которую часто упускают: priority на резервном узле должен быть ниже, чем на основном, иначе при восстановлении сети может начаться конфликт за IP (split-brain на уровне двух active-балансировщиков — отдельная и неприятная история, разобранная в статье про split-brain в кластере базы, тот же механизм применим и к паре балансировщиков).
- DNS-балансировка на уровне двух независимых IP (несколько A-записей на разные балансировщики) — проще в развёртывании, но зависит от того, как быстро клиенты и резолверы подхватывают изменение записи при отказе, и не даёт мгновенного переключения, если один из IP просто перестал отвечать, а не выдал ошибку.
- Managed-балансировщик облачного провайдера — снимает заботу о резервировании самого балансировщика с вас, но добавляет зависимость от инфраструктуры и SLA конкретного провайдера, которую тоже стоит явно учитывать, а не считать её отказоустойчивость бесконечной.
Отдельно стоит проверить, что сам health-check балансировщика проверяет реальную готовность сервиса, а не просто открытый порт — иначе балансировщик может продолжать слать трафик на ноду, которая держит соединение, но не отвечает по существу. Этот конкретный сценарий с примерами из практики разобран в статье «Балансировщик слал трафик на мёртвую ноду».
Нужен сервер под эту задачу?
Разверните VPS MAATRIX за пару минут: NVMe, AMD EPYC, root-доступ, локации UK, США, Франция и РФ. Оплата картой РФ и по СБП.
Арендовать серверОбщая база данных: узкое место, которое остаётся за кадром
Типичная схема «балансировщик + два сервера» подразумевает два одинаковых веб-сервера, которые оба ходят в одну и ту же базу данных. Это логично — данные должны быть согласованными для обоих серверов — но именно здесь чаще всего прячется настоящая единая точка отказа всей системы.
Если оба веб-сервера подключены к одному инстансу PostgreSQL или MySQL без репликации, отказоустойчивость на уровне фронтенда не имеет значения: как только падает база, оба сервера одновременно начинают отдавать ошибки подключения к БД, несмотря на то что оба физически живы и балансировщик исправно распределяет между ними трафик. С точки зрения пользователя сайт полностью недоступен — ровно так, как если бы вообще не было ни балансировщика, ни второго сервера.
Это не теоретический риск, а частая причина, по которой красиво спроектированная схема на практике не спасает от простоя: бюджет вложен в удвоение слоя, который падает реже, а слой, который падает чаще и без резервирования кладёт всё сразу, остаётся нетронутым.
Варианты схемы БД и что каждая реально защищает:
| Схема БД | Что защищает | Что остаётся уязвимым |
|---|---|---|
| Один инстанс, без реплики | Ничего на уровне отказа БД | Полный простой при падении сервера с базой |
| Асинхронная репликация (primary + replica) | Данные читаемы с реплики, можно вручную промоутнуть её в primary | Небольшое окно потери последних транзакций (replication lag), ручное переключение без автоматизации |
| Синхронная репликация | Гарантия, что подтверждённая транзакция есть на реплике | Задержка записи растёт, если реплика недоступна — платите доступностью за надёжность |
| Managed-кластер БД с автоматическим failover | Автоматическое переключение на реплику при отказе primary | Зависимость от конкретного провайдера, задержка на детектирование отказа (обычно не мгновенная) |
Настройка базовой асинхронной репликации PostgreSQL — сравнительно недорогой первый шаг, который закрывает самый грубый риск («упал единственный сервер с базой — потеряли всё») и подробно описан в статье «Как установить и настроить репликацию PostgreSQL на VPS». Оговорка здесь важна: «просто включить репликацию» не отменяет необходимости продумать, кто и как выполнит переключение на реплику при реальном отказе — либо автоматика для этого есть и протестирована, либо переключение будет ручным и займёт время, которое стоит закладывать в ожидания по простою заранее.
Sticky sessions и состояние приложения: почему второй сервер не спасает пользователя
Даже если балансировщик задублирован и база реплицирована, остаётся третий слой, который чаще всего ломает саму идею отказоустойчивости незаметно — состояние конкретной пользовательской сессии.
Если приложение хранит сессию в памяти процесса или в локальном файле на диске конкретного сервера, а балансировщик настроен с sticky sessions (привязка клиента к одному и тому же бэкенду по cookie или IP-хэшу) — вся схема держится на том, что закреплённый сервер остаётся живым. В момент, когда он падает, пользователь не просто теряет незначительную задержку — он теряет сессию целиком: разлогинивается, теряет содержимое незаконченной формы, корзину покупок или прогресс загрузки файла, потому что второй сервер ничего не знает о его состоянии. Формально балансировщик честно переключил трафик на живой сервер — но с точки зрения пользователя сервис только что «упал» для него лично.
Это именно тот случай, когда наличие двух серверов создаёт иллюзию отказоустойчивости, которая рассыпается в конкретный момент отказа. Подробный разбор механизма — с чем именно приходится расплачиваться за удобство sticky-сессий и в каких сценариях это осознанный компромисс, а в каких скрытая проблема — в статье «Липкие сессии изнутри: чем система платит за то, что пользователь возвращается на тот же сервер». Похожая проблема возникает и с вебсокет-соединениями, когда состояние подключения существует только в памяти конкретной ноды — разбор такого случая с раскидыванием вебсокетов по нодам есть в статье «Балансировщик раскидывал вебсокеты по нодам, а состояние было локальным».
Решение здесь архитектурное, а не про настройку балансировщика:
- Вынести сессии во внешнее хранилище, доступное с любого сервера — Redis или Memcached с своим резервированием, а не просто «ещё один процесс на одном из двух серверов» (иначе вы просто перенесли единую точку отказа на уровень хранилища сессий).
- Не хранить файлы локально на сервере приложения — загрузки, сгенерированные отчёты, временные файлы должны идти в объектное хранилище или на shared-volume, иначе переключение на второй сервер теряет то, что физически осталось лежать на первом.
- Если stateless-архитектура пока недостижима, честно фиксировать это как известное ограничение: sticky sessions с локальным состоянием — рабочий компромисс для не самых критичных сценариев, но его нельзя одновременно называть «отказоустойчивостью» в смысле «пользователь не заметит отказа сервера».
Общая инфраструктура и дата-центр как скрытая зависимость
Даже когда балансировщик задублирован, база реплицирована и приложение stateless, остаётся уровень, о котором часто просто не думают, потому что он не относится к «серверам» в узком смысле — сеть и физическая площадка.
Если оба сервера (и оба балансировщика, и оба узла базы) находятся в одной стойке одного дата-центра, они разделяют одни и те же физические зависимости: один ввод электропитания, один аплинк в интернет, один коммутатор верхнего уровня, одно охлаждение помещения. Отказ на этом уровне — а к нему относятся не только полное обесточивание, но и, например, авария коммутатора или временная потеря аплинка у конкретной площадки — кладёт всю схему целиком одновременно, независимо от того, сколько серверов и реплик внутри неё настроено.
Это тот случай, где ответ не «купите ещё серверов», а «явно решите, какой уровень отказа вы готовы пережить и сколько это стоит». Полное резервирование на уровне двух независимых дата-центров или регионов — совсем другой порядок сложности (синхронизация данных через интернет, стоимость межрегионального трафика) и часто экономически не оправдан для проекта, которому не нужна доступность уровня «переживает отказ целого ЦОД». Уровни отказоустойчивости и их стоимость по шагам разобраны в статье «Стоимость отказоустойчивости по уровням: от одного сервера до кластера».
Практический минимум даже без перехода на мульти-датацентр: находятся ли два ваших сервера физически на разных хостах виртуализации (а не на одном гипервизоре, где второй сервер — просто вторая виртуалка на той же железке) и не привязана ли доставка трафика к единственному сетевому маршруту без альтернативы.
Как на самом деле проверить отказоустойчивость всей цепочки
Настоящая отказоустойчивость не проверяется чтением схемы архитектуры — она проверяется перебором каждого компонента с вопросом «что произойдёт с сервисом, если именно этот элемент откажет прямо сейчас». Практический способ — выписать цепочку от пользователя до данных как список и для каждого звена явно ответить на три вопроса: задублировано ли оно, автоматическое ли переключение и сколько данных или состояния теряется при отказе.
Пользователь
→ DNS / точка входа — единая запись или несколько независимых?
→ Балансировщик — один инстанс или пара с VRRP/keepalived?
→ Веб-сервер / приложение — сколько нод, где хранится состояние сессии?
→ База данных — один инстанс или репликация с failover?
→ Хранилище файлов — локально на сервере или во внешнем сторидже?
→ Сеть / дата-центр — общий аплинк, стойка, электропитание?
Для каждой строчки полезно не полагаться на предположения, а проверить фактически: временно остановить конкретный компонент на тестовом окружении (или в спокойное время на проде, если это допустимо) и посмотреть, что реально произойдёт — сервис продолжит работать без вмешательства человека, потребует ручного переключения, или упадёт целиком. Именно на этом шаге обычно выясняется разница между «отказоустойчивость есть в презентации» и «отказоустойчивость есть на самом деле».
Резервирование каждого следующего звена стоит дороже предыдущего, и не всегда экономически оправдано резервировать всё до идеала — иногда правильный ответ «мы осознанно принимаем риск простоя базы на N минут при ручном восстановлении из бэкапа, потому что дублирование не окупается для нашей нагрузки». Это нормальная инженерная позиция, если она принята осознанно, а не по умолчанию из-за того, что до этого звена цепи просто не дошли руки.
Нужен сервер под эту задачу?
Разверните VPS MAATRIX за пару минут: NVMe, AMD EPYC, root-доступ, локации UK, США, Франция и РФ. Оплата картой РФ и по СБП.
Арендовать серверНужны сами нейросети для контента?
Генерируйте изображения, видео и озвучку нейросетями на falapi.io — десятки моделей в одном окне. Оплата картой РФ и по СБП.
Частые вопросы
Если у меня нет бюджета на резервирование всех звеньев сразу, с чего начинать?
С самого дешёвого и самого разрушительного при отказе звена — обычно это база данных без единой реплики: её отказ кладёт весь сервис целиком, а базовая асинхронная репликация стоит сравнительно немного по инфраструктуре и настраивается за один заход.
Managed-балансировщик облачного провайдера снимает проблему единой точки отказа полностью?
Он снимает с вас заботу о резервировании самого балансировщика, но не отменяет остальные звенья цепи — базу, состояние приложения, сеть. И стоит отдельно уточнить у провайдера SLA конкретно на балансировщик, а не считать его отказоустойчивость бесконечной по умолчанию.
Sticky sessions — это всегда плохо для отказоустойчивости?
Нет, это осознанный компромисс, а не однозначная ошибка. Проблема не в самих sticky sessions, а в том, что их часто включают, не проговорив вслух: «мы принимаем, что при отказе сервера часть активных пользователей потеряет сессию». Если это принято сознательно — нормально; если про это просто не думали — это и есть скрытая точка отказа.
Как понять, что двух серверов вообще недостаточно и нужен полноценный кластер?
Ориентируйтесь не на количество серверов, а на цепочку зависимостей из раздела выше — если хотя бы одно звено в ней не задублировано, добавление третьего веб-сервера ничего не даст, а если задублированы все звенья до сети и дата-центра, часто оказывается, что двух серверов достаточно для целевого уровня доступности.
Нужно ли сразу мигрировать на несколько дата-центров, чтобы закрыть последний пункт про инфраструктуру?
Не обязательно и часто не оправдано — резервирование на уровне одного дата-центра (разные хосты виртуализации, дублированный ввод питания и сети внутри площадки) закрывает подавляющее большинство реальных отказов; переход на мульти-датацентр стоит рассматривать отдельно, когда цена простоя явно превышает цену такой миграции.
Обсудить статью, задать вопрос или начать новую тему
Есть вопрос по этой статье, идея для обсуждения или просто хотите поделиться опытом? Сообщество MAATRIX ждёт. Для общения, пожалуйста, зарегистрируйтесь в нашем личном кабинете.
Перейти в сообщество →