Платформенная инженерия Миграции: как переводить десятки команд и не сорвать поставку
0%

Миграции: как переводить десятки команд и не сорвать поставку

Миграции: как переводить десятки команд и не сорвать поставку

Платформенная команда выбрала новый рантайм. Причины настоящие: старый стек держится на трёх самописных скриптах, у одного из них нет автора уже год, апгрейд базового образа занимает две недели ручной работы. Посчитали, что новый вариант снимет с платформы четверть человека постоянной поддержки, а командам даст выкат за четыре минуты вместо девятнадцати. Написали внятный документ, согласовали с руководством, разослали письмо: «до конца квартала всем перевести свои сервисы, инструкция по ссылке».

Через квартал переведено 38 % сервисов. Через пять месяцев — 88 %, и кривая встала. Через год живы оба рантайма: в старом остались четырнадцать сервисов, из них четыре — критичные платёжные, три принадлежат команде, которая весь год выпускает новый продукт и физически не имеет свободного спринта, два не принадлежат никому (авторы уволились), а про остальные пять никто не может сказать, работают они вообще или просто крутятся. Платформа год держит две дежурки, два конвейера и два счёта в облаке. Экономия, ради которой всё затевалось, не наступила ни на день: она наступает только в момент выключения старого, а старое не выключено.

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

Это не история про плохую команду или ленивых пользователей. Это история про неверную модель: миграцию считали технической задачей, у которой есть инструкция и срок. На самом деле миграция — это продуктовое изменение с одним неприятным свойством:

Миграция — это релиз платформы, выгоду от которого получает компания или сама платформа, а работу делают чужие руки.

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

Асимметрия выгоды и работы

Разложим на четыре типовых случая. Столбец «кто платит трудом» — это и есть счёт, который вы выставляете, часто не осознавая этого.

Что мигрируем Кто получает выгоду Кто платит трудом Что видит инженер продуктовой команды
Версия базового образа с уязвимостью компания (риск), безопасность продуктовые команды «опять срочная ерунда, у нас релиз»
Смена рантайма или CI платформа (меньше поддержки), компания (скорость потом) продуктовые команды «нам было нормально»
Новый формат манифеста после рефакторинга платформы платформа продуктовые команды «вы поменяли, мы чиним»
Переезд в другое облако по решению бизнеса бизнес все «нас не спрашивали»

Отсюда — правило, вокруг которого строится всё остальное:

Кто получает выгоду, тот и платит трудом. Если выгода у платформы, работу делает платформа. Если выгода общая — работу делят, но автоматическую часть всё равно берёт на себя тот, кто ломает.

Это не благотворительность, а экономика. Час инженера продуктовой команды стоит столько же, сколько час инженера платформы, но с двумя надбавками: он отнимается от поставки (то есть от того, ради чего компания существует) и тратится в сорока местах на одну и ту же задачу — с сорока разными ошибками. Ровно этим правилом отличается платформа-продукт от платформы-налога — см. «Инфраструктура как API», где то же правило сформулировано для смены версии схемы.

Единственная честная метрика миграции

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

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

Кривая миграции: хвост и окно двойной стоимости

Картинка описывает почти любую массовую миграцию: быстрый разгон, 85–90 % за первую треть срока, и длинный хвост, который стоит столько же, сколько весь разгон. Красная полоса внизу — это окно двойной стоимости: пока оно открыто, платформа несёт обе цены и не получает ни одной выгоды. Задача менеджера миграции — не «поднять процент», а закрыть это окно как можно раньше, и иногда самый быстрый способ закрыть его — вообще не мигрировать хвост (об этом ниже).

Отсюда набор метрик, который стоит вести с первого дня; не путайте его с метриками принятия платформы вообще (про них — в главе о developer experience). У первой строки таблицы есть приятное свойство: её нельзя нарисовать — либо старый кластер удалён и счёт за него исчез, либо нет.

