План релиза — это план возврата
Прогон изменения через dev, staging и production снижает риск и ничего не доказывает. До релиза записанным должно быть то, как вернуть продакшен обратно.
- Выкатить
- Проверить в продакшене
Выкатить предыдущий артефакт
- Добавить поле
- Писать в оба
- Залить историю
- Читать новое
- Перестать писать старое
Отменить последний шаг, до отмеченного
- За флагом
- Проверить
- Открыть
Возврата нет. Проверять до того, как станет видно.
Каждая команда, с которой я работал, прогоняет изменения через окружения, и каждая выкатывала что-то, что прошло их все и всё равно сломалось в продакшене. Это не провал дисциплины. Это и есть природа окружений: выборка из реальности, снятая на условиях, которые вы сами и выбрали.
00 / Что staging на самом деле доказывает
Staging доказывает, что изменение не ломается теми способами, которые вы додумались проверить. Это настоящий и полезный результат, и он меньше, чем кажется, потому что в staging нет ровно тех частей продакшена, из-за которых случаются интересные отказы:
- объёма данных и того единственного арендатора, чьи данные устроены не как у всех;
- конкурентности: живых пользователей, делающих что-то одновременно, с уже прогретыми кэшами;
- интеграций в том состоянии, в котором они находятся сегодня;
- клиентов в тех версиях, которые у людей действительно запущены.
Поэтому исход релиза решает не вопрос «прошло ли на staging», а вопрос «если в продакшене это окажется неверным, что мы делаем в следующие десять минут». На живом продукте ответ должен существовать до выкатки, потому что после вы будете отвечать на него в цейтноте, на глазах у клиентов и в тот час, который сами и выбрали.
01 / Три класса изменений, и дёшево откатывается только один
Откатывается передеплоем. Только код: ни схемы, ни данных, записанных в новом виде. Вернуться — значит выкатить предыдущий артефакт. Из чего следует, что его нужно хранить и хотя бы раз откатиться намеренно, чтобы путь был известным, а не теоретическим.
Откатывается с работой. Изменение схемы или данных, сделанное аддитивно: добавить новое поле, писать в оба, залить историю, начать читать новое и только потом перестать писать старое. Каждый шаг обратим по отдельности, ради чего такой порядок и нужен. Запишите, какой шаг и есть точка невозврата, и переход через неё считайте отдельным решением со своей датой.
Не откатывается. Разрушительная миграция, ушедшее письмо, вызов чужого API, мобильная сборка, которая уже у людей в телефонах. Для них «откатить» — не план и никогда им не было. Работает другое: сделать изменение невидимым, пока оно не проверено. За флагом, в теневой таблице, в задаче, которая пишет туда, где пока никто не читает.
Большинство инцидентов, которые я видел в этой области, — это изменение из второго класса, с которым обошлись как с изменением из первого.
02 / Радиус поражения — до изменения, а не во время проверки
В SaaS для упаковочной отрасли, где я работал, фронтенд, бэкенд и мобильное приложение жили в разных репозиториях, у каждого был свой конвейер выкатки и свои окружения. Это хорошая структура: каждая часть выходит по своему графику. Она же кое-что прячет, и я это понял при проверке, а не при планировании: сходятся все они на одном слое данных, поэтому изменение общей схемы достаёт дальше того репозитория, который у вас открыт.
На практике это значит: до изменения ответьте на вопрос «что ещё это читает», посмотрев, а не вспомнив. Поле, которое вы удаляете из схемы GraphQL, запрашивают клиенты, которых выкатывали не вы. Веб-клиент чинится за полдня. Мобильный — нет, потому что версия в чьём-то кармане останется у человека, пока он не обновится, и тот, кто обновляется медленнее всех, определяет, сколько ваш бэкенд будет отдавать старую форму.
Архитектурное правило отсюда скучное и его стоит соблюдать: общий слой меняется аддитивно, а удаление происходит на один релиз позже, чем хочется.
03 / Ночной релиз покупает окно, а не безопасность
Если клиенты собраны в одном часовом поясе, релиз вне их рабочих часов — дешёвое и настоящее преимущество, и доступно оно не всем. Но стоит быть точным в том, что именно он покупает: не меньшую вероятность ошибки, а только время заметить и среагировать до того, как это кого-то коснётся.
Время полезно, лишь если им кто-то пользуется. То есть до релиза нужно решить, на что вы смотрите в первые пятнадцать минут — тот самый запрос, тот самый график, тот самый экран, по которому видно, что миграция сделала задуманное, — и кто принимает решение.
04 / Абзац, который делает всю работу
Не регламент. Пять строк на изменение, написанных, пока вся картина ещё у вас в голове:
- Что меняется.
- Что ещё это затрагивает.
- Как мы проверяем в продакшене, что получилось.
- Как возвращаемся.
- Когда перестаём чинить вперёд и возвращаемся.
Пятая строка — главная и обычно отсутствующая. В два часа ночи, когда данные клиента в неизвестном состоянии, никто не решает хорошо, сколько ещё продолжать попытки. Решить это заранее, когда оно ничего не стоит, и есть весь приём.
Конкретный вариант третьей строки, из миграции Node.js с 16 на 20 на том же продукте: проверкой было не «тесты зелёные». Проверкой был заранее определённый сценарий — завести арендатора, а затем сделать обычные вещи, которые делает клиент, — прогнанный руками по развёрнутой системе, потому что проблемы с зависимостями и рантаймом, которые та миграция породила, юнит-тесты не замечают.
05 / Что я бы выяснил перед следующим
- К какому классу относится изменение, письменно, и где у него точка невозврата.
- Что такое предыдущий артефакт и выкатывал ли его кто-нибудь намеренно хоть раз.
- Самая старая версия клиента, которая ещё в ходу, и безопасно ли для неё изменение.
- Одна проверка, доказывающая, что изменение сработало, и одна — что нет.
- Кто не спит и что ему разрешено решать в одиночку.
Длинная версия проекта, из которого всё это взято — что диагностировали, что мигрировали и как выпускали, — описана отдельным кейсом.