Начнём не с серверов, а с одного человека. Покупатель нажал «Оплатить заказ», сайт ответил «Заказ принят», и в этот момент один из ваших двух серверов исчез. Вопрос всей статьи звучит так: где окажется эта последняя запись? Если вы можете ответить на него для каждого вида отказа, у вас есть архитектура. Если ответ «ну, там же второй сервер», у вас пока есть второй счёт за хостинг.

Ниже разберём, что именно покупает резервный VPS в другой стране, чего он не покупает вообще, и как посчитать допустимую потерю и время возвращения сервиса. Все числа в примерах условные: они нужны, чтобы показать ход рассуждения, а не результат чьих-то измерений.

Три вещи, которые мы привыкли называть «сайтом»

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

  • Экземпляр приложения — код, веб-сервер, рабочие процессы. Он, как правило, не хранит ничего ценного и воспроизводится из репозитория и конфигурации. Его легко запустить на втором сервере.
  • Данные — база, загруженные файлы, очереди, сессии. Они изменяются прямо сейчас, и каждая секунда работы основного сервера добавляет в них то, чего нет больше нигде.
  • Точка входа — то, как пользователь вообще находит ваш сервер: DNS-имя, IP-адрес, балансировщик. Если она указывает на мёртвый узел, живой резерв остаётся незамеченным.

Резервный VPS легко и дёшево закрывает первую сущность. Со второй начинаются компромиссы, а третья часто оказывается самым слабым звеном именно потому, что про неё забывают. Отдельный сервер в другой стране не делает автоматически отказоустойчивыми ни данные, ни вход. Он лишь даёт место, где можно выстроить эти механизмы.

Нормальный день: куда идут чтение и запись

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

Чтение здесь безобидно. Если каталог товаров или статья блога прочитаны с реплики, максимум, что случится, — пользователь увидит данные чуть более старые, чем есть на основном узле. Для витрины это часто приемлемо. С записью всё иначе: новый заказ, смена пароля, платёж, комментарий должны попасть в одно авторитетное место. В классической схеме это основной сервер, а резервный только получает копию.

Отсюда первое правило, которое стоит произнести вслух: в каждый момент времени у каждого вида данных должен быть ровно один узел, принимающий запись. Всё остальное — следствия этого правила.

Копия всегда немного позади

Как изменения из основной базы попадают на резерв? В PostgreSQL это журнал предзаписи (WAL): основной сервер пишет в него все изменения и передаёт их standby-серверу. В документации PostgreSQL о warm standby разобраны два режима, и разница между ними — это разница между двумя ответами на наш вопрос про последнюю запись.

При асинхронной передаче основной сервер подтверждает клиенту фиксацию, не дожидаясь, пока изменение доедет до резерва. Это быстро, но остаётся окно: всё, что подтверждено клиенту, но ещё не передано, при потере основного узла пропадёт. При синхронной фиксации основной сервер может ждать подтверждения от standby, и тогда окно потери сужается, но каждая запись теперь платит задержкой за путь до другой страны и зависит от того, жив ли резерв и сеть между площадками.

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

Есть и вторая деталь из той же документации. Пропускная способность канала должна превышать скорость образования WAL, а после отключения реплике нужно догнать основной сервер. Прикинем на условных числах. Пусть в обычное время база производит поток изменений, с которым канал справляется с запасом. Ночью запускается массовое обновление цен, журнал растёт в разы, и канал перестаёт успевать. Резерв отстаёт всё сильнее. Если основной узел погибнет именно в этот час, потеря будет не «пара секунд», а весь накопленный хвост. Окно потери — не константа, а величина, которая зависит от нагрузки в конкретный момент. Поэтому отставание реплики нужно измерять и показывать на мониторинге, а не предполагать.

Один пользователь, один отказ, одна временная линия

Теперь вернёмся к покупателю. Разложим его действие по времени при асинхронной репликации. Время условное, секунды выбраны для наглядности.