Метрика Как считать Порог тревоги
Доля выключенного старого сервисы, где старый путь физически отключён не растёт две недели подряд
Скорость волны сервисов в неделю за последние 4 недели падение вдвое от пика
Прогноз завершения остаток / скорость, отдельно по хвосту выходит за объявленную дату выключения
Доля PR от бота, слитых без правок слитые без правок / все от бота ниже 80 % — кодмод плохой
Хвост без разобранной причины сервисы в хвосте без записанной причины больше 20 % хвоста
Влияние на поставку lead time и частота выката когорты до/во время/после рост lead time более 20 % к базовой линии
Исключения без даты пересмотра по реестру исключений любое ненулевое значение

Считаем до старта: три числа

Миграция — это инвестиция с длинным горизонтом. Прежде чем объявлять её, посчитайте три числа. Расчёт занимает полдня и регулярно заканчивается решением «не мигрируем».

Возьмём компанию из примера: 24 продуктовые команды, 140 сервисов.

1) Цена перевода (чужое время)
   T_ручной_медиана        = 6 ч на сервис
   k_переключение          = 1,6   (работа идёт кусками между задачами)
   N_обычных               = 125 сервисов
   N_сложных               = 15 сервисов × 3 дня = 45 дней
   → 125 × 6 × 1,6 = 1200 ч ≈ 150 дней + 45 = 195 человеко-дней
     ≈ 0,9 человеко-года времени продуктовых команд

2) Цена платформы
   кодмод, бот пул-реквестов, документация, песочница   = 60 дней
   дежурство по миграции 0,3 FTE × 12 мес                = 72 дня
   → 132 человеко-дня

3) Цена окна двойной стоимости
   0,4 FTE платформы на поддержку старого × 12,5 мес     = 105 дней
   инфраструктура старого стека 1400 USD/мес × 12,5      ≈ 17 500 USD

Выгода после выключения:
   платформа перестаёт поддерживать старое                = 0,25 FTE
   команды перестают обходить старое, 2 ч в неделю × 24   ≈ 1,2 FTE
   → ~1,45 FTE в год

Дальше арифметика неприятная, но полезная. Вложено примерно 195 + 132 + 105 ≈ 430 человеко-дней, то есть около двух человеко-лет. Возврат — 1,45 FTE в год, но он начинается после выключения, то есть с тринадцатого месяца. Полный возврат вложений наступает примерно через 25–27 месяцев от старта. Это нормальная миграция: она окупается на горизонте двух лет, а не квартала.

Три вывода, которые из этого следуют.

Первый: горизонт решения — два года, а не квартал. Если вы всерьёз допускаете, что через полтора года смените этот рантайм ещё раз, миграция не окупится никогда. «Скучная технология» здесь не поза, а способ не платить эту цену дважды (Choose Boring Technology).

Второй: длительность окна двойной стоимости — главный рычаг. Сократить окно с 12,5 до 7 месяцев дешевле и полезнее, чем сэкономить на кодмоде. Это классическая точка воздействия: вы не ускоряете отдельных исполнителей, вы меняете структуру задержки в системе.

Третий: если считать нечего, миграции нет. Расчёт вида «новый стек современнее» — не расчёт. Формулировка «мы уже начали, надо доделать» — это невозвратные издержки, а не аргумент. Соотнесите с главой про стоимость: миграция — самая дорогая строка платформенного бюджета, и почти всегда невидимая, потому что расход лежит в чужих трекерах.

Классы миграций: стратегия зависит от класса

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

Практическая ценность классификации в том, что она сразу задаёт три вещи: нужен ли параллельный период, какой должен быть откат и сколько времени вы обязаны дать. Для совместимой миграции честный срок — недели, для миграции с состоянием — кварталы. Требовать переезда базы «до конца месяца» — это не амбициозность, а непонимание класса задачи (про выбор и переезд хранилищ).

