Заметки релизного цикла — часть V
Самой цитируемой страницей, не ушедшей в продакшен в прошлом квартале, оказалась та, на которой наш наблюдатель сработал сам в 2:14 ночи. Кандидатный релиз прошёл гейт, выдержал положенные четыре минуты на 5%, продвинулся до 25% и там завис. Поминутный монитор качества зафиксировал три подряд значения ниже порога на юридическом срезе, остановил выкатку и перенаправил маршрутизацию на предыдущий релиз. К моменту, когда уведомление дошло до дежурного инженера — за квитанцией, а не за инцидентом — продакшен-трафик уже девять минут как был возвращён на проверенный релиз.
Никому не нужно было ничего делать. Архитектура из первой статьи серии описывает, что представляют собой четыре стадии. Этот материал — о том, что работает между человеческими одобрениями: о слое автоматизации под архитектурой, о границе, на которой конвейер либо сам делает правильное действие, либо нет.
Главное утверждение: большинство решений конвейера должно быть автоматизировано, но не все. Граница имеет значение. Конвейер, автоматизирующий всё подряд, рано или поздно продвинет релиз, который должен был отловить человек; конвейер, не автоматизирующий ничего, бессмыслен. Эта статья — о том, как провести границу правильно.
Спектр автоматизации
Каждое решение конвейера находится где-то на спектре от «срабатывает само без уведомлений человека» до «отказывается продолжать без явного подписанного одобрения». Ниже показано, где на этом спектре в нашем поставляемом конвейере располагается каждое из несущих действий.
Два из маркеров выше — красные, а не окрашены по своей зоне. Это асимметричные решения: два места, где конвейер занимает жёсткую позицию по поводу того, кто за что отвечает. Триггер авто-отката срабатывает, не спрашивая; его нельзя отключить в конфигурации, потому что весь смысл его существования в том, чтобы он работал в 2:14 ночи. Переопределение при провале гейта отказывается продолжать без письменного обоснования; и его тоже нельзя отключить в конфигурации, потому что весь смысл в том, чтобы будущему-вам нужно было прочесть причину. Большая часть остального конвейера конфигурируема; эти два решения — нет.
Как на самом деле срабатывает авто-откат
Самый частый вопрос про авто-откат — «что мешает ему сработать по ошибке?» Честный ответ: ничто в одиночку. Защита возникает из того, как устроена проводка триггера.
Стадия Observe запускает цикл поминутной оценки. Каждую минуту она:
- Берёт небольшую выборку недавних продакшен-трейсов с активного релиза.
- Воспроизводит каждый трейс через активную модель (не кандидатную — мы оцениваем то, что фактически обслуживает).
- Оценивает каждое воспроизведение тем же откалиброванным судьёй с человеческим якорем, который вёл Gate-2[1].
- Вычисляет единый показатель качества вывода по выборке. Записывает его в
CanaryHealthSample.
Откат срабатывает, когда три подряд поминутных значения опускаются ниже порога отката (по умолчанию: 0,85 от порога гейта — то есть 0,55, если гейт был 0,65). Не одна плохая минута; три. Трёхминутная блокировка — это шумовой фильтр: единичное аномальное значение не запускает ничего, а устойчивая регрессия — да.
Когда блокировка снимается, воркер отката выполняет:
# По факту — конвейер запускает это сам. Без подтверждения человеком.
POST /api/v1/releases/<previous_release_sha>/activate
# ответ за <1с; слив запросов в полёте ~12с на сервисе ~100 репликСрабатывает квитанция. Дежурный инженер видит уведомление в Slack за квитанцию, а не за инцидент. Он открывает квитанцию; видит три значения ниже порога, прошедшее время и хеши vindex_sha256_before/after[2]. Двенадцать секунд — это время слива запросов в полёте; сам обмен — субсекундный. К тому моменту, когда инженер достаточно проснётся, чтобы спросить «нужно ли мне что-то делать?», ответом будет «нет, но всё же стоит посмотреть, почему гейт это пропустил».
Реальная квитанция авто-отката
Вот как выглядит квитанция в продакшене. Тот же формат с цепочкой хешей, документированный на странице комплаенса, с дополнительными полями, специфичными для события авто-отката:
{
"kind": "auto_rollback",
"release_id": "rel_a01c66",
"previous_release_id": "rel_8f72b1",
"trigger_at": "2026-05-29T02:14:23Z",
"completed_at": "2026-05-29T02:14:35Z",
"elapsed_seconds": 12,
"trigger_reason": "observer_quality_threshold_breach",
"observer_readings": [
{ "minute_at": "2026-05-29T02:11:00Z", "quality_score": 0.523, "below_threshold": true, "slice": "legal-IP-licensing" },
{ "minute_at": "2026-05-29T02:12:00Z", "quality_score": 0.508, "below_threshold": true, "slice": "legal-IP-licensing" },
{ "minute_at": "2026-05-29T02:13:00Z", "quality_score": 0.491, "below_threshold": true, "slice": "legal-IP-licensing" }
],
"rollback_threshold": 0.55,
"active_manifest_sha256_before": "9abaeaf6c91f8b...",
"active_manifest_sha256_after": "8f72b1de4a93c5...",
"audit_chain_signature": "sha256(...)",
"notified_users": ["oncall@customer.example"],
"notification_sent_at": "2026-05-29T02:14:36Z"
}Сама квитанция — это первая точка контакта для дежурного. Её прочтение отвечает на вопросы, которые полусонный инженер действительно задаст: что сработало, какой срез провалился, насколько, сколько занял обмен, что сейчас работает. Подсказка к следующему действию обычно звучит как «пойди разберись, почему гейт вообще это пропустил» — а в квитанции провалившегося релиза уже есть таблица Спирмена по срезам.
Что конвейер НЕ делает сам
Следствие из «авто-откат срабатывает, не спрашивая» — это то, что некоторые другие вещи активно не могут произойти. Три явных отказа.
Он не продвигает релиз, проваливший гейт, без подписанного переопределения. Провал гейта помечает релиз как gate_fail; эндпойнт /activate отказывается принимать SHA манифеста; ни один трюк в командной строке это не обходит. Единственный путь вперёд — принудительное переопределение с forceGateOverride: true И overrideReason: "<свободный текст>". Поле причины обязательно, формат свободный, и оно попадает в журнал аудита вместе с идентификатором пользователя. Мы сделали так, чтобы будущий-вы мог прочесть, почему текущий-вы решил, что регрессия среза приемлема. В реальной эксплуатации путь переопределения использовали три человека. Их обоснования всё ещё лежат в журнале аудита.
Он не продвигает канарейку с уровня до 100%, если хоть один монитор деградирует. Если латентность p95, доля 5xx ИЛИ показатель качества вывода выходит за пределы коридора к концу выдержки на контрольной точке, конвейер останавливается на этой точке и оповещает дежурного. Он не продвигается, чтобы потом извиняться.
Он не запускает авто-канарейку для холодного релиза. У релиза без истории продакшен-трафика — свежий файнтюн на новом датасете, скажем, — нет ничего, с чем сравнивать качество вывода. Конвейер отказывается запускать канарейку на холодном релизе. Мы требуем сначала 24-часовое теневое развёртывание, которое наблюдает кандидата на реальных продакшен-трейсах, но не отдаёт его ответы. После 24 часов у нас есть базовая линия качества; затем канарейка может стартовать. Медленнее; честнее; не конфигурируется.
Насколько быстро восстановление, от начала до конца?
Публикуемое нами значение времени восстановления — 12 секунд. Это слив запросов в полёте на сервисе ~100 реплик. Сам обмен манифестом — субсекундный. Чтобы это было полезно читателю, 12 секунд нужно разложить:
- 0–60 секунд до отката: приходят три подряд значения ниже порога. Первое такое значение запускает таймер блокировки. Каждая последующая минута продлевает блокировку, если качество всё ещё ниже порога.
- t = 0: третье значение ниже порога пишется в
CanaryHealthSample. Воркер отката фиксирует третий страйк и отправляет/activate previous_release. - t < 1 секунды: указатель активного релиза в слое маршрутизации (в Redis) переключается. Новые запросы начинают попадать в предыдущий релиз.
- t = 1 до ~12 секунд: кандидатный релиз продолжает обслуживать запросы, которые были в полёте на момент обмена. Слив в полёте. Некоторые потоковые ответы естественно завершаются 8–10 секунд, так что хвост очистки в типичном сервисе — около 12с.
- t ≈ 13 секунд: запись квитанции аудита пишется и подписывается. Срабатывает уведомление.
В сравнении с публичными постмортемами, на которые мы продолжаем ссылаться как на якоря: июньская авария Cloudflare 2022 года[3] заняла 44 минуты от «мы знаем, что откатывать» до «откат завершён» — и это был инфраструктурный уровень. Апрельская авария Atlassian 2022 года[4] заняла по 12 часов на сайт, потому что состояние было размазано по нескольким системам. Порог «элитного исполнителя» по DORA[5] для восстановления после провалившегося развёртывания — менее одного часа. Двенадцать секунд — не на порядок лучше элитного порога; это на три порядка лучше. Архитектурное решение, которое делает это возможным, — упакованный релизный манифест из Стадии 1. Без манифеста у вас нет единого объекта, на который можно перенаправить маршрутизацию.
Репетиции отката — непривлекательная практика, которую никто не проводит
Вот часть, которую большинство команд пропускает: единственный надёжный сигнал того, что путь отката работает, — это то, что вы провели целенаправленную, запланированную репетицию и подтвердили это. Каждый квартал мы проводим одну. Репетиция выглядит так:
- Выберите случайно запланированное время в рабочий час будня. Сообщите команде, что она будет, но не уточняйте конкретный час.
- Внедрите синтетическую регрессию качества на канареечном срезе. (У нас есть тестовый флаг, позволяющий кандидатной модели отвечать на магический заголовок фразой «Я отказываюсь отвечать» — гарантированный провал у откалиброванного судьи.)
- Прогоните тестовый релиз через гейт (он проходит — мы тестируем откат, а не гейт). Запустите канарейку.
- Наблюдатель фиксирует три значения ниже порога. Срабатывает авто-откат.
- Подождите, пока дежурный инженер отреагирует. Засеките, сколько ему понадобится времени. Отметьте, доверяет ли он квитанции достаточно, чтобы не эскалировать тревогу.
- Убедитесь, что журнал аудита показывает флаг тестового режима в квитанции отката, чтобы будущие аудиты могли отличать репетицию от реального инцидента.
Первая наша репетиция заняла 19 секунд от начала до конца (12с обмена + 7с задержки установления, которую пришлось чинить). Самая недавняя — Q1 2026 — заняла 12 секунд. Репетицию пропускать нельзя. Каждый квартал; на каждом клиентском кластере.
Большинство команд никогда не проводили целенаправленную репетицию отката. Первый раз, когда их путь отката отрабатывает, — это реальный инцидент под давлением, с несколькими людьми в звонке. Репетиция — это то, что превращает 12-секундное значение в реальное число, а не в желаемое.
Что это не решает
Три честных ограничения:
Авто-откат может уйти в пинг-понг. Если ПЛОХ И кандидат, И предыдущий релиз — скажем, у предыдущего релиза тоже была медленно развивающаяся регрессия среза, которую никто не заметил, — конвейер может откатиться, затем предыдущий релиз тоже провалит пост-откатное наблюдение, и третьего релиза, на который можно откатиться, не окажется. Конвейер останавливает трафик на странице обслуживания, а не мечется. Решение — держать в цепочке манифестов более одного предыдущего здорового релиза, чтобы цель отката была конфигурируемой.
Наблюдатель добавляет стоимость инференса. Воспроизведение продакшен-трейсов через активную модель на 5% выборке добавляет примерно 5% к затратам на инференс. Мы считаем это правильным компромиссом. Часть клиентов считает, что для низкомаржинальных нагрузок это слишком дорого, и хочет уменьшить частоту выборки. Регулятор есть.
Плохой судья хуже отсутствия судьи. Если откалиброванный судья, который ведёт наблюдателя, сам разкалиброван — отошёл от человеческого якоря или обучен на устаревшем корпусе — наблюдатель может запускать авто-откат по неправильной причине. Каденс рекалибровки имеет значение. Материал Calibrating-the-Judge[6] описывает процедуру; операционное требование — чтобы вы её действительно выполняли.
FAQ
Почему триггер отката — три подряд минуты, а не одна?
Потому что у показателей качества LLM есть шумовой пол: единичное аномальное минутное значение может прийти из особенностей выборки (5% выборка трейсов случайно попала на сложный срез), а не из реальной регрессии. Трёхминутная блокировка — самый дешёвый шумовой фильтр, который при этом удерживает общее время реакции под полторы минуты. Мы тюнинговали в обе стороны; три — оптимум для типичной формы трафика наших клиентов. Выдержка конфигурируется на уровне релиза, если форма вашего трафика отличается.
Должен ли авто-откат быть конфигурируемым на «выкл»?
В нашем поставляемом конвейере — нет. Смысл автоматического механизма безопасности в том, чтобы он работал в 2:14 ночи, когда никто не смотрит. Конфигурируемо отключаемый авто-откат — это стикер со словами «у нас раньше была страховочная сетка». Аргумент за конфигурируемость состоит в том, что некоторые нагрузки слишком малозначимы, чтобы оправдать любые ложноположительные откаты. Мы считаем, что этот аргумент ведёт не туда: если ваша нагрузка слишком малозначима для авто-отката, то вам и релизный конвейер не нужен.
Как вы обрабатываете случай, когда предыдущий релиз тоже плох?
Цель отката по умолчанию — previous_release, но цепочка манифестов хранит больше истории, чем просто N-1. Операторы могут перенаправить откат на любой исторически здоровый SHA манифеста — /api/v1/releases/<historically_good_sha>/activate — и это путь ручного вмешательства, когда автоматический откат на N-1 попадает в плохой более ранний релиз. Аварийный клапан есть. Он срабатывает редко.
Какую метрику правильно оптимизировать — MTTR или MTBF?
MTTR — среднее время до восстановления — с большим отрывом, по крайней мере для LLM-систем. MTBF (среднее время между отказами) предполагает детерминированное понятие «отказа», которого у LLM-нагрузок нет. Качество вывода непрерывно дрейфует; «отказ» — это пороговое решение. Оптимизация под быстрое восстановление устойчива к тому, где вы рисуете порог; оптимизация под «никогда не падать» хрупка и ложна. Элитный порог DORA[5] сам сформулирован в терминах MTTR, и это правильная рамка.
Вы действительно проводите репетиции отката?
Да — ежеквартально, по расписанию, с флагом тестового режима в квитанции, чтобы репетицию можно было отличить от реального инцидента в журнале аудита. Первая наша репетиция вскрыла 7-секундную задержку установления, о которой мы не догадывались. Репетиция — единственный способ узнать, что путь действительно работает; прочтения runbook недостаточно. Большинство команд её не проводили — именно поэтому показатели MTTR у большинства команд скорее желаемые, чем измеренные.
References
- Калибровка LLM-as-judge. Zheng et al., Judging LLM-as-a-Judge with MT-Bench and Chatbot Arena (NeurIPS 2023). Якорь для того, почему откалиброванный судья необходим и почему согласие по срезам важнее агрегированного. Поминутный цикл оценки наблюдателя опирается на это.
- Аттестация весов vindex. Документирована на странице комплаенса Divinci и разобрана в материале про регулируемые области. Поля `vindex_sha256_before/after` в квитанции авто-отката — это криптографический якорь, который аудитор может проверить, не доверяя нашим логам.
- Авария Cloudflare в июне 2022. Cloudflare outage on June 21, 2022. «06:58: первопричина найдена и понята. Начинается работа по откату проблемного изменения… 07:42: последний из откатов завершён». Сорок четыре минуты на откат на инфраструктурном уровне, отчасти потому что инженеры наступали на откаты друг друга. Якорь для утверждения «обмен, управляемый манифестом, такого режима отказа иметь не может».
- Авария Atlassian в апреле 2022. Post-Incident Review: April 2022 Outage. 12 часов на сайт на восстановление, 14 дней суммарно на 883 сайта, потому что состояние было размазано по независимо версионируемым системам. Якорь для утверждения «упакованный релизный манифест — это то, что делает секунды-а-не-часы возможными».
- Порог восстановления после провалившегося развёртывания по DORA. DORA — Software delivery performance metrics. Порог «элитного исполнителя» для времени восстановления после провалившегося развёртывания документирован как менее одного часа. Значение 12 секунд у конвейера — на три порядка ниже элитного порога, и так это сравнение и стоит читать.
- Калибровка ИИ-судьи. Наш сопутствующий материал Calibrating the AI Judge. Процедура удержания человеко-якорного судьи в калибровке со временем. Операционное утверждение этой статьи — что авто-откат работает только настолько хорошо, насколько хорош ведущий его судья, — справедливо только при условии, что судья действительно периодически рекалибруется.
- Внутреннее — справка по конвейеру Divinci. Архитектура, под которой лежит этот слой автоматизации: материал о четырёхступенчатом конвейере. Полная поверхность API задокументирована в справочнике API; раздел про управление релизами — это тот, о котором говорит эта статья.
Следующее в этой серии: CI-тестирование пользовательских языковых моделей в 2026. Эта статья — про операционный слой между человеческими одобрениями. Следующая — про слой до старта конвейера: pre-merge CI: что оценивать на момент PR, какие виды регрессий вы действительно ловите до того, как их увидит гейт, и какие — нет.
Ready to Build Your Custom AI Solution?
Discover how Divinci AI can help you implement RAG systems, automate quality assurance, and streamline your AI development process.
Get Started Today