Запись A изменена, dig с вашего ноутбука показывает новый адрес, проверка из панели регистратора тоже довольна. А в журнале доступа старого сервера строки продолжают появляться: не сплошным потоком, а ровно так, чтобы нельзя было спокойно выключить машину. Вот кто-то с мобильной сети, вот чей-то скрипт, вот партнёрская интеграция, которая, кажется, не обновлялась с прошлой весны.
Такая картина не означает, что DNS «сломался». Она означает, что в слове «переключили» мы смешали три разных события: изменили источник ответа, дождались обновления промежуточных кешей и заставили клиентов реально прийти по новому адресу. Первое делается за секунды и подконтрольно вам. Второе и третье подконтрольны вам лишь частично.
Три места, где живёт старый адрес
Когда вы меняете запись у DNS-провайдера, вы меняете ответ авторитетного сервера — того, кто отвечает по зоне как первоисточник. Больше ничего вы не меняете. Клиентское приложение обычно с этим сервером напрямую не разговаривает: между ними стоит рекурсивный резолвер — у провайдера, у мобильного оператора, в корпоративной сети, в публичном DNS-сервисе или на самой машине клиента. Он спрашивает авторитетный сервер, получает ответ и запоминает его на срок, который указан в TTL.
Поэтому правильнее думать не об одной записи, а о цепочке из трёх уровней:
- авторитетный ответ — то, что отдаёт ваш DNS-хостинг прямо сейчас; он уже новый;
- кеш резолвера — копия ответа, которую хранит чужая инфраструктура; её возраст вы не видите и не сбросите;
- кеш приложения — адрес, который программа, библиотека, среда выполнения или операционная система запомнили на своей стороне, иногда вообще без оглядки на TTL.
Третий уровень самый неприятный. Долгоживущий процесс может разрешить имя один раз при старте и дальше держать адрес в памяти: например, пул подключений к API, воркер очереди, демон, который читает конфигурацию только при перезапуске. Для него «запись обновилась» — новость из другого мира. Он просто продолжает ходить туда, куда ходил.
Что на самом деле обещает TTL
TTL — это указание резолверу, сколько можно считать ответ свежим. Это не команда «через N секунд все на планете обязаны забыть адрес». Разница тонкая, но на ней держится вся тема.
Во-первых, отсчёт идёт не от момента вашего изменения, а от момента, когда конкретный резолвер получил ответ. Если резолвер запросил запись за минуту до правки и TTL был 3600, он будет отдавать старый адрес почти час после того, как вы всё поменяли. Другой резолвер, спросивший за секунду до истечения, обновится практически сразу. Поэтому окно переключения — не точка, а размазанный интервал, верхняя граница которого определяется старым TTL, но с оговорками, о которых ниже.
Во-вторых, стандарты допускают поведение, при котором резолвер выдаёт устаревшие данные. В RFC 8767 описан режим serve-stale: если авторитетные серверы недоступны, резолвер может ответить уже просроченной записью, лишь бы клиент что-то получил. Там же прямо следует из логики документа, что истечение TTL не гарантирует мгновенного отказа от прежней записи у всех резолверов. Для вашего переезда это значит: рассчитывать на секундомер нельзя, а на «подавляющее большинство клиентов за период около старого TTL» можно, но без обещаний насчёт хвоста.
Практический вывод: TTL нужно снижать заранее. Если записи с TTL в несколько часов поменять и сразу же переключить адрес, то новое малое значение распространится только после того, как истечёт старое. Поэтому порядок такой: сначала уменьшить TTL, подождать не меньше прежнего значения, и только потом менять адрес. Иначе вы снижаете TTL и одновременно живёте в мире, где его ещё никто не видел.
Разберём на цифрах, зачем нужна эта пауза. Допустим, прежний TTL был 14400 секунд (четыре часа), а вы за час до переезда поставили 300. Резолвер, который успел взять запись за минуту до этой правки, сохранит старый адрес и старый TTL: новое значение он увидит только после очередного запроса к авторитетному серверу, то есть почти через четыре часа. Если же подождать после снижения TTL хотя бы те же четыре часа, любая копия записи, созданная до правки, к моменту смены адреса уже истекла. Тогда в кеше не может оказаться записи со старым длинным сроком, и верхняя граница окна определяется уже пятью минутами, а не четырьмя часами.
Здесь же видно, чем плох расчёт «поставил 300 и жди пять минут». Пять минут отсчитываются от момента, когда конкретный резолвер получил ответ, а не от вашей правки. Кроме того, как следует из RFC 8767, истечение TTL не гарантирует, что каждый резолвер немедленно перестанет использовать прежнюю запись. Поэтому короткий TTL сжимает основную массу переходов, но не отменяет хвост, и за хвостом всё равно нужно наблюдать по журналам.
Три клиента, три разные истории
Чтобы увидеть, как это выглядит во времени, возьмём условный пример: сайт переезжает с прежнего VPS на новый в другой локации. Заранее TTL был снижен до 300 секунд и подождали достаточно, чтобы это значение разошлось. В 12:00 адрес в зоне изменён. Цифры здесь иллюстративные, это не замер.
| Время | Клиент А: браузер через резолвер провайдера | Клиент Б: серверный скрипт партнёра | Клиент В: мобильное приложение с открытым соединением |
|---|---|---|---|
| 11:58 | Резолвер получил старый адрес и хранит его ещё около 5 минут от момента запроса | Скрипт разрешил имя при старте утром и держит адрес в памяти | Установил соединение с прежним сервером и обменивается данными |
| 12:00 | Запись изменена, у резолвера всё ещё старая копия | Продолжает работать со старым адресом | Соединение живо, DNS в этот момент не нужен вовсе |
| 12:04 | Идёт на старый сервер, пока копия не истекла | Старый адрес | Старый сервер |
| 12:08 | Копия истекла, новый запрос даёт новый адрес — клиент переходит | Старый адрес: перезапуска не было | Старый сервер, пока соединение не закроется |
| Позже | Новый сервер | Старый адрес до перезапуска или до срабатывания собственной логики повторного разрешения имени | Новый сервер после обрыва и повторного подключения |
Обратите внимание, что причины у «отставания» разные, а лекарства тоже. Клиент А просто ждёт истечения кеша — его вы подтолкнуть не можете, только подождать. Клиент Б зависит от поведения кода: если вы его разработчик, можно добавить периодическое повторное разрешение имени; если нет, остаётся написать владельцу и оставить старый узел живым. Клиент В вообще не смотрит в DNS, пока соединение открыто: адрес выбирается один раз при установлении.
Полезно прикинуть, как эти три истории выглядят в журнале старого сервера. От клиента А строки исчезнут сами, примерно через пять-десять минут после правки, и исчезнут у разных пользователей в разное время, потому что у каждого резолвера своя копия записи с собственным моментом получения. От клиента В строки не появятся вовсе, пока соединение живёт, а затем он разом перейдёт на новый узел после переподключения: на графике это ступенька, не связанная с TTL. Клиент Б опаснее всех для планирования: он будет присылать запросы с прежнего адреса, пока кто-то не перезапустит процесс, и в журнале это выглядит как ровная тонкая линия, не убывающая вообще.
Поэтому один и тот же график «запросов на старом узле» нельзя читать как единую кривую затухания. Это сумма как минимум трёх разных процессов: истечения кешей, обрыва соединений и перезапуска чужого кода. Если на графике вы видите падение до небольшой ровной «полки», это почти наверняка клиенты типа Б, и ждать их ухода бессмысленно: им нужно письмо или проксирование, а не терпение.
Нужен сервер под эту задачу?
Разверните VPS MAATRIX за пару минут: NVMe, AMD EPYC, root-доступ, локации Россия, США, Франция и Великобритания. Оплата картой РФ и по СБП.
Открытые соединения: DNS тут ни при чём
Это отдельная история, и её часто путают с кешами. DNS отвечает на вопрос «куда идти». Когда соединение уже установлено, этот вопрос закрыт. Долгие TCP-сессии, WebSocket-каналы, потоковые подключения, keep-alive между балансировщиком и клиентом продолжают жить на прежнем узле, сколько бы раз вы ни меняли запись. Новый адрес они увидят только после разрыва и нового разрешения имени — если приложение вообще будет его делать, а не переиспользует старые данные.
Из этого следуют два практических решения.
- Не рубить старый узел, пока на нём есть живые сессии. Сначала перестаньте принимать новые подключения (если есть такая возможность), дайте существующим завершиться естественным образом, и только затем выключайте.
- Задать разумный предел жизни соединения на стороне сервера. Если сервер сам закрывает keep-alive после определённого времени простоя или числа запросов, клиент вынужден переподключаться, а значит, повторно разрешать имя. Это инструмент именно переходного периода, подбирать значения нужно под свой протокол и нагрузку.
Для измерения того, что происходит на стороне клиента, полезен curl с его переменными времени. Он различает time_namelookup, time_connect и time_appconnect — моменты завершения DNS, TCP-подключения и TLS-рукопожатия, считая от начала операции. Важная деталь из документации: эти показатели накопительные, и складывать их как независимые этапы нельзя. Если namelookup равен 0,02, а connect 0,06, то на сам TCP ушло 0,04, а не 0,08. Для задачи переезда этот инструмент показывает другое: действительно ли клиент у вас проходит через новое разрешение имени и куда его привело. Для прямой проверки адреса можно запросить имя и убедиться, к какому IP произошло подключение, или принудительно подставить адрес и сравнить поведение узлов.
curl -s -o /dev/null -w "%{remote_ip} %{time_namelookup} %{time_connect} %{time_appconnect} %{time_starttransfer} %{time_total}\n" https://example.com/Переменная remote_ip здесь показывает, на какой адрес в итоге пришло соединение, а накопительные времена помогают заметить, что новый узел отвечает не медленнее прежнего. Домен в примере, разумеется, ваш собственный вместо example.com.
Этот же запрос удобно использовать как точку наблюдения до и после правки. Запустите его с нескольких машин в разных сетях: с домашнего подключения, с мобильного интернета и с любого сервера в другой локации. Если у всех в remote_ip уже новый адрес, значит, основные резолверы на их пути обновились. Если у одной из сетей по-прежнему старый, вы нашли живой пример чужого кеша, который можно проверить повторно через несколько минут. Заметьте, что это проверка отдельных путей, а не гарантия для всех клиентов: она показывает, что видят ваши точки, и не доказывает, что хвост пуст.
Если же remote_ip новый, а time_namelookup заметно больше остальных значений, это просто холодный кеш: резолвер сходил к авторитетному серверу. Повторный запуск через секунду обычно покажет меньшее число, и эту разницу не стоит принимать за деградацию нового узла.
Два адреса на переходный период
Если принять, что старые клиенты будут приходить и после переключения, то выключать прежний узел сразу — значит сознательно ронять их. Спокойнее строить переезд как период совместной работы двух серверов. Старый и новый обслуживают одно и то же приложение, а различие только в том, сколько трафика приходит на каждый.
Что для этого нужно предусмотреть:
- Одинаковое поведение. Ответы на старом и новом узле должны быть совместимы: одинаковые версии приложения, те же сертификаты для имени, согласованные настройки. Иначе клиент, попавший на «не тот» узел, увидит другое поведение и вы не поймёте, где ошибка.
- Общее или синхронизируемое состояние. Если у приложения есть база, сессии, загруженные файлы, оба узла должны работать с одними данными либо вы должны чётко знать, какой узел пишет. Удвоенная запись в две независимые базы — это уже не переезд, а расхождение данных.
- Понятная роль старого узла. Он может либо полноценно отвечать, либо проксировать запросы на новый узел. Второй вариант удобен тем, что весь «хвост» старых клиентов фактически обслуживается новой системой, а старый VPS превращается в тонкий ретранслятор. Тогда вы управляете его судьбой в одной точке.
Если в схеме есть обратный прокси, следите за привязкой клиента к узлу. В документации модуля upstream NGINX описано, что ip_hash выбирает вышестоящий сервер по IP клиента и направляет его туда повторно, пока узел доступен; если выбранный сервер недоступен, запрос может попасть на другой. Для переезда это двоякий факт. С одной стороны, привязка по IP полезна, когда состояние сессии живёт на конкретном узле. С другой — она значит, что один и тот же клиент будет упорно ходить на прежний сервер, даже если вы хотите постепенно сместить нагрузку. Если вы рассчитываете на плавное перераспределение, стоит заранее решить, нужна ли вам такая липкость в переходный период, или сессии лучше вынести в общее хранилище.
Другой практичный приём — использовать на старом узле перенаправление или проксирование строго по именам. Если клиент приходит по домену, он получит корректный ответ. Если он приходит просто по IP (а такие интеграции встречаются: в конфигурации партнёра прописан адрес, а не имя), то DNS вообще не участвует и переключение записью ему не поможет. Таких клиентов можно найти только по журналам и обработать вручную: написать владельцам, договориться о дате, оставить узел с понятным ответом на период, пока они не перейдут.
Какой ресурс нужен новому узлу
В переходный период новый сервер получает сразу всё: свой трафик, трафик клиентов, перешедших по DNS, а иногда ещё и проксируемые запросы со старого. Нагрузка на него растёт быстрее, чем кажется по плану. Поэтому размер нового VPS лучше выбирать с запасом, а не «по текущему потреблению старого», и проверять под реальной нагрузкой. Ресурсы из тарифной линейки — это отправная точка для теста, а не обещание ёмкости.
Если вы разворачиваете новый узел в другой локации, у VPS MAATRIX в публичном предложении перечислены UK, US, RU и FR, а серверы используют NVMe, процессоры AMD EPYC и дают root-доступ. Для переезда это удобно практически: root позволяет поставить на старом узле прокси и перенаправление, а на новом — настроить всё под своё приложение. Тарифы идут от Ba (1 vCPU, 2 ГБ RAM, 40 ГБ NVMe) через Uab (2 vCPU, 4 ГБ, 80 ГБ), Heka (4 vCPU, 8 ГБ, 160 ГБ), Maat (8 vCPU, 16 ГБ, 240 ГБ) до Djed (16 vCPU, 32 ГБ, 320 ГБ). Чтобы выбрать, какой из них годится именно вашему приложению, прогоните нагрузочный тест на новом узле, пока старый ещё держит основной трафик. Наличие нужной ОС и образов в конкретной локации лучше проверить в форме заказа.
Есть ещё одно соображение, о котором часто забывают. Если новый узел стоит в другой стране или у другого оператора, часть клиентов может попасть на него с другой сетевой задержкой, а ваши сторонние зависимости — платёжные шлюзы, внешние API, списки разрешённых адресов — могут вести себя иначе. Поэтому разрешённые IP-адреса у партнёров нужно обновить до переключения, а не после жалобы. Это тоже форма «старых клиентов»: не они идут на прежний адрес, а прежний адрес зашит в чужих правилах.
Как понять, что старый узел можно отпускать
Здесь самое важное. Факт изменения DNS-записи — не критерий готовности к выключению. Критерий — поведение трафика на самом старом узле. Пока он принимает запросы, значит, кому-то он ещё нужен или кто-то ещё не знает о переезде.
Что имеет смысл наблюдать:
- Число запросов в единицу времени на старом узле. Здоровая картина — убывающая кривая с длинным хвостом. Резкая ступенька вниз после смены записи — хорошо, но не повод выключать: хвост просто стал тоньше.
- Разбивку по клиентам. Группируйте по IP источника, User-Agent, заголовку Host, пути. Так хвост превращается из «какого-то трафика» в конкретные списки: поисковые боты, мониторинг, партнёрский скрипт, мобильная версия, обращения по голому IP.
- Заголовок Host. Если запросы приходят с вашим доменом, их отправил DNS (возможно, из старого кеша). Если с IP-адресом или чужим именем, то DNS тут ни при чём, и ждать истечения TTL бессмысленно.
- Число открытых соединений. Долгие сессии могут не оставлять новых строк в журнале доступа, пока работают, но видны в списке активных подключений на сервере.
Полезно записывать, когда каждый «хвостовой» источник исчез. Это превращает ожидание в наблюдение: вы видите, что партнёрский скрипт ушёл после перезапуска, а мобильные клиенты — по мере обновлений и переподключений. Так вы лучше понимаете реальное окно переезда в вашей конкретной системе, а не опираетесь на значение TTL из учебника.
Выключение стоит проводить как две отдельные операции. Сначала старый узел переводится в режим «только ответ с перенаправлением или заглушкой»: он продолжает слушать, но возвращает понятное сообщение либо проксирует на новый. Это даёт возможность поймать оставшихся клиентов и увидеть, кто они. Только когда в течение осмысленного для вашего сервиса периода (суток, недели — зависит от ритма клиентов, например, от еженедельных отчётов или ночных заданий) не приходит ничего, кроме собственных проверок, узел можно останавливать. Останов тоже лучше сделать обратимым: сначала выключить, не удаляя, и только потом освободить ресурсы. Если у кого-то еженедельное задание, вы узнаете о нём именно в этот момент.
Если же переезд связан с ограничениями на число запросов, не забудьте, что на новом узле лимиты начинают отсчёт с нуля, а клиенты, перешедшие волной, могут упереться в них одновременно. Подробнее о том, почему честные клиенты страдают первыми, написано в статье про rate limiting.
Хвост как показатель, а не как недоразумение
Хорошо проведённый DNS-переезд выглядит скучно: TTL уменьшен заранее, адрес изменён, два узла какое-то время работают одинаково, трафик на старом тихо затухает, а в журналах остаётся несколько узнаваемых источников. Каждый из них вы либо перевели, либо согласовали, либо сознательно отпустили. Выключение прежнего VPS происходит не в момент, когда запись в зоне стала новой, а в тот момент, когда на нём перестали появляться чьи-то обращения. Сама смена записи — только начало этого наблюдения, и закончить его честнее счётчиком запросов на старом узле, дошедшим до нуля и продержавшимся там достаточно долго.