Возьмём конкретное приложение: сервис записи на консультации. Фронтенд и API на одном VPS в США, рядом PostgreSQL, очередь фоновых задач, загрузки пользователей на локальном диске, платёжный провайдер присылает webhook, почтовый сервис получает письма-подтверждения. Аудитория переехала в Европу, и приложение должно работать во Франции. Сервер можно поднять за вечер. Трудно другое: чтобы у заказов, оплат и расписания осталась одна история, а не две, которые потом придётся склеивать вручную.
Всё остальное в этой статье подчинено одному вопросу: кто в каждую минуту переезда имеет право записывать данные. Если ответ на него всегда однозначен, остальное решается инженерной аккуратностью. Если в какой-то момент отвечают «и тот, и другой», вы получаете расхождение, которое чинят неделями.
Архитектура, от которой мы отталкиваемся
Чтобы разговор не растёкся, зафиксируем схему. Один сервер приложения, одна база PostgreSQL на том же сервере, файлы пользователей в каталоге на диске, домен с A-записью на американский адрес. Это самый частый вид небольших сервисов, и у него есть полезное свойство: источник истины один. Нет мультимастера, нет шардов, и вопрос «где правда» решается просто — там, где сейчас работает база.
Для такой схемы реалистичны два пути переноса данных, и выбор между ними определяет весь план:
- Короткая остановка записи. Вы на время запрещаете новые изменения, снимаете финальный экспорт, загружаете его во Франции и переключаетесь. Просто, проверяемо, но есть окно, в котором сервис работает только на чтение или не работает вовсе.
- Потоковая репликация. Французская база становится репликой американской, догоняет её, и в момент переключения остановка нужна лишь на время, пока последние изменения доедут. Окно короче, но сложнее настройка, и появляются вопросы к сети между площадками.
Честно говоря, для сервиса с небольшим объёмом данных и терпимым ночным окном первый путь часто лучше: меньше движущихся частей. Второй оправдан, когда база велика, а окно недопустимо долгое. Обещать «переезд без единой секунды остановки» при экспорте-импорте нельзя: пока вы копируете, данные меняются, и без отдельного механизма синхронизации «безостановочно» не получится.
Французская копия, которая ничего не отправит наружу
Первая задача — получить рабочее окружение во Франции, на котором можно безопасно репетировать. Здесь и кроется главная ловушка: копия боевого приложения, поднятая «как есть», будет вести себя как боевое. Фоновые задачи начнут рассылать письма клиентам, планировщик попытается списать деньги по подпискам, интеграции отправят события во внешние системы. Для читателя письмо «Ваша запись подтверждена» от репетиции — это не шутка, а реальная путаница.
Поэтому репетиционную копию нужно сделать пригодной для проверки без живых побочных эффектов. Практические меры:
- подставить в окружение репетиции тестовые ключи платёжного провайдера и песочницу почтового сервиса вместо боевых;
- отключить планировщик задач, которые что-то отправляют или списывают, либо направить их в «ловушку» — адрес, где письма просто собираются;
- заменить адреса клиентов в данных, если копия делается с живой базы; о том, как обезличить данные и не сломать связи между таблицами, подробно написано в статье «Тестовая база — копия боевой: как обезличить и не сломать логику»;
- убедиться, что webhook-адреса в кабинетах внешних сервисов не переключены на репетицию.
Последний пункт обманчив. Провайдер платежей присылает уведомления на тот адрес, который вы указали в его кабинете. Пока вы его не меняли, репетиция этих уведомлений не получит — и значит, проверить её приём можно только искусственно: отправив себе тестовое событие из песочницы или воспроизведя сохранённый запрос. Это нужно сделать до переезда, а не в момент переключения, иначе вы узнаете о неверной подписи или закрытом порте уже на живом трафике.
Что проверять на репетиции
Не «открывается ли главная», а те пути, которые приносят деньги или фиксируют обязательства. Для нашего сервиса записи это создание записи, оплата тестовым платежом, получение webhook с подтверждением, отправка письма в ловушку, загрузка файла и его повторное открытие. Отдельно проверьте фоновую очередь: поставьте задачу и убедитесь, что воркер её забрал и результат записался в базу.
Заодно полезно сравнить скорость отклика. Утилита curl умеет печатать время этапов запроса: time_namelookup, time_connect, time_appconnect, time_starttransfer и time_total. Важная деталь из документации curl: эти значения накопительные, каждое отсчитывается от начала операции, поэтому складывать их как независимые этапы нельзя. Чтобы узнать, сколько ушло именно на TLS, вычтите время соединения из времени завершения рукопожатия. Если сравнивать французскую репетицию и американский боевой сервер из одной точки в Европе, видно, что выигрыш обычно складывается из сетевых этапов, а время обработки запроса приложением остаётся вашим, и от смены страны оно само не уменьшится.
Сколько накопится изменений, пока вы репетируете
Репетиция занимает время, а боевая база живёт. К моменту финального переноса копия отстанет, и важно заранее прикинуть, насколько. Пример условный, цифры выдуманы для расчёта: пусть база весит 20 ГБ, а за сутки в неё добавляется около 150 МБ изменений. Если вы сняли репетиционную копию во вторник, а переезд назначили на субботу, к субботе накопится порядка 600 МБ расхождения. Это не страшно, но означает, что репетиционную копию нельзя просто «довернуть» — финальный перенос должен либо заново залить всё, либо воспользоваться репликацией.
Важно понимать, чем именно вы снимаете копию. Утилита pg_dump, как сказано в документации PostgreSQL, создаёт согласованный экспорт одной базы, в том числе когда с ней одновременно работают пользователи. Это удобно: репетиционный дамп не требует остановки сервиса. Но у него есть граница: глобальные объекты — роли и tablespaces — в экспорт одной базы не входят, для них предусмотрен pg_dumpall. На практике это значит, что после загрузки дампа на новом сервере приложение может не подключиться, потому что нужной роли там нет. Роли и их права стоит перенести отдельно и проверить на репетиции, а не обнаружить при переключении.
Согласованность дампа — это согласованность на момент начала снимка. Всё, что записано после, в нём отсутствует. Это ровно та разница, которую вы посчитали выше, и поэтому финальный перенос требует отдельного решения.
Файлы пользователей живут отдельно от базы
Ещё одна история, которую дамп не охватывает, — загруженные файлы. База хранит ссылки и метаданные, а сами файлы лежат на диске. Если перенести базу, а файлы оставить, часть записей будет ссылаться на отсутствующие объекты. Файлы разумно синхронизировать заранее и в несколько проходов: первый тяжёлый, последний — быстрый, только изменения, уже после запрета записи. Тогда финальное окно зависит не от общего объёма каталога, а от того, сколько файлов добавили за последние часы.
Репликация: когда окно хочется сделать коротким
Если 20 ГБ в окне не укладываются или база намного больше, на сцену выходит потоковая передача журнала. В документации PostgreSQL о warm standby описаны два важных свойства. Во-первых, при асинхронной передаче WAL остаётся окно, в котором ещё не переданные изменения могут быть потеряны; синхронная фиксация, наоборот, может ждать подтверждения от standby. Во-вторых, пропускная способность канала должна превышать скорость образования WAL, а после разрыва связи реплике нужно время, чтобы догнать основной сервер.
Для нашего переезда из этого вытекают конкретные решения.
- Не включайте синхронный режим на межконтинентальном канале ради красоты. Тогда каждая фиксация транзакции в США будет ждать подтверждения из Франции, и пользователи почувствуют задержку на каждой записи. Для переезда достаточно асинхронной реплики, но тогда нужно помнить про окно потери.
- Окно потери закрывается запретом записи. Пока запись идёт, реплика всегда чуть позади. Когда вы остановили запись на старой стороне и дождались, пока реплика получила всё, окно исчезает — отставать больше нечему.
- Оцените скорость WAL. Если база пишет в пиковые часы быстрее, чем канал успевает передавать, реплика не догонит. Переезд назначают на спокойное время и сначала наблюдают, как отставание ведёт себя в нагрузку.
Здесь же проявляется, что репликация не отменяет остановки записи, а сокращает её. «Безостановочно» тут по-прежнему не получится: чтобы французская база стала главной, она должна получить последнюю транзакцию старой, а значит, новые записи на американской стороне в этот момент должны прекратиться.
Нужен сервер под эту задачу?
Разверните VPS MAATRIX за пару минут: NVMe, AMD EPYC, root-доступ, локации Россия, США, Франция и Великобритания. Оплата картой РФ и по СБП.
Временная шкала: где разрешена запись
Теперь главное. Ниже условная шкала переезда с отдельной фазой запрета новых изменений. Время приведено для примера, реальные значения вы определите репетицией.
| Фаза | Условное время | Запись разрешена | Что происходит |
|---|---|---|---|
| Подготовка | за несколько дней | США | Репетиция, понижение TTL, первичная синхронизация файлов и данных |
| Догонка | за несколько часов | США | Реплика или повторная заливка сокращают отставание Франции |
| Запрет изменений | T+0 — T+20 мин | Никому | США в режиме чтения или страница обслуживания; финальный перенос, проверка счётчиков |
| Передача права записи | T+20 мин | Франция | Французская база становится главной, приложение подключено к ней |
| Смена имени | T+25 мин | Франция | A-запись указывает на французский адрес, наблюдаем за клиентами |
| Хвост на старом адресе | после T+25 мин | Только Франция | Старый сервер не принимает записи, а перенаправляет или отвечает ошибкой |
Обратите внимание на колонку «Запись разрешена». В фазе запрета в ней никто, и это не пробел, а замысел. Именно эта пауза гарантирует, что в базе не появится транзакция, которую некому будет перенести. Её длина складывается из времени финального экспорта и загрузки (или догонки реплики), синхронизации хвоста файлов и ваших проверок: сверки количества заказов и последней записи по времени.
Как запретить изменения технически? Самый честный способ — на уровне приложения: включить режим обслуживания, при котором API отвечает на изменяющие запросы понятной ошибкой, воркеры остановлены, а планировщик отключён. Дополнительно можно перевести базу в режим только для чтения или временно закрыть подключения приложения, чтобы случайный фоновый процесс не записал лишнего. Особое внимание — к тому, что пишет без участия пользователя: фоновые задачи, повторные попытки, webhook.
Webhook и интеграции в момент запрета
Пока вы держите окно, внешний мир продолжает присылать события. Платёжный провайдер может прислать подтверждение оплаты именно в эти двадцать минут. Что с ним будет? Если старый сервер отвечает ошибкой, многие провайдеры повторяют доставку позже, но расписание и число повторов определяются конкретным провайдером, поэтому его документацию нужно прочитать заранее. Рассчитывать на то, что событие дойдёт само, не стоит без этой проверки.
Надёжнее подготовить собственные меры. Первая — обработчик webhook должен быть идемпотентным: повторное получение того же события не должно создавать вторую оплату. Опираться тут стоит на идентификатор события от провайдера и уникальное ограничение в базе, а не на идентификатор вашей попытки обработки. Вторая — после переключения сверить список событий в кабинете провайдера за время окна с записями во французской базе. Третья — не считать, что вызов внешнего API и запись в вашу базу происходят атомарно: если вы отправили запрос провайдеру, а затем началась остановка, состояние нужно уточнять по его данным, а не предполагать.
Отдельный вопрос — адреса и ограничения. Внешние сервисы могут разрешать обращения только с определённых IP. У французского сервера адрес будет другим, и если почтовый или платёжный провайдер ведёт белый список, новый адрес нужно внести заранее. Гарантировать, что сторонний API без дополнительных настроек будет отвечать новому иностранному адресу, нельзя, поэтому такие вызовы тоже входят в репетицию.
Смена имени и клиенты, которые не торопятся
Когда французская сторона приняла запись, остаётся направить к ней пользователей. Для этого меняется A-запись домена. Заранее, за срок не меньше прежнего TTL, значение понижают, чтобы кеши быстрее забывали старый адрес. Но TTL — не рычаг, а пожелание. В RFC 8767 описан режим serve-stale: резолвер может отдавать устаревшие кешированные ответы, если авторитетные серверы недоступны, и истечение TTL не гарантирует, что все резолверы мгновенно перестанут пользоваться прежней записью. Кроме резолверов, есть и клиенты со своим кешем: мобильные приложения, интеграции, старые скрипты, которые вообще запомнили IP.
Поэтому часть клиентов ещё какое-то время будет приходить на американский сервер. Этого нельзя избежать, но можно сделать безопасным. Главное правило: старый сервер больше не принимает записи в базу. Варианты поведения:
- Прозрачное проксирование. Старый сервер пересылает запросы на французский. Клиент ничего не замечает, но вы платите задержкой и держите зависимость от американской машины.
- Ответ с ошибкой на изменяющие запросы. Чтение допускается, запись — нет, клиент получает понятный отказ и повторит позже, когда имя обновится.
- Полная остановка приложения на старой стороне. Проще всего, но клиенты с залипшим адресом увидят недоступность.
Для небольшого сервиса удобен второй вариант: он не создаёт расхождения и не требует сложной маршрутизации. За хвостом полезно следить по логам: сколько запросов ещё приходит на старый адрес и откуда. Когда поток стихнет до единичных обращений, сервер можно выключать — но не удалять сразу, о чём ниже.
Возврат после новых записей: почему это не обратный DNS
Самая опасная иллюзия переезда: «если что, просто вернём A-запись назад». Это правда, пока Франция не приняла ни одной записи. С первой же новой оплаты или регистрации ситуация меняется. Американская база больше не содержит всей истории: в ней нет всего, что произошло после передачи права записи. Если вернуть DNS назад и принять записи там, вы получите две базы с разными продолжениями одной истории.
Поэтому план возврата делится на два состояния.
До первой записи во Франции. Откат прост: Франция ещё ничего не изменила, американская база остаётся эталоном. Вы возвращаете DNS и снимаете запрет записи. Именно поэтому в первые минуты после переключения полезно провести контрольные операции и убедиться, что всё работает, прежде чем открывать приложение для всех.
После записей во Франции. Теперь возврат — это новый переезд в обратную сторону. Нужно снова запретить изменения, перенести накопленные во Франции изменения в США (дамп, обратная репликация или выгрузка конкретных таблиц), сверить данные и только потом менять имя. Если этого не сделать, часть заказов окажется потеряна. Хорошо, если вы заранее решили, какие данные наиболее критичны: оплаты, новые клиенты, подтверждённые записи. Их можно вытащить из французской базы выборочно.
Практическое следствие: старую американскую базу нельзя «чистить» и выключать сразу после переезда. Её стоит сохранить в неизменном состоянии на определённый срок как точку опоры, но пометить как недействующую, чтобы никто случайно не начал писать в неё. Если вы настраивали репликацию, подумайте, нужна ли обратная — тогда американская база останется актуальной, а откат превращается в управляемую процедуру. Но это ещё одна система, которую надо настроить и проверить на репетиции, она не появляется сама.
Какой ресурс нужен французскому серверу
Для нашего приложения во Франции нужно место под базу, файлы и запас на импорт. Дамп и распакованная база одновременно занимают место, а если вы включаете репликацию, потребуется ещё и пространство под журнал. Подбирать конфигурацию лучше от измерений, а не от общих ожиданий: посмотрите, сколько памяти и процессора реально использует американский сервер в пике, и возьмите это за отправную точку.
На VPS MAATRIX доступна локация FR, а также UK, US и RU; у серверов NVMe-диски, процессоры AMD EPYC и root-доступ, который понадобится для настройки PostgreSQL, ролей и синхронизации. В линейке есть тарифы от Ba (1 vCPU, 2 ГБ RAM, 40 ГБ NVMe) до Djed (16 vCPU, 32 ГБ, 320 ГБ). Для нашего условного сервиса с базой в десятки гигабайт и файлами пользователей смотреть стоит в сторону более ёмких вариантов вроде Heka (4 vCPU, 8 ГБ, 160 ГБ) или Maat (8 vCPU, 16 ГБ, 240 ГБ), но это не обещание ёмкости, а точка для проверки: нагрузочная репетиция покажет, хватает ли ресурсов. Диск особенно важно считать с запасом: при загрузке дампа вам нужно и место под сам файл, и место под развёрнутую базу.
Практичный бонус для репетиции: сервер создаётся автоматически через пару минут после оплаты, поэтому французскую площадку можно поднять заранее, потренироваться и при необходимости пересоздать. Резервные копии автоматически в тарифы не включены в подтверждённом виде, поэтому копии данных перед переездом вы делаете сами и храните отдельно от обоих серверов.
Репетиция целиком, по таймеру
Последний шаг подготовки — прогнать всю шкалу, но на тестовой копии и с секундомером. Вы получите три числа, которых иначе не узнать: сколько занимает финальный экспорт и загрузка, сколько длится синхронизация хвоста файлов и сколько времени у вас уходит на собственные проверки. Их сумма и есть реальная длина фазы запрета. Если она не устраивает, нужна репликация; если устраивает, оставьте простой путь.
Во время репетиции отдельно сверяйте данные: количество строк в ключевых таблицах, последний идентификатор заказа, сумму оплат за период. Эти числа вы повторите в боевой день после загрузки, до того как передадите право записи. Совпали — идём дальше, не совпали — запись остаётся на старой стороне, и ничего не потеряно.
Проверьте и клиентские ожидания: что увидит пользователь в окне запрета, какое сообщение покажет API, что произойдёт с мобильным приложением, которое повторяет запрос. Проще всего это обнаружить заранее, на репетиции, а не по обращениям в поддержку.
Один владелец данных
Хороший переезд из США во Францию заканчивается не сменой A-записи и не письмом «мы теперь в Европе». Он заканчивается моментом, когда на вопрос «где сейчас актуальные данные» есть один ответ, и этот ответ — французская база. Американский сервер к этому времени не принимает записи, у него нет задач, которые что-то меняют, а оставшиеся клиенты получают отказ или перенаправление, но не создают собственных версий истории. Старая база лежит нетронутой как архив на случай разбирательств, и в ней никто не живёт. С этой минуты ваш сервис действительно живёт во Франции: быстрее для тех, кто рядом, и честнее для тех, кто хранит у вас свои заказы.