Выкатка и откат: почему канарейка не помогает без нужного детектора
Четыре стратегии выкатки на одном и том же дефекте — и неожиданный результат: одна и та же канарейка задевает в двадцать два раза меньше запросов, если тревога поднимается по доле ошибок. С детектором, который ждёт двухсот ошибок, она проигрывает переключению всего трафика разом.
Полное техническое изложение
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): возврат мгновенный.
Отсюда практический критерий, который стоит применять к своей системе: считать надо не только «как мы выкатываем», но и «за сколько мы возвращаемся»: дефект живёт от начала до конца отката, а не до момента обнаружения.
Как именно модель это считает, видно из четвёртого раздела того же прогона. В каждую секунду испорчено столько запросов: поток × доля трафика на новой версии × доля запросов, которые ломает дефект (100 × доля × 5 %). Ущерб — сумма этого по всем секундам до конца отката, а время до тревоги, минута на решение и сам откат идут друг за другом, поэтому их длительности складываются:
4. WHERE THE AFFECTED REQUESTS COME FROM: THE THREE TIMES ADD UP
----------------------------------------------------------------
run phase seconds affected
canary, by rate until alarm 29 7.25
deciding 60 15.00
rolling back 30 7.50
sum 119 29.75 -> 29 in table 1/2
canary, by count until alarm 324 195.00
deciding 60 300.00
rolling back 30 150.00
sum 414 645.00 -> 645 in table 1/2
feature-flag, by rate until alarm 29 1.45
deciding 60 3.00
rolling back 0 0.00
sum 89 4.45 -> 4 in table 1/2
У канарейки с детектором по доле ошибок доля трафика постоянна, и всё сводится к одной формуле: 100 × 5 % × 5 % × (29 + 60 + 30) с = 29,75, в таблице — 29. У детектора по числу ошибок доля трафика на 300-й секунде выросла с пяти процентов до ста, поэтому там сумма по секундам, а не одно умножение: 645. И это не произведение времён: у флага откат занимает 0 с, а задетые запросы всё равно есть — они случились до тревоги и во время решения. Модель при этом считает только испорченные ответы: необратимые записи, потерянные данные и деньги в неё не входят — о них раздел ниже.
И отсюда же — почему у ответа на вопрос «сколько держать канарейку» есть и нижняя, и верхняя граница. Слишком мало — детектор не наберёт выборку, и вы выкатите дефект дальше (первый прогон). Слишком долго — вы удлиняете время, в течение которого система живёт в двух версиях сразу, а это отдельный источник ошибок: несовместимые форматы, разные схемы, дублирующиеся фоновые задачи.
Глубже: что не откатывается
Последний раздел — про границу метода, и о ней стоит сказать самому.
Миграции базы данных не откатываются вместе с кодом. Если новая версия добавила колонку и начала в неё писать, возврат кода не удалит данные и не восстановит старую схему. Поэтому изменения схемы делят на совместимые шаги: сначала добавить и научиться читать, потом писать, потом убрать старое — каждый шаг обратим по отдельности.
Отправленные сообщения и внешние вызовы не отзываются: письмо ушло, платёж проведён, сообщение в очередь положено. Откат кода вернёт поведение, но не последствия.
Прогретые кеши и соединения после отката холодные, как после перезапуска (урок про пробы). Поэтому первые минуты после отката система работает хуже, чем до выкатки, — и это надо учитывать, когда решение об откате принимают по метрике задержки.
Отсюда граница, которую стоит назвать вслух, потому что весь урок до этого места считал откат само собой разумеющимся: откат возможен не всегда. Если новая версия успела изменить схему или сами данные, возвращать код некуда: старая версия не умеет читать то, что записала новая, и «вернуть как было» означало бы ещё одну необратимую операцию поверх первой. В такой ситуации работает не откат, а движение вперёд — выкатывается исправление, а не предыдущая версия, и делать это приходится под той же аварией, без права на вторую попытку.
Разница практическая, и понимать её надо до аварии, а не во время неё. План отката имеет смысл ровно до той операции, после которой он перестаёт существовать, — поэтому необратимые шаги и отделяют от обратимых, делают по одному и по возможности откладывают на потом. Тогда у большинства выкаток остаётся дешёвый выход назад, а дорогой путь вперёд нужен только там, где иначе нельзя.
Как отвечать на собеседовании
Короткий ответ: ущерб от плохой выкатки зависит от того, сколько трафика и как долго обслуживает дефектная версия, и от тяжести дефекта, а не от названия стратегии. При постоянных потоке и долях число испорченных запросов — это поток × доля трафика на новой версии × доля запросов, которые ломает дефект, × суммарное время воздействия, а время до тревоги, решение и откат в этом времени складываются. Поэтому канарейке нужен детектор, способный заметить проблему на маленькой выборке: в модели урока та же канарейка задела 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