Заказ на временной линии отказа (условный пример, асинхронная репликация)
МоментЧто происходитГде лежит запись о заказе
T+0 сПокупатель нажимает «Оплатить», основной сервер фиксирует заказ и отвечает «принят»Только на основном сервере
T+1 сЖурнал изменений ещё в пути к резервуОсновной сервер, на резерве записи пока нет
T+2 сОсновной сервер пропадает: сбой диска, потеря хоста, сетевая изоляцияЕсли сервер потерян безвозвратно — нигде
T+3 сПокупатель видит страницу «Заказ принят» и ждёт письмаРезервная база этого заказа не знает
T+5 минВы замечаете отказ и повышаете резерв до основногоРезервная база принимает новые записи без этого заказа

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

При синхронной фиксации та же линия выглядит иначе: основной сервер не ответил бы «принят» до подтверждения от резерва. Потеря этого заказа исключается, но если резерв или канал недоступны, запись может начать ждать. Вы выбираете, что хуже: потерять подтверждённый заказ или на время перестать принимать заказы. Для разных сервисов правильный ответ разный, а для магазина с платежами и для блога он почти наверняка разный.

Две головы вместо одной: почему страшнее всего «оба живы»

Теперь самый коварный сценарий. Связь между площадками оборвалась, но оба сервера работают. Резерв не видит основного и решает, что тот умер. Автоматика или нетерпеливый администратор повышает его до основного. Часть пользователей по-прежнему попадает на первый сервер, часть уже на второй, и теперь заказы пишутся в две независимые базы.

Это называют split-brain, и у него неприятное свойство: простого способа слить две истории обратно в одну нет. Заказ номер 1042 на одном сервере и совсем другой заказ номер 1042 на другом, один и тот же пользователь с двумя разными корзинами. После этого вы выбираете, чьи записи считать истинными, а чьи вручную переносить или терять.

Защита здесь не в «умнее автоматике», а в дисциплине. Несколько приёмов, которые работают даже на двух серверах:

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

Честно признаем: с двумя узлами нет третьего голоса, который рассудил бы, кто из них прав. Если вы хотите автоматического выбора, потребуется третий участник-свидетель, иначе автоматика будет гадать. Для небольшого проекта разумнее оставить решение человеку и сделать его процедуру короткой и отрепетированной.

Точка входа: почему живой резерв никто не видит

Допустим, резерв повышен и готов. Как пользователи узнают о нём? Самый доступный способ — сменить DNS-запись на адрес резервного VPS. И здесь начинается область, где ожидания чаще всего расходятся с реальностью.

Вы ставите короткий TTL и считаете, что через пару минут все придут на новый адрес. Но, как следует из RFC 8767, резолвер вправе отдавать устаревшие кешированные данные, когда авторитетные серверы недоступны, а истечение TTL не гарантирует, что каждый резолвер тут же перестанет пользоваться прежней записью. На практике это значит: часть пользователей ещё некоторое время будет ходить на старый адрес, и вы не управляете этим временем. Короткий TTL уменьшает хвост, но не уничтожает его.

Отсюда следствие, которое влияет на план. Если старый адрес после отказа молчит, эти пользователи видят ошибку, пока их кеш не обновится. Если же старый сервер каким-то образом ещё отвечает и пишет, получается уже знакомый split-brain, только устроенный через DNS. Поэтому смена DNS и остановка записи на старом узле — единая операция, а не две независимые.

Альтернатива — держать перед обоими серверами один входной узел или балансировщик, и тогда пользователям не нужно менять адрес. Но входной узел сам превращается в единственную точку отказа, если он один и расположен в одном месте. Резервирование начинается с вопроса «что стоит перед серверами», и у него тоже нет бесплатного ответа. О схемах распределения трафика между несколькими площадками подробнее рассказано в материале про балансировку между серверами в разных странах.

Нужен сервер под эту задачу?

Разверните VPS MAATRIX за пару минут: NVMe, AMD EPYC, root-доступ, локации Россия, США, Франция и Великобритания. Оплата картой РФ и по СБП.

АРЕНДОВАТЬ СЕРВЕР

Сессии: тихая причина «все вышли из аккаунта»

Даже если данные и вход настроены, пользователь может заметить отказ по мелочи: его выбросило из личного кабинета, а корзина опустела. Причина — сессии. Если они хранятся в памяти приложения или в локальном файле основного сервера, резерв о них ничего не знает.

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