Отдельно про принудительный класс. Уязвимость с активной эксплуатацией или конец поддержки версии — единственный случай, когда «надо» не обсуждается. Но и здесь обсуждается способ: даже в этом случае рассылка «всем обновиться за неделю» проигрывает сорока автоматическим пул-реквестам, потому что за неделю сорок команд физически не найдут сорок окон в спринтах (безопасность по умолчанию).

Мигрирует платформа, а не сорок команд

Главный технический приём массовой миграции — кодмод: программа, которая переписывает код и конфигурацию за пользователя. Классическая формулировка практики принадлежит инженерии Google: масштабные изменения (large-scale changes) там выполняет команда, которая инициировала изменение, а не тысячи владельцев кода (Software Engineering at Google, глава 22). Там же сформулировано «правило Бейонсе»: если поведение важно, оно должно быть покрыто тестом в общем прогоне — иначе оно сломается при массовой правке и это будет считаться нормой.

Три детали отличают работающий кодмод от рассылки с прикреплённым скриптом.

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

Когорты, а не залп. Первая партия — 5–7 сервисов, желательно ваших собственных и добровольцев. На них ловится 80 % дефектов кодмода. Массовый заезд начинается только после того, как когорта прошла без ручных правок.

У PR есть срок жизни, владелец и откат в описании. Пул-реквест, висящий три месяца, — не прогресс, а мусор в чужом репозитории: не слито за две недели — бот пишет владельцу, за месяц — случай уходит в разбор хвоста. И одна строка «вернуть — revert этого коммита, старый путь работает до 1 марта» стоит ноль и снимает половину тревоги.

Инструменты для кодмодов существуют и не бесплатны: OpenRewrite для JVM, jscodeshift для JS/TS, comby для структурных правок почти любого синтаксиса, gofmt -r и go fix в Go, Sourcegraph Batch Changes и Renovate для массовой рассылки правок и обновления версий. Цена владения у каждого честная: первый рабочий рецепт обычно стоит от двух до шести недель, и он всегда спотыкается о чужие «особенные» файлы.

Когда кодмод невозможен

Не всё автоматизируется: если для перевода нужно принять решение (какой класс ресурса выбрать, что делать с самописным сайдкаром, куда девать локальный кэш), кодмод не поможет. Стратегия здесь другая — сократить число решений до одного.

Плохо: «прочитайте гайд на 12 страниц и решите, как вам мигрировать». Хорошо: бот открывает PR с уже выбранным разумным значением по умолчанию и одной строкой в описании: «мы поставили tier: standard, если у вас критичный сервис — поменяйте на critical и слейте; вопросы — в канал, отвечаем за рабочий день». Инженер принимает одно решение за минуту вместо пятнадцати решений за день. Это ровно та работа по снижению когнитивной нагрузки, о которой говорят Team Topologies.

Жизненный цикл сервиса в миграции

Считать миграцию по командам бесполезно: команда с одним сервисом и команда с двадцатью — это разные объёмы. Считайте по сервисам и держите у каждого явное состояние. Полезно ровно то же, что и в платформенном API: состояние должно быть выведено из реальности, а не проставлено человеком в табличке.

Два состояния здесь важнее остальных. Проверен отделяет «слито» от «работает»: без него вы обнаружите на выключении, что двадцать сервисов формально мигрировали, но продолжают ходить в старый эндпоинт. Старое_выключено — то самое, по которому считаются деньги.

И заметьте Кандидат_на_снос. В каждой миграции обнаруживается 5–10 % сервисов, которые не нужно мигрировать — их нужно выключить. Это чистая экономия и самый приятный побочный эффект любой массовой инвентаризации.

Волны: пилот, когорты, хвост

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

Три вещи обязаны быть в этом плане.

Пилот на своих сервисах. Платформа мигрирует себя первой. Это не ритуал доверия, а способ найти дефекты до того, как их найдут двадцать четыре команды одновременно. Если платформа не готова мигрировать собственные сервисы, миграция не готова.

