Blue-green vs Canary: как безопаснее вносить изменения в API и БД
Blue-green и canary развертывания для изменений API и базы данных: практические шаги, чтобы снизить риск простоя при миграциях схемы и медленном обновлении мобильных клиентов.

Почему развертывания становятся рискованными при изменении схемы и медленном обновлении мобильных клиентов
Развертывание может выглядеть идеально в тестах и всё равно провалиться, как только попадёт под реальный трафик. Обычно причина в том, что меняется не только код. Меняются контракт API и схема базы данных, и они редко эволюционируют одинаково быстро.
Проблемы возникают, когда разные части системы по‑разному понимают, что является «правильным». Новый бэкенд ожидает столбец, которого ещё нет. Старый бэкенд записывает данные в формате, который новый код больше не понимает. Даже небольшие изменения — переименование поля, ужесточение валидации или изменение значения enum — могут вызвать ошибки в продакшне.
Мобильные приложения повышают ставку, потому что старые версии остаются у пользователей. Кто‑то обновляет через минуты, кто‑то — через недели. Это значит, что бэкенду нужно одновременно обслуживать несколько поколений клиентов. Если вы выпускаете API, который работает только с самой новой версией приложения, вы можете сломать оформление заказа, регистрацию или фоновую синхронизацию для части пользователей и не заметить сразу.
«Риск простоя» — это не только недоступность сайта. В реальных системах это часто проявляется частичными сбоями:
- всплеск 4xx/5xx на конкретных эндпоинтах при том, что остальное кажется в порядке
- сбои входа в систему из‑за несоответствия токенов, ролей или записей пользователей
- молчаливые проблемы с данными (неправильные значения по умолчанию, усечённый текст, пропавшие связи), которые проявятся через дни
- зависшие фоновые задачи, которые собирают очередь, требующую часов, чтобы очиститься
Именно поэтому команды сравнивают blue-green и canary: задача — сократить радиус поражения, когда изменения не полностью совместимы.
Blue-green и canary простыми словами
Когда люди сравнивают blue-green и canary, они обычно отвечают на один вопрос: вы хотите большой управляемый рычажный переключатель или маленький аккуратный эксперимент?
Blue-green: две полные версии и переключение трафика
Blue-green означает, что вы запускаете две полные среды одновременно. «Blue» — текущая версия, обслуживающая пользователей. «Green» — новая версия, развернутая и протестированная параллельно. Когда готовы — переключаете трафик с blue на green.
Этот подход хорош для предсказуемости. Вы можете проверить новую версию в условиях, близких к продакшну, прежде чем она начнёт обслуживать реальных пользователей, и затем сделать один чистый cutover.
Откат тоже простой: если после переключения что‑то пошло не так, направьте трафик обратно на blue. Это почти мгновенный откат, но кеши, фоновые задачи и изменения в данных всё равно могут осложнить восстановление.
Canary: сначала небольшая доля трафика
Canary означает, что новая версия сначала получает небольшой срез пользователей или запросов. Если всё выглядит хорошо, вы поэтапно увеличиваете эту долю, пока она не будет обслуживать всех.
Canary подходит, когда вас пугает неизвестное поведение под реальным трафиком. Вы ловите проблемы рано, до того как почувствует большинство пользователей.
Откат делается путём уменьшения процента canary до нуля (или прекращения маршрутизации на новую версию). Это обычно быстро, но не всегда чисто, потому что часть пользователей могла уже создать данные или состояние, которые теперь должны корректно обрабатывать обе версии.
Простая памятка по компромиссам:
- Blue-green предпочитает чистые cutover'ы и быстрые откаты.
- Canary предпочитает обучение на реальном трафике с ограниченным радиусом поражения.
- Ни один из подходов автоматически не решает риск базы данных. Если изменения схемы несовместимы, оба могут провалиться.
- Canary зависит от мониторинга, потому что решения принимаются по живым сигналам.
- Blue-green часто требует дополнительной ёмкости, так как вы запускаете два полноценных стека.
Пример: если вы выпускаете API, который иногда возвращает новое поле, canary поможет увидеть, крашатся ли старые клиенты на неожиданном поле. Если изменение требует переименования столбца, с которым старый код не справится, blue-green не спасёт вас, если сама миграция схемы не спроектирована для поддержки обеих версий.
Чем миграции баз данных отличаются от деплоев кода
Кодовый релиз обычно просто откатить. Если новая версия работает некорректно, вы деплойнете старую сборку — и вы, как правило, возвращаетесь в исходное состояние.
Изменение базы данных другое: оно меняет форму данных. Как только строки перезаписаны, столбцы удалены или ограничения ужесточены, возврат редко бывает мгновенным. Даже если откатить приложение, оно может не понимать новую схему.
Поэтому риск простоя при миграции схемы чаще зависит не от метода развёртывания, а от того, как спроектирована сама миграция.
Основы онлайн-миграций
Самые безопасные миграции работают при параллельном запуске старой и новой версий приложения. Паттерн прост: сделайте изменение, которое безопасно игнорировать, обновите код, чтобы использовать его, а потом уберите старое.
Распространённая последовательность «расширить — затем сократить» выглядит так:
- сначала аддитивные изменения: добавить nullable‑столбец, новую таблицу, индекс так, чтобы он не блокировал записи;
- двойное поведение: писать и в старое, и в новое, или читать из нового с откатом на старое;
- отдельный backfill: мигрировать существующие данные малыми партиями;
- переключение: перевести большую часть трафика на новое поведение;
- разрушительные изменения в конце: удаление старых столбцов, удаление старых веток кода, ужесточение ограничений.
«Big bang» миграции объединяют самые рискованные шаги в одном релизе: длинные блокировки, тяжёлые backfill'ы и код, который предполагает, что новая схема уже везде есть.
Почему медленные обновления мобильных клиентов усложняют задачу
Мобильные клиенты могут оставаться на старых версиях неделями. Ваш бэкенд должен продолжать принимать старые запросы и отдавать старые ответы, пока база данных эволюционирует.
Если старое приложение отправляет запрос без нового поля, сервер не может внезапно сделать это поле обязательным в базе. Нужен период, в котором оба поведения работают.
Какая стратегия снижает риск простоя при миграциях схемы
Самый безопасный выбор зависит не столько от инструмента развёртывания, сколько от одного вопроса: могут ли старая и новая версии приложения корректно работать на одной схеме базы данных некоторое время?
Если ответ — да, blue-green часто даёт минимальный простой. Вы можете подготовить изменение базы сначала, держать трафик на старом стеке, а затем одним переключением направить трафик на новый. Если что‑то не так — быстро возвращаете трафик назад.
Blue-green всё равно проваливается, если новое приложение требует немедленного наличия новой схемы. Обычные примеры: удаление или переименование столбца, который ещё читается старой версией, или добавление NOT NULL до того, как приложение пишет значение. В таких случаях откат может быть небезопасным, потому что база уже несовместима.
Canary лучше, когда нужно контролируемое обучение. Небольшая часть реального трафика сначала попадает на новую версию — это помогает выявить пограничные случаи вроде отсутствующих индексов, неожиданных паттернов запросов или иного поведения фоновых задач под нагрузкой. Минус в том, что вам нужно поддерживать обе версии одновременно, а это обычно означает обратную совместимость изменений в БД.
Практическое правило принятия решения
При выборе между blue-green и canary для риска простоя при миграциях:
- Выбирайте blue-green, когда можно сделать изменение аддитивным и совместимым, и вы в основном хотите быстрый чистый переключатель.
- Выбирайте canary, когда не уверены в поведении в продакшне или ожидаете, что редкие формы данных имеют значение.
- Если миграция вынуждает немедленно ломать совместимость, не выбирайте между blue-green и canary — измените план на expand-then-contract.
Как выглядит «совместимо» в реальной жизни
Предположим, вы добавляете новое поле в таблицу orders. Безопасный путь: добавить колонку как nullable, задеплоить приложение, которое начинает её писать, затем backfill старых строк и только позже включить ограничения. В таком сценарии blue-green даёт чистый cutover, а canary — раннее предупреждение, если какой‑то путь кода всё ещё предполагает старую форму.
Как медленные мобильные обновления влияют на выбор стратегии
Веб‑пользователи обновляют страницу. Мобильные — нет.
На iOS и Android люди остаются на старых версиях неделями и месяцами. Некоторые вообще не обновят, пока приложение не заставит их сделать это, а некоторые устройства долго бывают офлайн. Это значит, что ваш «старый» клиент продолжает вызывать API задолго после релиза бэкенда. Старые мобильные клиенты становятся постоянным тестом вашей обратной совместимости.
Это смещает цель с «развернуть без простоя» на «поддерживать несколько поколений клиентов одновременно». На практике мобильная среда часто подталкивает к мышлению в стиле canary для API, даже если инфраструктуру вы обновляете через blue-green.
Обратная совместимость vs версионирование API
Чаще всего вы хотите обратную совместимость, потому что она позволяет старым и новым приложениям использовать одни и те же эндпоинты.
Примеры обратимой совместимости: добавление новых полей, принятие старых и новых форм payload'а, сохранение существующих полей в ответе и избегание изменения смысла данных. Версионирование API становится полезным, когда поведение действительно нужно изменить (не просто добавить данные) или когда требуется удалить/переименовать поля.
Пример: добавить опциональное поле marketing_opt_in обычно безопасно. Пересчитать price по‑новому — обычно нет.
Планирование окна депрецирования
Если вам всё‑таки нужен breaking change, рассматривайте окончание поддержки как продуктовое решение. Полезное окно депрецирования измеряется «активными пользователями на старых версиях», а не календарными днями.
Практическая последовательность:
- Выпустить бэкенд, который поддерживает старых и новых клиентов.
- Выйти с новым мобильным приложением и отслеживать принятие по версиям.
- Блокировать или вводить ограничения только когда доля старых версий падает ниже безопасного порога.
- Удалить старое поведение в самом конце, с планом отката.
Пошагово: безопасный паттерн rollout для API + БД
Когда вы меняете API и базу одновременно, самый безопасный план обычно двух- или трёхступенчатый. Каждая стадия должна быть безопасной сама по себе, даже если пользователи будут держать старое мобильное приложение недели.
Паттерн rollout, который не ломает старых клиентов
Начните с аддитивного изменения схемы. Добавьте новые столбцы или таблицы, избегайте переименований и удалений, позволяйте NULL там, где нужно, и используйте значения по умолчанию, чтобы старый код не столкнулся с ограничениями.
Затем выпустите код, который терпит обе формы данных. Чтение должно принимать «поле отсутствует» и «поле присутствует». Записи пока продолжают писать старое поле и опционально новое.
Типичная последовательность:
- Добавить новые элементы схемы (столбцы, таблицы, индексы), не удаляя старые;
- Деплойнуть код, который читает из старого или нового поля и не падает на null;
- Backfill существующих строк малыми партиями, затем проверить счётчики, долю null и производительность запросов;
- Переключить путь записи на новое поле, сохранив fallback для чтения;
- Когда старые мобильные версии уйдут, удалить старое поле и убрать legacy-код.
Backfill и верификация: где прячутся сбои
Backfill'ы чаще всего фейлятся, потому что их запускают как «быстрый скрипт». Выполняйте их постепенно, следите за нагрузкой и проверяйте результаты. Если новое поведение нуждается в индексе — добавьте его до переключения чтения/записи, а не после.
Пример: вы добавили phone_country_code для улучшенной форматировки. Сначала добавляете колонку (nullable), обновляете API так, чтобы он принимал её, но работал без неё, делаете backfill из существующих номеров, затем начинаете записывать её для новых регистраций. Неделями позже, когда старые версии почти уйдут, можете убрать legacy‑парсер.
Инструменты и практики, которые упрощают оба подхода (без сложных настроек)
Вам не нужна сложная инфраструктура, чтобы сделать blue-green и canary безопаснее. Несколько привычек уменьшают сюрпризы, когда API и схемы БД движутся с разной скоростью.
Dual-read и dual-write (временно)
Dual-write — писать данные и в старое, и в новое место в переходный период (например, в users.full_name и новый users.display_name). Dual-read — читать из нового с откатом на старое. Это даёт время для обновления клиентов, но должно быть кратковременным мостом. Решите, как его убрать, отслеживайте, какой путь используется, и добавьте проверку консистентности.
Флаги фич для изменения поведения
Флаги фич позволяют деплоить код без включённого рискованного поведения. Это помогает и при blue-green, и при canary: вы отделяете «деплой» от «включения».
Пример: деплойте поддержку нового поля в ответе, но оставьте сервер возвращать старую форму, пока не будете готовы. Затем включите поведение для небольшой группы, наблюдайте за ошибками и постепенно увеличивайте. Если что-то сломается, выключите флаг без полного отката.
Контрактный подход к тестированию (API — это обещание)
Многие инциденты при миграциях — это не «проблемы с базой», а проблемы ожиданий клиентов.
Трактуйте API как обещание. Избегайте удаления полей или изменения смысла. Делайте неизвестные поля опциональными. Аддитивные изменения обычно безопасны. Breaking changes ждут новой версии API.
Надёжные джобы миграции данных
Миграции часто требуют фоновых job'ов для копирования данных, вычислений или очистки. Эти джобы должны быть однообразными и повторяемыми: безопасно запускать несколько раз, легко ретраить, с возможностью троттлинга, чтобы не создавать всплесков нагрузки.
Частые ошибки, вызывающие простои при миграциях
Большинство инцидентов случается, когда релиз предполагает, что всё движется синхронно: все сервисы деплоятся одновременно, все данные чистые, все клиенты быстро обновляются. В реальности так не работает, особенно с мобильными клиентами.
Типичные ошибки:
- раннее удаление или переименование столбца — старый API, фоновые джобы или мобильные клиенты всё ещё его используют;
- предположение, что клиенты быстро обновляются — мобильные релизы могут доходить до пользователей очень медленно;
- запуск миграций, блокирующих большие таблицы в пиковое время — даже «простой» индекс может заблокировать записи;
- тестирование только на чистых выборках — в продакшне есть null, странные форматы, дубликаты и legacy‑значения;
- отсутствие реального плана отката для кода и данных — «мы можем задеплоить прежнюю версию» недостаточно, если схема уже изменилась.
Пример: вы переименовали status в order_status и задеплоили новый API. Веб‑версия работает. Старые мобильные клиенты всё ещё шлют status, и API теперь отклоняет запросы, из‑за чего падают оформления заказов. Если вы удалили столбец, восстановить поведение быстро нельзя.
Лучший дефолт: делайте изменения маленькими и обратимыми, держите старые и новые пути работающими вместе и заранее пропишите, что делать при всплеске метрик (как вернуть трафик обратно, какой feature flag выключает новое поведение и как валидировать/починить данные при проблемном backfill'е).
Быстрый чеклист перед релизом
Незадолго до релиза короткий чеклист ловит проблемы, которые приводят к ночным откатам. Это особенно важно при одновременных изменениях API и БД и медленных обновлениях мобильных клиентов.
Пять проверок, которые предотвращают большинство сбоев
- Совместимость: подтвердите, что старая и новая версии приложения работают с одной и той же схемой БД. Практический тест — запустить текущую production‑сборку против staging‑базы с применённой миграцией.
- Порядок миграций: убедитесь, что первая миграция аддитивна, а разрушительные изменения (удаление столбцов, ужесточение ограничений) запланированы на потом.
- Откат: определите самый быстрый путь назад. Для blue-green — переключение трафика обратно. Для canary — отправка 100% трафика на стабильную версию. Если откат требует ещё одной миграции — это не просто откат.
- Производительность: измерьте задержки запросов после изменения схемы, а не только корректность. Отсутствующий индекс может сделать один эндпоинт практически недоступным.
- Реальность клиентов: опознайте самую старую активную версию мобильного приложения, которая всё ещё вызывает API. Если значимая доля пользователей на ней — планируйте более длинное окно совместимости.
Небольшой сценарий здравого смысла
Если вы добавляете поле preferred_language, сначала применяйте миграцию в базе как nullable. Затем деплойте серверный код, который читает это поле, если оно есть, но не требует его. Только после того как большинство трафика перейдёт на обновлённые клиенты, делайте поле обязательным и удаляйте устаревшие пути.
Пример: добавить новое поле, не сломав старые мобильные приложения
Предположим, вы добавляете новое профильное поле country, и бизнес хочет сделать его обязательным. Это может сломаться в двух местах: старые клиенты не отправляют поле, и база отклоняет записи, если вы преждевременно включите NOT NULL.
Более безопасный подход — разделить изменения: сначала добавить поле обратно‑совместимым способом, затем позже требовать его, когда клиенты подтянутся.
Как это выглядит с blue-green
При blue-green вы развертываете новую версию рядом со старой. Всё равно нужно, чтобы изменение схемы было совместимо с обеими версиями.
Безопасная последовательность:
- применить миграцию (добавить
countryкак nullable) - задеплоить green‑версию, которая принимает отсутствие
countryи использует fallback - протестировать ключевые сценарии на green (регистрация, редактирование профиля, оформление заказа)
- переключить трафик
Если что‑то пойдёт не так — переключитесь обратно. Главное условие: откат сработает только если схема по‑прежнему поддерживает старую версию.
Как это выглядит с canary
При canary вы открываете новое поведение для небольшой доли (обычно 1–5%) и наблюдаете за ошибками валидации, изменением задержек и неожиданными ошибками базы.
Частый сюрприз — старые мобильные клиенты присылают обновления профиля без country. Если API сразу считает его обязательным, вы увидите 400. Если база требует NOT NULL — возможны 500.
Более безопасная последовательность:
- добавить
countryкак nullable (опционально с безопасным значением по умолчанию, например "unknown") - принимать отсутствие
countryот старых клиентов - постепенно backfill'ить
countryдля существующих пользователей фоновым заданием - позже включать «required» сначала на уровне API, затем в базе
После релиза документируйте, что старые клиенты могут отправлять и что гарантирует сервер. Письменный контракт предотвращает повторение этой ошибки в следующей миграции.
Если вы строите с AppMaster (appmaster.io), та же дисциплина rollout применима, даже если вы генерируете backend, web и нативные мобильные приложения из одной модели. Используйте платформу, чтобы сначала выпускать аддитивные изменения схемы и терпимый API, а ужесточать ограничения только после роста принятия.
Вопросы и ответы
Blue-green запускает две полные среды и переключает весь трафик разом. Canary выпускает новую версию сначала для небольшой доли трафика и затем постепенно увеличивает её долю по сигналам из продакшна.
Выбирайте blue-green, когда вам нужен чистый cutover и вы уверены, что новая версия совместима с текущей схемой БД. Это особенно полезно, если основной риск — в коде приложения, а не в неизвестном поведении в продакшне.
Canary безопаснее, когда нужно изучать поведение в реальном трафике — например, если под сомнением паттерны запросов, редкие данные или фоновая обработка. Он сокращает blast radius, но требует пристального наблюдения и готовности остановить rollout.
Нет. Если изменение схемы ломает совместимость (например, удаление или переименование столбца, который всё ещё используется старым кодом), и blue-green, и canary могут провалиться. Безопаснее проектировать онлайн-миграцию, которая поддерживает старые и новые версии одновременно.
Потому что мобильные пользователи могут оставаться на старых версиях неделями, бэкенд должен одновременно обслуживать несколько поколений клиентов. Это вынуждает дольше сохранять backward compatibility и избегать изменений, требующих немедленного обновления всех клиентов.
Начинайте с аддитивных изменений, которые старый код может игнорировать — nullable-столбцы, новые таблицы. Затем деплойте код, который поддерживает обе формы данных, постепенно делайте backfill, переключайте поведение и только потом удаляйте старые поля или ужесточайте ограничения.
Составьте список того, что отправляют старые клиенты и что они ожидают в ответ, затем не удаляйте поля и не меняйте смысл данных. Предпочитайте добавление опциональных полей, принимайте обе формы запросов и откладывайте валидацию "required", пока доля новых клиентов не станет высокой.
Dual-write — запись в старое и новое поля во время перехода. Dual-read — чтение нового поля с откатом на старое при необходимости. Используйте временно, отслеживайте, какой путь используется, и заранее планируйте очистку.
Флаги фич позволяют деплоить код с выключенным рискованным поведением, а затем включать его постепенно. Если метрики ухудшаются, флаг можно выключить без полного отката, что полезно и при blue-green, и при canary.
Ранние удаления или переименования столбцов, принудительная NOT NULL-валидация до того, как клиенты начали отправлять значение, и миграции, блокирующие большие таблицы в пиковое время — частые причины. Ещё одна проблема — тестовые данные, отличающиеся от продакшна, из‑за чего backfill и проверки терпят неудачу.


