Выкатка и откат: почему канарейка не помогает без нужного детектора
Четыре стратегии выкатки на одном и том же дефекте — и неожиданный результат: одна и та же канарейка задевает в двадцать два раза меньше запросов, если тревога поднимается по доле ошибок. С детектором, который ждёт двухсот ошибок, она проигрывает переключению всего трафика разом.
Полное техническое изложение
TL;DR
Стратегия выкатки не защищает сама по себе — защищает сигнал, по которому её останавливают. Канарейка — это не «безопасная выкатка», а всего лишь маленькая доля трафика на новой версии. Уменьшив долю, вы уменьшили не риск, а размер выборки, по которой беду ещё предстоит заметить; пока рядом нет правила, способного увидеть беду на этой маленькой выборке, осторожность оборачивается отсрочкой.
Отсюда результат, ради которого стоит урок: осторожная стратегия может проиграть прямолинейной. Модель: при детекторе, который ждёт 200 накопленных ошибок, канарейка задела 645 запросов против 520 у переключения всего трафика разом. Причина — размер выборки: на пяти процентах трафика фиксированное число ошибок копится в двадцать раз дольше, и тревога просто ждёт объёма. Тот же дефект и та же канарейка с детектором по доле ошибок — 29 запросов.
Дальше — числа, вторая половина темы и граница. Разница между двумя детекторами при одной и той же канарейке — в 22,2 раза; у флага, который начинает с одного процента трафика, смена детектора меняет результат в 123,8 раза, и при детекторе по доле он задевает 4 запроса против 29 у канарейки — доля впятеро меньше, а возврат не занимает времени вовсе. Худший результат в модели у rolling — 1820 задетых запросов, и виновата в нём не выкатка, а откат: возврат десяти реплик занимает столько же, сколько их замена. Числа здесь модельные: дефект, поток, пороги и длительности заданы допущениями, содержательны отношения между строками. И у всего этого есть граница: откат возможен не всегда — после применённой миграции или отправленного письма возвращать уже нечего.
- сервис работает в нескольких одинаковых экземплярах, и новую версию можно включить не для всех сразу;
- трафик между экземплярами кто-то распределяет, и это распределение можно менять на ходу;
- дефект новой версии обычно виден не сразу: сначала он просто портит часть ответов.
- слова «сине-зелёная», «rolling», «канарейка», «feature-flag» — все четыре разобраны в уроке обычными словами;
- как устроено правило тревоги: по числу ошибок или по их доле;
- что делают с миграциями базы, когда откат уже невозможен.
Что здесь на самом деле спрашивают
Лестница обычно такая:
- «Какие бывают стратегии выкатки?» — разминка на перечисление.
- «Чем канарейка лучше?» — здесь начинается содержание.
- «Как понять, что канарейка плохая?» — главный вопрос темы, и обычно его пропускают.
- «Сколько держать канарейку?» — вопрос про выборку.
- «Как быстро вы можете откатиться?» — вопрос, который важнее вопроса про выкатку.
- «Что делать с миграцией базы?» — вопрос про то, что не откатывается.
Этот урок стоит на модели, а не на замере: стратегии выкатки описываются документацией отдельных продуктов, общего стандарта у них нет. Модель отвечает не на вопрос «сколько именно у вас», а на вопрос «от чего это зависит».
База: как вообще заменяют работающую версию
Задача звучит просто: сейчас запросы обслуживает версия, которая работает, а обслуживать их должна новая. Сложность вся в том, что выключить одно и включить другое нельзя незаметно — между этими двумя состояниями кто-то обслуживает живой поток запросов. Способов пройти этот промежуток обычно называют четыре, и все они описываются без единого числа.
Пересоздание. Старую версию выключают, новую включают. Между ними — перерыв, во время которого сервиса нет. Способ честный и простой, и он вполне годится там, где перерыв заранее разрешён.
Постепенная замена. Экземпляры меняют по одному: выключили один со старой версией, подняли на его месте новый. Перерыва нет, зато какое-то время обе версии работают одновременно и обслуживают общий поток.
Сине-зелёная. Рядом со старой поднимают полный второй комплект — новую версию целиком, — и, когда он готов, переключают на него весь трафик разом. Возврат устроен так же: переключением обратно.
Канареечная. Новую версию получает небольшая доля трафика, всё остальное продолжает идти на старую. Если ничего плохого не происходит, долю постепенно увеличивают до полной.
Вот главное, что стоит вынести из этого списка: канарейка сама по себе — не «безопасная выкатка», а просто маленькая выборка новой версии. Она ничего не решает и ни от чего не защищает; она лишь уменьшает долю трафика, которая столкнётся с дефектом, пока кто-то не решит остановиться.
И отсюда — главный вопрос урока: кто и по какому сигналу решает остановиться? Безопасность появляется не от стратегии, а от того, что рядом со стратегией есть сигнал, по которому выкатку останавливают, и он срабатывает раньше, чем дефект успевает навредить. Без такого сигнала маленькая доля просто растягивает происходящее во времени.
Модель дальше сравнивает не ровно эти четыре способа, а те, между которыми
обычно и выбирают, и называет их так, как они подписаны в прогонах:
blue-green — сине-зелёная, rolling — постепенная замена, canary —
канареечная. Пересоздания в модели нет: сервису с заранее разрешённым перерывом
сигнал не нужен, останавливать нечего. Зато есть четвёртый участник,
feature-flag, — флаг в коде: та же маленькая доля, что у канарейки, но
переключает её не инфраструктура, а само приложение при обработке запроса.
Этого уже достаточно, чтобы ответить на базовый вопрос собеседования. Всё дальнейшее — про то, что происходит, когда сигнал есть, но подобран неудачно, и почему время возврата важнее времени выкатки.
Механизм 1: четыре стратегии при детекторе, который считает ошибки
Первый прогон: тревога поднимается, когда накопилось 200 ошибок; потом ещё минута на решение; потом откат.
1. DETECTION BY ERROR COUNT: 200 ERRORS RAISE THE ALARM
-------------------------------------------------------
strategy affected detected, s rolled back, s
blue-green 520 39 104
rolling 1820 139 499
canary 645 324 414
feature-flag 495 606 666
Результат неожиданный, и в этом главная ценность модели: канарейка (645) оказалась хуже переключения всего трафика разом (520).
Механизм такой. Канарейка держит на новой версии пять процентов трафика, а детектор ждёт двухсот ошибок — и на маленькой доле набирает их в двадцать раз дольше, и тревога отстаёт: 324-я секунда против 39-й у переключения разом. Отставание не выросло вдвадцатеро только потому, что на 300-й секунде канарейка уже стала полной выкаткой. Осторожность превратилась в задержку.
Rolling — худший результат, 1820. Здесь виновата не выкатка, а откат: десять реплик, которые заменялись по одной, точно так же по одной и возвращаются. Между тревогой на 139-й секунде и завершённым откатом на 499-й проходит ровно шесть минут, и всё это время дефект работает на половине трафика и больше.
Механизм 2: тот же дефект, другой детектор
Второй прогон отличается одним: тревога поднимается не по числу ошибок, а по их доле — если новая версия держит плохую долю тридцать секунд.
2. DETECTION BY ERROR RATE: 30 SECONDS OF A BAD SHARE
-----------------------------------------------------
strategy affected detected, s rolled back, s
blue-green 470 29 94
rolling 1270 29 389
canary 29 29 119
feature-flag 4 29 89
Теперь стратегии ведут себя так, как о них рассказывают: канарейка задевает 29 запросов, флаг — 4, а переключение разом — 470.
Сравнение двух прогонов и есть содержание урока:
3. THE SAME STRATEGY UNDER THE TWO DETECTORS
--------------------------------------------
strategy by count by rate times fewer
blue-green 520 470 1.1x
rolling 1820 1270 1.4x
canary 645 29 22.2x
feature-flag 495 4 123.8x
Чем меньше доля трафика на новой версии, тем больше зависимость от детектора. У переключения разом смена детектора почти ничего не меняет (1,1), у флага, который начинает с одного процента, — в 123,8 раза.
Отсюда вывод, которым стоит отвечать: стратегия выкатки и способ обнаружения — одна конструкция, а не две. Канарейка без детектора, способного увидеть беду на маленькой выборке, — это не осторожность, а отсрочка: тот же дефект, только позже и без выигрыша.
Механизм 3: откат важнее выкатки
Посмотрите на колонку «rolled back» в первом прогоне: 104, 499, 414, 666. Это момент, когда трафик полностью вернулся на старую версию.
У rolling самое раннее обнаружение из трёх осторожных стратегий (139 с) и самый долгий откат: между тревогой и концом (499 с) проходят пять минут возврата реплик. У флага обнаружение самое позднее (606 с), но конец наступает ровно в момент решения — через минуту после тревоги (606 → 666): возврат мгновенный.
Отсюда практический критерий, который стоит применять к своей системе: считать надо не «как мы выкатываем», а «за сколько мы возвращаемся». Второе и определяет ущерб, потому что дефект живёт от начала до конца отката, а не до момента обнаружения.
И отсюда же — почему вопрос «сколько держать канарейку» имеет верхнюю и нижнюю границы. Слишком мало — детектор не наберёт выборку и вы выкатите дефект дальше (первый прогон). Слишком долго — вы удлиняете время, в течение которого система живёт в двух версиях сразу, а это отдельный источник ошибок: несовместимые форматы, разные схемы, дублирующиеся фоновые задачи.
Глубже: что не откатывается
Последний раздел — про границу метода, и о ней стоит сказать самому.
Миграции базы данных не откатываются вместе с кодом. Если новая версия добавила колонку и начала в неё писать, возврат кода не удалит данные и не восстановит старую схему. Поэтому изменения схемы делят на совместимые шаги: сначала добавить и научиться читать, потом писать, потом убрать старое — каждый шаг обратим по отдельности.
Отправленные сообщения и внешние вызовы не отзываются: письмо ушло, платёж проведён, сообщение в очередь положено. Откат кода вернёт поведение, но не последствия.
Прогретые кеши и соединения после отката холодные, как после перезапуска (урок про пробы). Поэтому первые минуты после отката система работает хуже, чем до выкатки, — и это надо учитывать, когда решение откатываться принимается по метрике задержки.
Отсюда граница, которую стоит назвать вслух, потому что весь урок до этого места считал откат само собой разумеющимся: откат возможен не всегда. Если новая версия успела изменить схему или сами данные, возвращать код некуда: старая версия не умеет читать то, что записала новая, и «вернуть как было» означало бы ещё одну необратимую операцию поверх первой. В этом положении работает не откат, а движение вперёд — выкатывается исправление, а не предыдущая версия, и делать это приходится под той же аварией, без права на вторую попытку.
Разница практическая, и понимать её надо до аварии, а не во время. План отката имеет смысл ровно до той операции, после которой он перестаёт существовать, — поэтому необратимые шаги и отделяют от обратимых, делают по одному и по возможности откладывают на потом. Тогда у большинства выкаток остаётся дешёвый выход назад, а дорогой путь вперёд остаётся только там, где иначе нельзя.
Как отвечать на собеседовании
Короткий ответ: ущерб от плохой выкатки определяется не стратегией, а произведением трёх вещей — доли трафика на новой версии, времени до обнаружения и времени отката. Поэтому канарейка выигрывает только вместе с детектором, способным увидеть проблему на маленькой выборке: в модели та же канарейка задела 645 запросов при детекторе, ждущем 200 ошибок, и 29 при детекторе по доле ошибок — разница в 22 раза.
Этого достаточно, чтобы ответить верно. Дальше — то, что добавляют, если собеседник копает.
Если интервьюер копает глубже
Хороший ответ отличают три вещи. Первая — вы говорите про откат, а не про выкатку: в модели rolling обнаружил дефект раньше канарейки, но задел почти втрое больше запросов, потому что возврат десяти реплик занимает столько же, сколько их замена. Вторая — вы называете границу применимости: миграции базы, отправленные письма и проведённые платежи не откатываются вместе с кодом. Третья — вы связываете размер канарейки с чувствительностью детектора: слишком маленькая доля означает, что тревога придёт поздно.
И одна вещь, которую стоит сказать до того, как о ней спросят: откат — не универсальное лекарство, а одна из двух возможностей, и вторая иногда единственная. После применённой миграции схемы или данных возвращаться некуда, и работает уже не откат, а движение вперёд с исправлением. Из этого следует практический порядок работы, а не рассуждение: необратимые шаги отделяют от обратимых и делают по одному, чтобы у каждой выкатки как можно дольше сохранялся дешёвый выход назад.
Дальше спросят
Сколько трафика пускать на канарейку?
Столько, чтобы детектор успевал набрать статистику быстрее, чем дефект успеет навредить. В модели пяти процентов хватило при детекторе по доле (29 задетых) и не хватило при детекторе по числу ошибок (645): тревога сработала, когда канарейка уже стала полной выкаткой.
Практический способ выбрать: посчитать, за сколько при выбранной доле накопится столько наблюдений, сколько нужно вашему правилу тревоги. Если это время сравнимо с длительностью самой канарейки — доля мала или правило не то.
Что важнее: быстро выкатывать или быстро откатывать?
Откатывать. Ущерб идёт от начала дефекта до конца отката, а не до момента обнаружения: в модели rolling заметил проблему на 139-й секунде, а закончил возвращать трафик на 499-й — ровно шесть минут дефекта после того, как о нём уже знали.
Поэтому у стратегии стоит спрашивать не «как долго идёт выкатка», а «за сколько мы вернём сто процентов трафика на предыдущую версию» — и проверять это учениями, а не предположениями.
Что делать с миграциями базы?
Разделять изменение схемы на шаги, каждый из которых обратим сам по себе: сначала добавить новое, не трогая старого; потом научить код читать оба варианта; потом писать в новый; и только после того, как откат перестал быть нужен, убрать старое.
Смысл в том, чтобы в любой момент откат кода не требовал отката данных. Обратная схема — «выкатили код и миграцию одной кнопкой» — работает ровно до первого отката, и тогда выясняется, что вернуть можно только код.
Чем отличается флаг от канарейки?
Точкой переключения. У канарейки трафик делит инфраструктура — балансировщик или маршрутизация; у флага решение принимает сам код при обработке запроса. Отсюда и разница в откате: у флага он мгновенный, потому что менять надо значение, а не размещение.
Цена флага — сложность в коде: обе ветки живут одновременно, их надо поддерживать и вовремя удалять. Флаг, оставшийся в коде на год, превращается в скрытую вторую версию системы, которую никто не тестирует.
Частые заблуждения
канарейка всегда безопаснее полного переключения
Только вместе с подходящим детектором. Модель: при тревоге по накопленным двумстам ошибкам канарейка задела 645 запросов против 520 у переключения разом — то есть оказалась хуже, потому что на пяти процентах трафика ошибки копятся в двадцать раз дольше.
главное — стратегия выкатки
Главное — произведение трёх вещей: доли трафика на новой версии, времени до обнаружения и времени отката. Модель: одна и та же канарейка даёт 645 или 29 задетых запросов в зависимости только от детектора — разница в 22,2 раза.
rolling — безопасный вариант по умолчанию
В модели он худший: 1820 задетых запросов. Причина не в выкатке, а в откате — десять реплик возвращаются так же по одной, и между тревогой на 139-й секунде и концом отката на 499-й дефект работает на половине трафика и больше.
если дефект обнаружен, ущерб прекратился
Ущерб идёт до конца отката. В модели самая ранняя тревога (rolling, 139 с) сочетается с самым долгим возвратом (499 с), а самая поздняя (флаг, 606 с) — с мгновенным. Считать надо время до возвращения трафика, а не до срабатывания тревоги.
чем меньше доля канарейки, тем безопаснее
Тем сильнее зависимость от детектора: в модели смена детектора меняет результат переключения разом в 1,1 раза, а флага с одним процентом трафика — в 124 раза. Маленькая доля без чувствительного правила тревоги — это отсрочка, а не защита.
откат возвращает систему в прежнее состояние
Возвращает код, а не последствия: миграция базы уже применена, письма отправлены, платежи проведены. Плюс кеши и пулы после отката холодные, как после перезапуска, поэтому первые минуты система работает хуже, чем до выкатки. А иногда откат невозможен вовсе: если старая версия не умеет читать то, что записала новая, остаётся не откат, а движение вперёд — выкатка исправления под той же аварией.
флаг — это просто канарейка, сделанная в коде
Разница в точке переключения и, как следствие, в скорости отката: у флага возврат мгновенный, потому что меняется значение, а не размещение. Цена — обе ветки живут в коде одновременно, и забытый флаг превращается в скрытую вторую версию системы.
Практика
Две задачи. Сначала ответьте, потом сверьтесь с настоящим выводом: в обеих правильный ответ взят из прогона модели, а не назначен.
Практика · что напечатает
canary_count = simulate("canary", "count")
canary_rate = simulate("canary", "rate")
rolling_rate = simulate("rolling", "rate")
print(canary_count["affected"])
print(canary_rate["affected"])
print(rolling_rate["affected"])Практика · оцените
Проверка знаний
Канареечная выкатка на 5 % трафика, тревога поднимается по накоплении 200 ошибок. Что произойдёт?
Это не пересказ и не отдельный текст: всё ниже взято из самой статьи — её выжимка, заголовки разборов, колонка «на самом деле» и таблица версий. Поэтому разойтись со статьёй эти тезисы не могут.
Суть
- Стратегия выкатки не защищает сама по себе — защищает сигнал, по которому её останавливают. Канарейка — это не «безопасная выкатка», а всего лишь маленькая доля трафика на новой версии. Уменьшив долю, вы уменьшили не риск, а размер выборки, по которой беду ещё предстоит заметить; пока рядом нет правила, способного увидеть беду на этой маленькой выборке, осторожность оборачивается отсрочкой.
- Отсюда результат, ради которого стоит урок: осторожная стратегия может проиграть прямолинейной. Модель: при детекторе, который ждёт 200 накопленных ошибок, канарейка задела 645 запросов против 520 у переключения всего трафика разом. Причина — размер выборки: на пяти процентах трафика фиксированное число ошибок копится в двадцать раз дольше, и тревога просто ждёт объёма. Тот же дефект и та же канарейка с детектором по доле ошибок — 29 запросов.
- Дальше — числа, вторая половина темы и граница. Разница между двумя детекторами при одной и той же канарейке — в 22,2 раза; у флага, который начинает с одного процента трафика, смена детектора меняет результат в 123,8 раза, и при детекторе по доле он задевает 4 запроса против 29 у канарейки — доля впятеро меньше, а возврат не занимает времени вовсе. Худший результат в модели у rolling — 1820 задетых запросов, и виновата в нём не выкатка, а откат: возврат десяти реплик занимает столько же, сколько их замена. Числа здесь модельные: дефект, поток, пороги и длительности заданы допущениями, содержательны отношения между строками. И у всего этого есть граница: откат возможен не всегда — после применённой миграции или отправленного письма возвращать уже нечего.
На самом деле
- Только вместе с подходящим детектором. Модель: при тревоге по накопленным двумстам ошибкам канарейка задела 645 запросов против 520 у переключения разом — то есть оказалась хуже, потому что на пяти процентах трафика ошибки копятся в двадцать раз дольше.
- Главное — произведение трёх вещей: доли трафика на новой версии, времени до обнаружения и времени отката. Модель: одна и та же канарейка даёт 645 или 29 задетых запросов в зависимости только от детектора — разница в 22,2 раза.
- В модели он худший: 1820 задетых запросов. Причина не в выкатке, а в откате — десять реплик возвращаются так же по одной, и между тревогой на 139-й секунде и концом отката на 499-й дефект работает на половине трафика и больше.
- Ущерб идёт до конца отката. В модели самая ранняя тревога (rolling, 139 с) сочетается с самым долгим возвратом (499 с), а самая поздняя (флаг, 606 с) — с мгновенным. Считать надо время до возвращения трафика, а не до срабатывания тревоги.
- Тем сильнее зависимость от детектора: в модели смена детектора меняет результат переключения разом в 1,1 раза, а флага с одним процентом трафика — в 124 раза. Маленькая доля без чувствительного правила тревоги — это отсрочка, а не защита.
- Возвращает код, а не последствия: миграция базы уже применена, письма отправлены, платежи проведены. Плюс кеши и пулы после отката холодные, как после перезапуска, поэтому первые минуты система работает хуже, чем до выкатки. А иногда откат невозможен вовсе: если старая версия не умеет читать то, что записала новая, остаётся не откат, а движение вперёд — выкатка исправления под той же аварией.
- Разница в точке переключения и, как следствие, в скорости отката: у флага возврат мгновенный, потому что меняется значение, а не размещение. Цена — обе ветки живут в коде одновременно, и забытый флаг превращается в скрытую вторую версию системы.
Что разобрано
- Что здесь на самом деле спрашивают
- База: как вообще заменяют работающую версию
- Механизм 1: четыре стратегии при детекторе, который считает ошибки
- Механизм 2: тот же дефект, другой детектор
- Механизм 3: откат важнее выкатки
- Глубже: что не откатывается
- Как отвечать на собеседовании
- Дальше спросят
- Частые заблуждения
- Практика
- Проверка знаний
Источники и что читать дальше
1 ИСТОЧНИК
- Модель этого урока: четыре стратегии выкаткиИсточник. Внешнего первоисточника у этого урока нет: стратегии выкатки описываются документацией отдельных продуктов, общего стандарта у них нет. Урок стоит на модели с объявленными допущениями: 100 запросов в секунду, дефект ломает 5 % запросов на новой версии, обнаружение либо по 200 накопленным ошибкам, либо по доле ошибок за 30 секунд, решение об откате занимает 60 секунд, а сам откат у каждой стратегии свой: 5 с у blue-green, 300 с у rolling, 30 с у канарейки, мгновенно у флага. Всё, что печатает прогон, выводится из этих правил./ru/bench/rollout/strategies.py