Явная заморозка. В любой компании есть даты, когда трогать поставку нельзя: пик продаж, отчётный период, крупный запуск. Эти даты должны быть в плане миграции с самого начала, а не появляться как сюрприз в ноябре. Заморозка удлиняет миграцию — и она же спасает её от отмены после первого сорванного бизнес-срока.

Тёмный день (он же brownout) — репетиция выключения: старый путь отключается на несколько часов в заранее объявленный день. Всё, что сломалось, — это ваш реальный хвост, а не тот, что в табличке. Практика пришла из объявления устаревания публичных API и работает внутри компании ровно так же. Первый тёмный день делайте коротким и в рабочее время, с дежурным наготове, — соотнесите с практиками безопасного релиза.

Хвост: почему последние 12 % стоят половины бюджета

Хвост — не следствие лени. Это набор нескольких разных причин, каждая со своим лечением. Ошибка менеджера миграции — считать хвост однородным и давить на него одним способом (обычно эскалацией). Лечение начинается с классификации: возьмите оставшиеся сервисы и разложите по причинам, честно, поимённо.

Причина в хвосте Доля (типично) Что делать Чего не делать
Сервис фактически мёртв 20–30 % проверить трафик, выключить, не мигрировать мигрировать «на всякий случай»
Нет владельца 10–20 % найти по коммитам и алертам; не нашли — платформа берёт и выключает по регламенту оставлять «до выяснения»
Технический особый случай 20–30 % платформа переводит руками или расширяет кодмод требовать от команды разобраться самой
Команда в пожаре или релизе 20–30 % согласовать новую дату письменно, поставить в план квартала эскалация к руководству как первый ход
Команда не согласна с миграцией 5–15 % разговор о ценности; если правы — исключение или отмена миграции продавливать мандатом

Последняя строка — самая ценная. Несогласие команды почти всегда содержит информацию: либо новый путь действительно хуже для их случая, либо выгода реальна, но неочевидна, либо вы просите их оплатить чужую выгоду. Все три случая лечатся разговором и расчётом, и ни один — приказом. Мандат даёт формальные 100 % и уводит недовольство в теневые обходы, которые вы обнаружите через год (про принятие и обходы).

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

# Шаг в старом конвейере: предупреждение появляется там, где его увидят,
# а не в почте. Дата, владелец и готовый PR — обязательны.
DEADLINE="2027-02-01"
LEFT=$(( ( $(date -d "$DEADLINE" +%s) - $(date +%s) ) / 86400 ))

echo "::warning::Старый рантайм отключается $DEADLINE (осталось $LEFT дн.)."
echo "  Сервис: $SERVICE  Владелец: $OWNER  Готовый PR: $MIGRATION_PR_URL"
echo "  Не подходит? Заявка на исключение: https://wiki/migrations/exception"

# За две недели до срока — объявленная заранее задержка (brownout),
# чтобы срок стал осязаемым. Она не растёт и не появляется внезапно.
if [ "$LEFT" -le 14 ]; then
  echo "Старый путь работает с задержкой 120 с. Это последнее предупреждение."
  sleep 120
fi

Приём работает, но у него есть предел: он ускоряет тех, кто просто откладывал, и никак не помогает тем, у кого техническая причина. Не используйте задержки как замену разбору хвоста и никогда не включайте их без объявленной даты — иначе это выглядит как саботаж собственных пользователей.