Если же вы используете балансировщик NGINX и хотите, чтобы пользователь стабильно попадал на один и тот же узел, пригодится ip_hash. По документации модуля upstream, он выбирает узел по IP клиента и направляет его туда же повторно, пока узел доступен, а при недоступности запрос может попасть на другой. Это именно «липкость на время», а не хранение сессии: после перехода на другой узел сессия, не лежащая в общем месте, будет потеряна. К тому же клиенты за общим адресом, например за корпоративным NAT, все окажутся на одном узле, и распределение нагрузки перестанет быть равномерным.

Приложение и данные восстанавливаются разными дорогами

Теперь разделим два понятия, которые обычно сливаются в одно слово «переключение».

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

Восстановление данных — отдельная история. Здесь вопрос не «как быстро», а «до какого момента». Реплика даёт вам состояние почти последнее, но она же с готовностью повторяет и ваши ошибки: если кто-то по ошибке удалил таблицу или приложение записало мусор, это быстро окажется и на резерве. От таких ошибок защищает не реплика, а история — копии на разные моменты времени.

Для файлов и каталогов такой историей может быть резервное копирование, например с помощью restic. В его документации по восстановлению сказано, что restore извлекает выбранный снимок в отдельный целевой каталог, причём --path влияет на выбор снимка, а --include и --exclude ограничивают извлекаемые файлы, и это не одно и то же. Есть также фильтры для выбора последнего снимка конкретного хоста и пути. Практическая польза для нашей темы: восстановление в отдельную директорию позволяет сначала посмотреть результат и лишь потом подменять рабочие данные. В ситуации, когда нервы на пределе, возможность проверить, что именно вы достаёте, ценнее скорости.

Запомним различие: реплика отвечает на вопрос «что, если сервер пропал», копии с историей — на вопрос «что, если мы сами всё испортили». Второй резерв в другой стране закрывает первое и ничего не обещает по второму, пока вы не настроите копирование с хранением нескольких состояний.

Что дают разные страны, а что они не обещают

Хочется сказать: «разные страны — значит независимые отказы». Это было бы удобно и неверно. Расположение в разных странах само по себе не гарантирует независимых причин отказа. У двух площадок может оказаться один и тот же крупный сетевой провайдер на маршруте, одна и та же ошибка в вашей конфигурации, общий сервис мониторинга, один аккаунт и один платёжный способ, одна и та же уязвимость в приложении. Всё это ломается одновременно, как бы далеко друг от друга ни стояли серверы.

Разнесение по странам реально помогает в других случаях: отказ в отдельной площадке или её локальной сети, недоступность определённого направления, проблемы маршрутизации к одному региону. Кроме того, у вас появляется возможность подвинуть пользователей ближе к ним по сети. Но «разные страны» — это ваш выбор площадок, а не свойство, которое надо принять на веру. Если для вас важна независимость, сверяйте её по конкретным признакам: кто провайдер, как устроено подключение, где лежат аккаунты и ключи. Заодно имейте в виду, что внешние API и платёжные сервисы могут по-разному относиться к адресам из разных стран, и это нужно проверять отдельно, а не считать данностью.

В публичном предложении MAATRIX перечислены локации UK, US, RU и FR, оплата картой РФ и СБП, а VPS создаётся автоматически через пару минут после оплаты; машины на NVMe с процессорами AMD EPYC и root-доступом. Для нашей темы важно следующее: два VPS в разных локациях — это два независимых по управлению узла, на которых вы сами строите схему репликации и входа. Доступность конкретных образов и операционных систем в каждой локации стоит уточнять в форме заказа. Автоматические резервные копии во всех тарифах не заявлены, поэтому копирование с историей нужно настраивать самим.

Сколько можно потерять и как долго можно молчать

Теперь соберём всё в два числа, без которых разговор о резервировании остаётся разговором о настроении.

Первое — допустимая потеря данных: сколько последних изменений вы готовы лишиться при худшем отказе. Второе — допустимое время возвращения: как долго сервис может быть недоступен. Эти значения задаются не технологией, а бизнесом. Хорошая проверка — спросить себя про каждый вид данных отдельно.

