Москва · работаем по всей России Пн–Пт, 10:00–19:00 (МСК)
Инфраструктура 5 мин чтения

Деплой без простоев

Релиз не должен быть событием. Атомарное переключение, совместимые миграции и быстрый откат — как обновлять сервис незаметно.

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

Для этого не обязательны кластер и собственная платформа. Основные приёмы работают и на одном сервере.

Откуда берётся простой

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

  • Файлы меняются на месте. Пока идёт копирование, часть кода уже новая, часть ещё старая. Запрос, попавший в этот момент, получает ошибку.
  • Зависимости ставятся на живой системе. Пока идёт установка пакетов, приложение находится в полусобранном состоянии.
  • Миграция блокирует таблицу. Изменение структуры большой таблицы может остановить запись на минуты.
  • Код и база не совпадают. Новый код ждёт колонку, которой ещё нет, или старый обращается к той, которую уже удалили.
  • Кэш очищается под нагрузкой. Первые запросы после релиза одновременно пересобирают всё, что было прогрето.
  • Фоновые процессы продолжают работать на старом коде или обрываются посреди задачи.

На каждую из причин есть своё решение, и вместе они складываются в понятную схему.

Атомарное переключение релизов

Идея простая: новая версия не накатывается поверх старой, а собирается рядом. Каждый релиз лежит в собственном каталоге; общие данные — загрузки, журналы, файл с секретами — вынесены отдельно и подключаются ссылками. Веб-сервер смотрит не на конкретный релиз, а на символическую ссылку current.

/var/www/app/releases/2026-04-02_1015/
/var/www/app/releases/2026-04-02_1432/
/var/www/app/shared/
/var/www/app/current -> releases/2026-04-02_1432

# переключение: новая ссылка создаётся рядом и переименованием заменяет старую
ln -sfn /var/www/app/releases/2026-04-02_1432 /var/www/app/current.new
mv -T /var/www/app/current.new /var/www/app/current

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

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

fastcgi_param SCRIPT_FILENAME $realpath_root$fastcgi_script_name;
fastcgi_param DOCUMENT_ROOT   $realpath_root;

Несколько предыдущих релизов остаются на диске. Это и есть откат: та же операция со ссылкой, только в обратную сторону.

Миграции: сначала расширить, потом сжать

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

Переименование колонки при таком подходе превращается из одной операции в несколько релизов.

  1. Миграция добавляет новую колонку. Старый код её не замечает.
  2. Выходит релиз, который пишет в обе колонки, а читает по-прежнему из старой.
  3. Фоновая задача небольшими порциями переносит накопленные значения.
  4. Выходит релиз, который читает из новой колонки.
  5. Когда всё стабильно, отдельная миграция удаляет старую.

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

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

После переключения: проверка и откат

Переключить версию — половина дела. Вторая половина — быстро понять, что новая версия работает, и ничего не потерять по дороге.

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

Откат, который ни разу не проводили, — такой же миф, как бэкап, который ни разу не разворачивали.

Когда нужен второй контур

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

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

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

Чек-лист выкатки

  1. Релиз собирается в отдельном каталоге, переключение — одной операцией.
  2. Общие данные и секреты вынесены из каталога релиза.
  3. Миграции совместимы с предыдущей версией кода; разрушающие — отдельно и позже.
  4. Тяжёлые изменения схемы проверены на копии реального размера.
  5. Кэш прогревается до переключения, а не после.
  6. После переключения выполняется автоматическая проверка, при неудаче — автоматический откат.
  7. Фоновые процессы перезапускаются аккуратно.
  8. Откат отрепетирован, предыдущие релизы хранятся на сервере.
  9. Перед релизом есть свежая резервная копия базы.

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

Команда «Бинарность»

Разрабатываем сайты, сервисы и модули, администрируем серверы по SLA. Пишем о том, с чем работаем сами.

Обсудить задачу
Контакт · начать работу

Есть задача для нас?

Мы не только пишем статьи — строим софт и держим инфраструктуру. Расскажите о проекте.

  • Ответ и предварительная оценка — за один рабочий день
  • NDA и фиксация объёма работ до старта
  • Код, домены и доступы оформляются на вас

Оставить заявку

Ответим в течение рабочего дня. Телефон или e-mail — на выбор.

Обсудить проект

Опишите задачу — вернёмся с оценкой и планом в течение рабочего дня.