Как не сорвать поставку

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

  1. Одна обязательная миграция на команду одновременно. Это требует общего реестра миграций и человека, который держит очередь. Две одновременные обязательные миграции — гарантированный срыв обеих.
  2. Бюджет прерываний: не более 10 % ёмкости команды в квартал на платформенные требования. Всё, что не влезло, переносится в следующий квартал вместе с датой выключения старого.
  3. Предупреждение до планирования, а не после. Дата и объём приходят за 4–6 недель до начала квартала, чтобы попасть в планирование, а не в середину спринта.
  4. Ворота качества по метрикам поставки. Если у мигрировавшей когорты p50 lead time вырос больше чем на 20 % или частота выката упала — волна останавливается до выяснения причин. Это тот же механизм, что и бюджет ошибок в SRE, только защищаемая величина — скорость поставки (метрики DORA).
  5. Откат — обязанность платформы. Если после перевода у команды посыпались инциденты, чинит платформа, а при необходимости возвращает на старый путь. Команда, которую бросили с чужой поломкой, не мигрирует добровольно больше никогда.
  6. Дежурство по миграции. Отдельный человек в неделю, канал с ответом в течение рабочего дня, регулярные часы поддержки. Без этого весь план держится на письме, а письмо не отвечает на вопросы.

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

Обратимость: параллельный запуск и переключение трафика

Для миграций с состоянием и смены рантайма «слить PR и посмотреть» не работает: ошибка обнаруживается на проде и на живых данных. Здесь применяют параллельный запуск.

Ключевая деталь — теневой режим: новый путь получает копию реальных запросов, но его ответ не показывается пользователю. Расхождения ловятся до того, как кто-то пострадал. Классическая реализация идеи — библиотека Scientist от GitHub, но своя версия на 100 строк пишется за день; ценность не в библиотеке, а в дисциплине сравнивать, а не верить.

Теневой трафик — это побочные эффекты. Если новый путь пишет в базу, шлёт письма или списывает деньги, теневой режим превращается в двойные операции. Тень безопасна только для чтения; для записи нужна отдельная песочница или явная защита на уровне зависимостей.

Двойная запись — это распределённая транзакция без транзакции. Пока пишутся два хранилища, у вас есть окно рассинхронизации, и «сверим потом» превращается в отдельный проект. Планируйте сверку и починку расхождений как часть миграции, а не как надежду (модели согласованности).

Откат имеет срок годности. Через две недели двойной записи вернуться назад уже нельзя: в новом хранилище появились данные, которых нет в старом. Дата, после которой откат невозможен, должна быть объявлена заранее и записана в плане — «мы больше не можем откатиться начиная с 15 марта» — это важнейшая строка документа.

Для схем данных используется приём «расширяй и сжимай» (expand/contract, он же parallel change, Martin Fowler): сначала добавляем новое, не ломая старое; потом переводим читателей; потом переводим писателей; и только затем удаляем старое. Четыре шага, каждый — отдельный релиз, между ними живут обе формы. Стратегия постепенного вытеснения большого куска системы описана там же как strangler fig и годится для смены рантайма целиком.

Учёт: миграция как объект платформы, а не письмо

Если миграция существует только в виде письма и таблицы, она умрёт вместе с вниманием её автора. Опишите её как объект — так же, как вы описываете сервисы в каталоге и API платформы.

# migrations/runtime-v3.yaml — миграция как декларация, а не как рассылка
apiVersion: platform/v1
kind: Migration
metadata:
  name: runtime-v3
  owner: platform-team
spec:
  reason: "Старый рантайм держится на неподдерживаемых скриптах; апгрейд образа — 2 недели ручной работы"
  benefit:
    platform_fte_saved: 0.25          # что платформа перестанет делать
  cost:
    team_days_estimate: 195           # чужое время — считаем явно
    platform_days_estimate: 132
    double_run_months: 12             # окно двойной стоимости
  class: runtime-change               # определяет требования к откату и срокам
  automation:
    codemod: recipes/runtime-v3
    green_checks_required: true       # красный PR к команде не уходит
  budget:
    max_team_capacity_percent: 10
    freeze_windows:
      - { from: 2026-11-01, to: 2026-12-15, reason: "пик продаж" }
  dates:
    announced: 2026-01-12
    waves_start: 2026-02-23
    dark_day: 2027-01-18              # репетиция выключения
    old_path_off: 2027-02-01          # дата, после которой экономия существует
  exceptions:
    require: [owner, reason, review_date]
    max_duration_days: 180            # бессрочных исключений не бывает
  rollback:
    supported_until: 2026-12-01   # после этой даты откат невозможен

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