Допустимая потеря и простой по видам данных (условные ориентиры для обсуждения)
ДанныеЧем грозит потеря последних измененийРазумное направление
Статьи блога, каталогПропадёт свежая правка, которую можно повторитьАсинхронная реплика, копии по расписанию
Заказы и платежиРасхождение с деньгами, потерянные обязательстваСинхронная фиксация или сверка с платёжным провайдером, повторимые операции
Сессии и корзиныРаздражение пользователяОбщее хранилище или согласие войти заново
Загруженные файлыПотеря контента пользователейКопирование на отдельную площадку с историей

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

Теперь время возвращения. Оно складывается из пяти частей: сколько вы обнаруживаете отказ, сколько решаете, что это именно отказ, сколько повышаете резерв и проверяете его, сколько переключаете вход и сколько ждёте хвост DNS-кешей. Условно: 3 минуты на обнаружение, 5 на решение, 5 на повышение и проверку, 1 на смену записи, а потом ещё какое-то время хвоста, которое вы не контролируете. Даже на этой выдуманной арифметике видно, что «автоматического нулевого простоя» из двух серверов не получается. Зато видно, где можно выиграть: мониторинг сокращает первую часть, письменная процедура — вторую и третью, предварительно подготовленный вход — четвёртую.

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

Какие ресурсы понадобятся резерву

Резервный сервер можно сделать скромнее основного, если он только принимает изменения и ждёт. Но в момент повышения он должен выдержать всю нагрузку, поэтому экономить на нём опрометчиво: его размер определяет, что именно вы сохраните при отказе. Стартовая точка для проекта среднего размера — тарифы вроде Uab (2 vCPU, 4 ГБ, 80 ГБ) или Heka (4 vCPU, 8 ГБ, 160 ГБ), для небольших сервисов подойдёт Ba (1 vCPU, 2 ГБ, 40 ГБ), а Maat и Djed пригодятся там, где база и приложение заметно крупнее. Эти характеристики — отправная точка для теста, а не обещание ёмкости: реальную нагрузку покажет проверка на вашем приложении.

Два места надо прикинуть заранее. Диск резерва должен вместить базу, накапливаемый журнал и копии с историей. А канал между площадками должен с запасом переносить ваш поток изменений в пиковый час, иначе реплика будет отставать именно тогда, когда это опаснее всего. Если в каталоге MAATRIX вам нужен готовый сервис вроде n8n или Nextcloud, их установка предлагается, но точное состояние наличия и операционной системы проверяется в форме заказа; репликацию их данных всё равно придётся планировать отдельно.

Отказ, который такая схема переживает

Вернёмся к покупателю. Определим, при каком конкретном отказе схема из двух VPS в разных странах держит удар, и честно назовём цену.

Отказ: основная площадка полностью недоступна, например потерян хост или оборван доступ к её сети, а резервная жива. База настроена на синхронную фиксацию заказов, а резерв получает журнал. Сессии лежат в общем реплицируемом хранилище. Перед сервисом стоит процедура переключения с коротким TTL, а повышение резерва выполняет человек после проверки, что старый узел не пишет.

Что происходит: покупатель, чей заказ был подтверждён, не теряет его, потому что основной не ответил «принят», пока резерв не подтвердил запись. Заказы, которые не успели подтвердиться, получают ошибку, и пользователь повторяет попытку, а не видит ложный успех. После повышения резерва сервис возвращается за время, которое вы отрепетировали, плюс хвост DNS-кешей. Пользователей не выбрасывает из кабинетов.

Цена этого свойства складывается из нескольких пунктов, и скрывать их не стоит:

  • каждая подтверждённая запись заказа стала медленнее на путь до другой страны и обратно;
  • если сломается резерв или канал между площадками, основной может начать ждать подтверждений, то есть приём заказов остановится, хотя «основной жив»; нужно заранее решить, при каких условиях допустимо временно ослабить режим и принять риск окна потери;
  • возвращение сервиса всё равно занимает минуты и частично зависит от чужих кешей, а не от вас;
  • схема не защищает от собственных ошибок и от общих для обеих площадок причин, вроде бага в приложении или общей учётной записи; для этого нужны копии с историей и разнесённые доступы;
  • резервный сервер оплачивается круглые сутки и большую часть времени не приносит ничего, кроме спокойствия.

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