Запись в DNS изменена, новый сервер отвечает на проверочные запросы, графики спокойны. И вдруг в журнале старой площадки появляется строка: тот же клиент, тот же токен, тот же POST /v1/orders. Прошёл час, потом сутки, а он всё ходит по старому адресу. Он не упрямый и не сломанный. Он просто живёт по другим правилам, чем ваш план переезда.
Такой клиент может быть мобильным приложением, которое три месяца не обновляли. Может быть скриптом партнёра, где адрес зашит в конфигурацию. Может быть сервисом с долгоживущим соединением, которое вообще не заглядывает в DNS. Поэтому вопрос звучит не «как переключить адрес», а «как перенести публичный API, когда нельзя одновременно обновить всех клиентов». Ответ строится вокруг одной идеи: старый и новый сервер некоторое время работают вместе, а решение о конце этого периода принимают по наблюдениям, а не по календарю.
Три разных вещи под одним словом «адрес»
Когда говорят «клиенты ходят на старый адрес», смешивают три слоя. Их полезно разделить, потому что у каждого свои причины запаздывания и свои средства управления.
Имя сервиса. Это запись вроде api.example.com. Она отвечает на вопрос «куда сейчас стоит идти». Имя живёт в DNS и кешируется по пути к клиенту. Подробнее о типах записей можно прочитать в материале про настройку DNS для сервера.
Сетевое соединение. Это уже установленный канал между конкретным клиентом и конкретным IP. Если клиент держит keep-alive или пул соединений, он может неделями обращаться к прежнему серверу, ни разу не переспросив DNS. Изменение записи на такое соединение не влияет.
Контракт API. Это набор обещаний: пути, формат тела, схема токенов, коды ответов, смысл полей, порядок событий. Контракт не знает про DNS. Можно идеально переключить имя и сломать половину клиентов, потому что новый сервер вернул дату в другом формате.
Эти слои ломаются независимо. Адрес может смениться, а контракт остаться прежним. Адрес может остаться, а контракт уйти вперёд. Задача переезда состоит в том, чтобы менять слои по одному и каждый раз понимать, какой именно сейчас под вопросом.
TTL — это не приказ забыть старый сервер
TTL легко принять за таймер, после которого мир обязан увидеть новое значение. На самом деле он задаёт время, на которое recursive resolvers могут кешировать запись (документация Amazon Route 53). То есть это разрешение держать старое значение, а не обязанность его выбросить. Чем TTL длиннее, тем дольше кеширующие resolver могут пользоваться прежним ответом, и поэтому документация советует уменьшать TTL заранее, до изменения используемой записи.
Из этого следуют три практических вывода.
- Снижать TTL нужно за срок, не меньший прежнего значения. Если TTL был сутки, а вы снизили его за час до переезда, часть кешей всё ещё хранит запись с суточным сроком.
- Снижение TTL не сбрасывает уже существующие кеши мгновенно и не переключает открытое соединение. Оно лишь сокращает будущие сроки хранения.
- Даже при коротком TTL найдутся клиенты, которые не уважают его: приложения с собственным кешем имён, библиотеки, которые резолвят адрес один раз при старте, жёстко прописанные IP.
Поэтому правильная установка такая: TTL помогает сделать хвост короче, но не отменяет необходимость обслуживать старый адрес. Старый сервер после переключения записи не выключают, а оставляют работающим и наблюдают за ним.
Одна временная линия, два типа клиентов
Для наглядности возьмём условный пример. Есть публичный API заказов. Клиенты двух типов: «внимательные» (свежие приложения, которые следуют DNS и обновляются) и «долгоживущие» (старые версии, партнёрские скрипты, зашитые адреса). Дни ниже условные, их нужно подставить под свой случай.
| Этап | Что делаем | Внимательный клиент | Долгоживущий клиент |
|---|---|---|---|
| День −3 | Снижаем TTL записи, новый сервер поднят и проверен отдельным именем | Ничего не замечает | Ничего не замечает |
| День 0 | Меняем запись на новый IP | В течение срока кеша переходит на VPS | Продолжает ходить на старый сервер |
| Дни 1–14 | Оба сервера обслуживают один и тот же контракт | Работает на новом | Работает на старом, мы видим его в журнале |
| Дни 14+ | Старый адрес отвечает сигналом о смене, а потом закрывается | Не затронут | Получает предупреждение, обновляется или отваливается |
Важна не сама таблица, а то, что в строках «дни 1–14» у вас два живых сервера, которые обязаны вести себя одинаково. Именно здесь переезд чаще всего ломается: не в момент переключения записи, а в период, когда оба сервера считают себя основными.
Сосуществование старого и нового endpoint
Есть три рабочие схемы, и у каждой своя цена.
Старый сервер как прокси к новому
Старая площадка продолжает принимать запросы и пересылает их на VPS. Для клиента ничего не меняется, а вся логика и данные живут в одном месте. Это самая безопасная схема для записи, потому что у вас единственный источник правды. Минус в том, что старая площадка остаётся на критическом пути: пока она проксирует, вы платите за неё и зависите от неё.
Если вы используете nginx как прокси, помните про буферизацию и таймауты. Директива proxy_buffering управляет буферизацией ответов upstream; при отключении данные уходят клиенту по мере получения. А proxy_read_timeout относится к интервалу между двумя последовательными чтениями, а не ко всей длительности ответа (документация nginx). Для API это значит: долгий потоковый ответ не оборвётся, пока данные приходят достаточно часто, а вот молчащий upstream будет отрезан по таймауту. При проксировании через дополнительное звено стоит проверить, что поведение для клиента осталось прежним.
Два независимых сервера с общими данными
Старый и новый сервер работают самостоятельно, но смотрят в одну базу. Это возможно, если база доступна обоим, но тогда вы фактически не переехали: данные остаются на старой стороне, а между серверами появляется сетевая задержка. Такая схема годится как кратковременный этап, но не как конечное состояние.
Два сервера с раздельными данными
Самый опасный вариант. Запись пришла на старый сервер, чтение идёт с нового, и клиент видит, что созданный заказ «пропал». Если у вас нет понятного механизма синхронизации, не выбирайте эту схему. DNS не обеспечивает согласованность данных, и надеяться, что она появится сама, не стоит.
Для большинства небольших и средних API разумнее всего выглядит первая схема на переходный период с последующим переключением источника правды в одно определённое мгновение. Тогда вопрос «где сейчас хранятся заказы» всегда имеет один ответ.
Запрос на запись: где скрывается двойное выполнение
Чтение при переезде прощает многое: повторили запрос, получили тот же ответ. Запись не прощает. Представьте клиента, который отправил POST /v1/orders на старый сервер, не дождался ответа из-за сетевой паузы и повторил запрос уже на новый, потому что DNS как раз сменился. Если оба сервера пишут в разные места, заказов станет два. Если пишут в одно место без защиты, тоже два.
Здесь полезно вспомнить, как устроены повторные доставки у вебхуков. Stripe, например, предупреждает о возможных повторах и предлагает распознавать их по идентификатору события (документация Stripe). Это не контракт вашего API, но принцип переносится: если повтор допустим, у операции должен быть устойчивый ключ, по которому сервер узнаёт «это я уже обрабатывал».
Для собственного API это выглядит так.
- Клиент присылает ключ операции, например в заголовке. Если у ваших клиентов такого ключа нет, его нельзя изобрести за них задним числом, но можно построить естественный ключ из содержания запроса, если он однозначен.
- Сервер хранит выполненные ключи и результат первого выполнения в одном общем месте, доступном обеим сторонам на период совместной работы.
- Повторный запрос с тем же ключом возвращает сохранённый результат, а не создаёт новую запись.
Тут есть тонкость: просто сгенерировать новый идентификатор при каждой попытке не значит обеспечить идемпотентность. Если ключ меняется от повтора к повтору, сервер не может узнать повторный запрос. Ключ должен принадлежать намерению клиента, а не конкретной отправке.
Есть и вторая тонкость. Если запись в вашу базу и вызов внешнего сервиса выполняются как два шага, одна транзакция базы не делает их атомарными. Если на переходном периоде вы переносите такие операции, продумайте, что произойдёт, когда первый шаг выполнился на старом сервере, а второй должен выполниться на новом. Без отдельного подтверждённого механизма безопаснее на время перехода выбрать одно место выполнения таких операций и не раздваивать его.
Практическое правило: на время совместной работы каждая записывающая операция должна исполняться в одном определённом месте, а второй сервер либо проксирует её туда, либо использует общее хранилище ключей. Выбор между этими вариантами зависит от того, где в данный момент лежат ваши данные.
Нужен сервер под эту задачу?
Разверните VPS MAATRIX за пару минут: NVMe, AMD EPYC, root-доступ, локации Россия, США, Франция и Великобритания. Оплата картой РФ и по СБП.
Журнал обращений вместо календаря
Самый соблазнительный способ закончить переезд — назначить дату. «Через месяц отключим». Но месяц ничего не говорит о том, кто ещё ходит на старый адрес. Надёжнее сделать старый адрес источником наблюдений.
Что стоит записывать в журнал старого сервера на этот период:
- идентификатор клиента: ключ API, токен, имя приложения, заголовок User-Agent — то, что у вас реально есть;
- версию контракта, к которой обращается клиент;
- метод и путь, чтобы отделить чтение от записи;
- IP источника, если это помогает связаться с владельцем;
- время последнего обращения.
Из этого журнала собирается простая таблица: кто остался, как часто ходит и что делает. Внимательные клиенты исчезнут первыми. Дальше останется хвост, и этот хвост нужно разбирать вручную.
| Что видно в журнале | Вероятная причина | Разумное действие |
|---|---|---|
| Запросы идут регулярно, с одним и тем же ключом | Адрес зашит в клиенте | Связаться с владельцем ключа, дать срок |
| Запросы идут пачками раз в сутки | Плановая задача или скрипт | Выяснить расписание, предупредить заранее до следующего запуска |
| Запросы редкие и без идентификации | Забытая интеграция или проверка | Ответить предупреждением, наблюдать дальше |
| Запросы нарастают после смены DNS | Часть клиентов получила старый IP из кеша | Проверить TTL и сроки кешей, подождать |
Последняя строка напоминает: не всякая активность на старом адресе — это «забытый» клиент. Иногда это просто остаточный кеш, который сам закончится по истечении срока.
Предупреждение вместо внезапной тишины
Когда вы выясните, кто остался, можно начать мягко давать знать об окончании. Если контракт позволяет, старый адрес может возвращать в ответах дополнительный заголовок или поле с указанием нового адреса и ориентировочной даты закрытия. Это не заставит клиентов измениться, но превратит молчаливое отключение в предсказуемое событие для тех, кто читает ответы. Для тех, кто их не читает, придётся писать людям.
Токены, callback и версии ответов
Даже если адрес идеально сосуществует, контракт может разойтись по мелочам.
Токены
Токен, выданный старой системой, должен приниматься новой. Если вы не переносите секрет подписи или таблицу выданных ключей, клиент с валидным токеном получит отказ на новом сервере и решит, что сломался сам API. Проверьте, что на период совместной работы оба сервера проверяют токены по одним и тем же правилам и одному хранилищу, а отзыв токена действует на обоих сразу. Иначе отозванный ключ продолжит жить на той стороне, до которой отзыв не дошёл.
Callback и вебхуки
Если ваш API сам отправляет уведомления клиентам, у вас есть вторая пара адресов: те, куда вы шлёте. Клиент мог зарегистрировать callback-URL, и тот не изменится при вашем переезде. Но у вас поменяется исходящий IP. Если клиент ограничивает приём по списку адресов отправителя, новые уведомления с VPS не пройдут. Предупредите об этом заранее и дайте клиентам время обновить список.
Если вы принимаете входящие вебхуки от сторонних сервисов, помните про подпись. Stripe подписывает события, и проверка требует исходного тела запроса; если изменить тело до проверки, подпись перестанет сходиться (документация Stripe). Для переезда это значит: прокси-слой между сервисом и вашим приложением не должен переписывать, пересжимать или переформатировать тело. Это относится именно к такому типу подписей; другие сервисы устроены по-своему, и проверять нужно по их документации.
Там же указано, что долгую обработку следует отделять от быстрого успешного ответа. На переезде это удобно: быстрый приём события на любом из серверов, а обработка позже и в одном месте. Тогда не важно, на какой из двух адресов пришло событие, обработает его один исполнитель, и ключ события защитит от повторной обработки.
Версии ответов
Новый сервер может быть собран из другой версии кода, с другой библиотекой сериализации, с другим часовым поясом по умолчанию. Мелочи, которые ломают клиентов:
- порядок полей или формат чисел и дат;
- пустой массив вместо отсутствующего поля;
- другие тексты ошибок, на которые кто-то опирается в коде;
- другие заголовки, например регистр имён или состав CORS;
- другие лимиты размера тела и таймауты.
Удобный приём: на период совместной работы отправлять один и тот же набор запросов на оба сервера и сравнивать ответы. Расхождение в теле или статусе — повод разобраться до того, как оно обнаружится у клиента. Версию контракта лучше не менять одновременно с переездом: два изменения сразу не позволяют понять, какое из них сломало клиента.
Что даёт VPS в такой схеме
Переходный период требует гибкости: поднять второй сервер рядом, настроить прокси, вести подробный журнал, ограничить доступ по спискам, быстро поменять конфигурацию. На VPS с root-доступом всё это делается на своём уровне без ожидания возможностей платформы. VPS MAATRIX предлагает NVMe, процессоры AMD EPYC и root-доступ, в публичном предложении перечислены локации UK, US, RU и FR; сервер создаётся автоматически через пару минут после оплаты картой РФ или СБП. Точные параметры и наличие нужно смотреть в форме заказа.
Размер сервера зависит от вашей нагрузки, и универсального минимума здесь нет. Для небольшого API на старте можно рассматривать тариф Ba (1 vCPU, 2 ГБ RAM, 40 ГБ NVMe) или Uab (2 vCPU, 4 ГБ, 80 ГБ), для более нагруженного — Heka (4 vCPU, 8 ГБ, 160 ГБ) и выше, вплоть до Maat и Djed. Но эти цифры не обещание ёмкости: сколько клиентов выдержит ваш API, покажет только замер на вашей нагрузке. Полезно помнить и о том, что на переходный период вы временно держите оба сервера и логирование, а значит, запас по диску под журналы не лишний.
Признак конца: старый адрес замолчал сам
Теперь вернёмся к клиенту из начала. Он по-прежнему обращается к старому адресу, и это нормально, пока вы его видите. Конец переезда наступает не тогда, когда истёк назначенный срок, а когда выполнены наблюдаемые условия.
- В журнале старого адреса нет обращений с известными ключами за период, который длиннее самого редкого расписания ваших клиентов. Если у кого-то задача раз в неделю, смотреть нужно дольше недели, а лучше дольше месяца при месячных отчётах.
- Остаток обращений без идентификации сведён к шуму, и вы понимаете его природу: сканеры, проверки, боты.
- Запросы на запись на старом адресе отсутствуют вовсе.
- Предупреждения в ответах работали достаточно долго, и владельцам оставшихся ключей написано лично.
Когда все условия выполнены, старый адрес можно перевести в режим, где он отвечает явной ошибкой с указанием нового адреса, а не молчит. Следите за журналом ещё некоторое время: если кто-то проснулся, вы увидите его и сможете вернуть проксирование, пока старая площадка не удалена. Только после спокойного периода без обращений её стоит закрывать окончательно.
Такой конец не обещает переезда без простоя для любого клиента: тот, кто зашил адрес и не читает письма, всё равно однажды заметит изменение. Но он увидит его в понятной форме, а вы будете знать, кто это и что произошло. Переезд заканчивается тогда, когда старый адрес долго молчит, а не тогда, когда вы решили, что он должен замолчать.