Отчётность по нему считается автоматически. Минимальный скрипт, который живёт годами:

"""Отчёт по миграции: состояние, скорость, прогноз и разбор хвоста.

Источник истины — реальность (реестр, факт выката, статус PR), а не таблица.
Сложность: O(n log n) по числу сервисов из-за сортировки в отчёте, память O(n).
"""
from collections import Counter
from dataclasses import dataclass
from datetime import date, timedelta

ГОТОВЫЕ = {"Проверен", "Старое_выключено"}

@dataclass(frozen=True)
class Service:
    name: str
    state: str                 # состояния из диаграммы жизненного цикла
    tail_reason: str | None    # "мёртв" | "без владельца" | "особый случай" | ...
    changed_at: date           # когда состояние менялось последний раз

def report(services: list[Service], today: date) -> dict:
    total = len(services)
    by_state = Counter(s.state for s in services)
    done = sum(by_state[s] for s in ГОТОВЫЕ)

    # Скорость — по факту за 4 недели, а не по обещаниям из плана.
    window = today - timedelta(days=28)
    per_week = sum(1 for s in services
                   if s.state in ГОТОВЫЕ and s.changed_at >= window) / 4

    # Хвост: всё, что стоит на месте дольше трёх недель.
    stuck = [s for s in services
             if s.state not in ГОТОВЫЕ and (today - s.changed_at).days > 21]
    tail = Counter(s.tail_reason or "не разобрано" for s in stuck)

    return {
        # Деньги считаем только по выключенному старому пути.
        "процент_выключенного_старого": round(100 * by_state["Старое_выключено"] / total, 1),
        "процент_готовых": round(100 * done / total, 1),
        "скорость_в_неделю": round(per_week, 1),
        "прогноз_недель": round((total - done) / per_week, 1) if per_week else "не сходится",
        "хвост": tail.most_common(),
        "хвост_без_разбора": tail["не разобрано"],   # главный дефект управления
    }

Строка хвост_без_разбора — самая полезная в отчёте. Пока у застрявшего сервиса нет причины, у миграции нет плана; «мы напоминаем» планом не является.

Стратегии: перевести, дать вымереть, заморозить

Принудительный перевод всех — не единственный вариант и часто не лучший. Есть три стратегии, и выбор между ними — продуктовое решение.

Перевести. Старое активно мешает: стоит денег, создаёт риск, требует поддержки. Окно двойной стоимости закрывается как можно быстрее, миграция обязательна для всех.

Дать вымереть. Новые сервисы создаются только на новом пути; старые доживают свой срок и уходят вместе с сервисами. Стоит дёшево, не отнимает у команд ни часа, но работает только при двух условиях: старый путь не создаёт риска и не требует активной разработки, а сервисы действительно имеют конечный срок жизни. Иначе вы получите вечное окно двойной стоимости — худший из вариантов.

Заморозить. Старый путь остаётся, но возможностей в него не добавляют, а бюджет фиксируют. Разумно, когда миграция не окупается на горизонте двух лет, а старое пока не опасно.

Правый нижний угол — самое полезное место на этой диаграмме. Там лежат миграции, которые кажутся обязательными («ну не может же легаси остаться на старой базе»), но не окупаются никогда. Решение «не мигрировать и записать почему» — это полноценное инженерное решение; оформите его как архитектурное решение с контекстом, чтобы через год к нему не вернулись случайно.

Команда, которой миграция не подходит

Это тот же вопрос, что и с золотым путём: путь помогает, пока он путь, а не забор. В миграции забор выглядит как «переводите или отключим». Иногда это законная позиция (принудительный класс), но чаще — признак того, что вы не разобрали случай.

Рабочая процедура состоит из четырёх шагов.

  1. Выслушать и записать техническую причину. Не «они не хотят», а «сервис использует локальный диск для 400 ГБ кэша, на новом рантайме его нет». Формулировка причины — уже половина решения.
  2. Проверить, сколько ещё таких. Один случай — исключение. Три случая — дыра в новом пути, и чинить надо путь, а не команду. Это ровно тот сигнал, который глава про абстракции описывает как «одна абстракция поверх трёх разных потребностей».
  3. Выдать исключение как объект, а не как молчание. У исключения есть владелец, причина, дата пересмотра и максимальный срок. Бессрочных исключений не бывает; пустой реестр исключений — не порядок, а слепота.
  4. Договориться о стоимости отказа. Команда остаётся на старом пути — значит, она берёт на себя его поддержку после даты выключения общей поддержки: дежурство, обновления, безопасность. Это не наказание, а честная передача цены владельцу решения.

Четвёртый пункт разрешает главный конфликт миграции. Платформа не может держать старый путь вечно ради одной команды — это налог на всех остальных. Но и выключить чужой прод она не имеет права. Передача владения превращает спор в выбор с понятной ценой: «мигрируйте за два дня либо берите поддержку на себя, вот её объём».

Типовые провалы

Письмо с дедлайном вместо кодмода. Самый частый и самый дорогой. Вы экономите свои шесть недель и тратите чужие двести человеко-дней, а получаете 88 % и вечный хвост. Плюс репутационный ущерб, который дороже: следующая ваша инициатива встретит сопротивление до того, как её прочитают.

Миграция без выгоды для пользователя. «Мы переехали на новый шаблон, теперь переезжайте вы». Если вся выгода у платформы, работу делает платформа — иначе вы просто перекладываете свою уборку на соседей и называете это стандартизацией.

Миграция без даты выключения. Отсутствие даты означает, что окно двойной стоимости открыто навсегда и экономии не будет никогда: дата объявляется одновременно с началом миграции, а не тогда, когда «почти все перешли». Соседний провал — большой взрыв, когда все переключаются в один день, а откат описан словами «ну, вернёмся как-нибудь»: хорошо выглядит на слайде и превращается в самый дорогой инцидент года.

Миграция в обёртку над облаком. Перевод сорока команд с прямого использования облака на свой тонкий слой, повторяющий облачный API один в один, — только с отставанием в поддержке новых возможностей и без документации в интернете. Цена платится, выгода отсутствует; проверка простая — сколько полей пользователь больше не заполняет (про обёртки). Тот же вид провала — миграция каталога в портал, которым не пользуются: «заполните карточки сервисов» стоит сотни часов чужого времени в обмен на данные, которые устареют за квартал, а каталог обязан выводиться из реальности автоматически.

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

Миграция ради инструмента. «Все переезжают на Kubernetes» или «нам нужен Backstage» как исходная посылка, а не как вывод из расчёта. У обоих есть настоящая цена владения: кластер с апгрейдами — это 1–2 постоянных инженера, портал — половина-один инженер на сопровождение, и оба ничего не стоят без потока задач, который они обслуживают (цена владения — в главе про стоимость и в главе про инфраструктуру как API). Инструмент выбирают после того, как посчитали миграцию, а не вместо этого.

Забыли посчитать, что мигрируют не только сервисы. Дашборды, алерты, рантбуки, привычки дежурных, ссылки в постмортемах, права доступа. Перенос наблюдаемости между стеками стоит отдельного человеко-квартала на сотню сервисов (глава про наблюдаемость), а окружения и стенды переезжают отдельно от прода (глава про окружения).

Локальная оптимизация в миграциях

Полезно посмотреть на всё это глазами системного мышления. Платформенная команда оптимизирует свою метрику — «процент переведённых», продуктовая свою — «поставка в срок»; обе локально правы, а система теряет: миграция идёт медленно, старое живёт долго, обе стороны считают другую нерациональной. Лечится это не призывами, а изменением того, что измеряется. Как только метрикой платформы становится «доля выключенного старого» (а не «переведено»), у неё сразу появляются стимулы писать кодмоды, разбирать хвост поимённо и удалять мёртвые сервисы вместо их перевода. Как только у продуктовой команды появляется бюджет прерываний и защита поставки, миграция перестаёт быть угрозой квартальным целям. Это же задержка в системе: выгода отложена на год, боль наступает сейчас — значит, о выгоде нужно напоминать данными, иначе её никто не почувствует.

Мини-итог

  • Миграция — это релиз платформы, работу по которому делают чужие руки. Асимметрия выгоды и труда — источник всех провалов; правило «кто получает выгоду, тот и платит трудом» разрешает большинство споров до их начала.
  • Экономия равна нулю до выключения старого. Считайте долю выключенного, а не долю переведённых; главный рычаг — длительность окна двойной стоимости, а не скорость отдельных исполнителей.
  • Считайте до старта. Чужие человеко-дни, свои человеко-дни, стоимость двойного периода и горизонт возврата. Двухлетняя окупаемость — норма; отсутствие расчёта — не норма.
  • Мигрирует платформа. Кодмод, бот с зелёными PR, когорты, откат в описании PR. Там, где автоматика невозможна, сокращайте задачу до одного решения с разумным значением по умолчанию.
  • Хвост неоднороден. Мёртвые сервисы выключают, бесхозные забирают, особые случаи делают руками, занятым командам двигают дату, несогласных слушают. Эскалация — последний ход, а не первый.
  • Поставку защищают явным контрактом: одна обязательная миграция за раз, бюджет прерываний, окна заморозки, предупреждение до планирования, ворота по метрикам поставки, откат за счёт платформы, дежурство по миграции.
  • «Не мигрировать» — полноценное решение. Дать вымереть или заморозить дешевле принудительного перевода, если старое не создаёт риска. И ни одна миграция не оправдывается тем, что целевой инструмент моден: у Kubernetes, портала и любого другого выбора есть постоянная цена владения, которую платит тот же бюджет.

Источники

  • T. Winters, T. Manshreck, H. Wright. Software Engineering at Google, глава 22 «Large-Scale Changes» — кто выполняет массовые изменения и почему не владельцы кода; правило Бейонсе.
  • M. Fowler. ParallelChange (expand/contract) и StranglerFigApplication — базовые схемы обратимых миграций.
  • GitHub. Scientist — параллельный запуск со сравнением результатов. Kubernetes. Deprecation Policy — окна устаревания как обещание, а не как объявление.
  • Инструменты кодмодов: OpenRewrite, jscodeshift, comby, Sourcegraph Batch Changes, Renovate.
  • N. Forsgren, J. Humble, G. Kim. Accelerate, IT Revolution, 2018 и DORA Research 2024 — метрики поставки как защищаемая величина во время миграций.
  • M. Skelton, M. Pais. Team Topologies, IT Revolution, 2019 — когнитивная нагрузка при массовых изменениях. D. McKinley. Choose Boring Technology, 2015 — почему частая смена стека убивает окупаемость любой миграции.

Что дальше

Мы разобрали самую конфликтную часть платформенной работы: как менять общее так, чтобы у сорока команд не сорвалась поставка, и как честно считать цену того, что вы просите сделать других. Почти каждый пункт этой главы упирается в один вопрос — а кто, собственно, всё это делает: пишет кодмоды, дежурит по миграции, разбирает хвост поимённо, ведёт переговоры об исключениях и говорит «нет» миграции, которая не окупается. Это работа платформенной команды, и у неё есть свой состав, свои границы и свой предел ёмкости, за которым любая инициатива начинает съедать поддержку.

Платформенная команда: состав, поддержка, дежурство и границы

Нашли неточность? Выделите фрагмент текста — рядом появится жучок.

Нужен разбор именно вашей ситуации?

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

Доска запросов