Последний участок пути — самый обидный, чтобы его завалить. Проблема названа, срез нарезан, код написан — и всё это ничего не стоит, пока не оказалось у пользователя и пока ты не увидел, сработало оно или нет. Владение от идеи до пользователя заканчивается не на «выкатил», а на «убедился, что помогло». Значит, релиз и измерение — не служебная процедура в конце, а часть продуктовой работы, и делает её тот же человек, что писал код.
Разговор здесь не про то, как настроить конвейер доставки — крафт деплоя живёт в непрерывной интеграции и доставке и в облачной инфраструктуре, и там ему и место. Разговор про продукт-инженерную петлю поверх этого крафта: как одному человеку выкатить дёшево, померить бизнес-результат, а не выпуск, и замкнуть круг «выкатил → увидел в данных → поправил» так, чтобы он крутился, а не забивался.
Почему релиз должен быть дешёвым
Дорогой релиз — тихий убийца итерации. Если выкатить изменение стоит полдня нервов, ручной сборки и молитвы, ты будешь выкатывать редко и крупными пачками. А крупная пачка — это и большой риск (сломалось — не понять, что именно), и отложенная обратная связь (пока копил на один большой релиз, три недели не видел, работает ли первая гипотеза). Оба эффекта бьют по главному: по скорости, с которой ты учишься на реальности.
Дешёвый релиз переворачивает это. Когда выкатить — вопрос одной команды и пары минут, ты выкатываешь маленькими шагами и часто. Каждый шаг несёт мало изменений, поэтому если что-то сломалось, причина очевидна. Каждый шаг быстро доходит до пользователя, поэтому петля обратной связи короткая.
Продукт-инженер вкладывается в дешевизну релиза не из любви к автоматизации, а потому что это прямой множитель на число итераций, которые он успеет прожить. Один человек не может позволить себе тратить итерации впустую — их у него и так меньше, чем у команды.
Минимальный конвейер для одиночки — это не корпоративный контур с пятью средами и ручными гейтами. Это «собралось → прогнались тесты → выкатилось» одним движением, с возможностью откатиться так же быстро. Возможность безопасно откатиться важнее, чем защита от любого мыслимого сбоя: она и есть то, что позволяет выкатывать смело. Всё остальное — наращивание по мере того, как продукт начинает этого стоить, не раньше.
Мерить бизнес-результат, а не выпуск
Выкатить — это ещё не результат, это только его предпосылка. Соблазн в том, чтобы засчитать себе выпуск: фича в проде, тикет закрыт, можно радоваться. Но выпуск — это то, что сделал ты; бизнес-результат — это то, что изменилось у пользователя. Между ними нет автоматической связи, и вся продуктовая работа живёт именно в этом зазоре.
Про это — отдельное эссе о бизнес-метрике, и здесь важно не пересказать его, а увязать с моментом релиза. В момент выката ты уже должен знать, по какому числу поймёшь, что изменение сработало. Не «посмотрим на дашборды», а конкретно: какая метрика должна сдвинуться, в какую сторону, за какой срок. Если ты не можешь назвать это число до релиза, ты выкатываешь вслепую — и как бы красиво ни легла фича, узнать, помогла ли она, будет уже не с чем сравнить.
Бизнес-метрика — почти всегда про поведение: стали ли доходить до конца, вернулись ли, перестала ли расти жалоба, которую ты чинил. Метрика выпуска — про факт отгрузки: сколько задеплоили, сколько строк, сколько фич. Первая говорит о пользователе, вторая — о тебе. Владеющий результатом смотрит на первую, даже когда вторая приятнее и её проще предъявить.
Как не утонуть в дашбордах
У измерения есть обратная ловушка: намерить всё. Поставить аналитику на каждое событие, собрать сорок графиков и утонуть в них так же надёжно, как тонут, не меряя ничего. Для одного человека это особенно опасно — время, ушедшее на разглядывание дашбордов, не ушло на следующий срез.
Спасение — привязывать измерение к решению. Метрика имеет смысл, только если по её значению ты сделаешь что-то разное: при одном бизнес-результате — закрепишь и пойдёшь дальше, при другом — откатишь или переделаешь. Если по числу ты в любом случае поступишь одинаково, мерить его незачем — это украшение, а не инструмент. Такой вопрос — «что я сделаю по-разному в зависимости от этого числа?» — отсекает большую часть дашбордов ещё до того, как ты их построил.
Отсюда и роль аналитики для продукт-инженера — лёгкая. Не хранилище данных и не BI-контур, а несколько событий вокруг гипотезы, которую ты сейчас проверяешь, и одно-два числа, которые прямо отвечают, сработал ли последний срез. Тяжёлая аналитика приходит, когда продукт дорос до неё; на старте она чаще признак того, что человек прячется за данными вместо того, чтобы смотреть на одно решающее число. Как эти числа складываются в устройство продукта — тема проектирования систем; здесь достаточно того минимума, что замыкает петлю.
Пример: один выкат от начала до конца
Принципы выше проще понять на конкретном выкате — когда видно, где петля замыкается, а где рвётся.
Задача: добавить кнопку «Оформить быстро» на карточку товара. Гипотеза — убирает лишний шаг в воронке и поднимает конверсию в заказ.
Перед выкатом — короткий чек-лист:
- тесты зелёные, сборка чистая;
- фича скрыта за флагом, откат — одна команда, меньше минуты;
- метрика зафиксирована: конверсия из карточки товара в заказ (CR_card) должна вырасти с 1,2% до ≥1,5% на когорте флага за 48 часов;
- базовое значение CR_card записано — сравнивать будет с чем.
Выкат: флаг включён на 10% трафика. Всё чисто, ошибок нет.
Через 6 часов: на когорту набежало около 12 тысяч просмотров карточки, CR_card 0,9% — хуже исходного, а не лучше. Объём уже такой, что списать разницу на случайность трудно, — но шесть часов слишком рано для приговора гипотезе: это повод пойти и посмотреть, а не делать вывод по одному числу.
Смотришь в записи сессий: на мобильных кнопка перекрывает фото товара, пользователи уходят раньше, чем добираются до CTA. Это не «гипотеза провалилась» — это верстка сломала страницу на мобильном.
Откат флагом. Правка CSS — 20 минут. Перевыкат. Через 48 часов: CR_card 1,7% — выше порога. Закрепляем, раскатываем на 100%.
Три вещи, которые сработали: фича-флаг дал откат без простоя и без риска; зафиксированный порог до выката (не «что-нибудь вырастет», а «1,5% за 48 часов») сразу показал, что 0,9% — повод идти разбираться, а не терпимое отклонение; одно число вместо двадцати — нашёл причину за несколько минут, а не утонул в графиках.
Петля «выкатил → увидел → поправил»
Всё это собирается в один короткий цикл. Ты выкатываешь маленький срез — дёшево, потому что конвейер дешёвый. Смотришь на одно-два числа бизнес-результата — а не выпуска. По этим числам решаешь: закрепить, доработать или откатить. И идёшь на следующий виток. Ценность не в отдельном обороте, а в том, что их много и они быстрые: продукт находится не гениальным замыслом, а числом коротких честных петель.
Петля замыкается сама на себя: решение по числу становится входом в следующий выкат.
Именно это владение и означает на последнем участке: ты же и меришь, и правишь. Нет стыка, через который релиз перекидывают «в эксплуатацию», а метрику — «в аналитику», и где ответственность растворяется. Тот, кто написал, тот и выкатил, тот и посмотрел в данные, тот и поправил. Петля замкнута на одного человека — и поэтому она замкнута вообще: некому переложить, значит, некому и уронить.
Это и есть замыкание продуктовой петли. Проблема доводится до среза, срез — до кода, код — до пользователя, а бизнес-результат — обратно до решения. Круг сходится, и один человек прошёл его целиком: увидел боль, выбрал число, нарезал, написал, выкатил и проверил, что помогло. Дешёвый релиз и лёгкая метрика — это не финальная бюрократия, а то, что не даёт всей предыдущей работе повиснуть в воздухе на последнем шаге.
Когда число не сдвинулось, а вёрстка ни при чём
В примере выше исход самый удобный: метрика упала, причина оказалась в вёрстке, правка заняла двадцать минут. Так бывает, и довольно часто — но есть исход, к которому надо готовиться отдельно: всё сделано правильно, ничего не сломано, и число не сдвинулось.
Отличить один случай от другого — первое действие, и оно занимает полчаса. Порядок проверки от дешёвого к дорогому:
- Дошло ли изменение до людей. Включён ли флаг, на какую долю, попали ли в неё те, кого вы имели в виду. Самая частая и самая обидная причина «ничего не изменилось» — изменение видели сорок человек вместо четырёх тысяч.
- Нашли ли новое. Событие «увидел» против события «воспользовался». Если видели тысячи, а нажали единицы, это не про гипотезу, это про заметность.
- Работает ли вообще. Ошибки в этом сценарии, время ответа, отвалы на полпути. Молчаливая поломка у части пользователей выглядит в цифрах точно как неподтверждённая гипотеза.
- Правильно ли считается число. Событие не туда, когорта не та, сравнение с неверной базой. Проверять это стоит именно здесь, а не после — иначе можно закрыть работающую гипотезу из-за ошибки в счёте.
Если все четыре пункта в порядке — люди увидели, воспользовались, ничего не сломано, счёт верен, — вывод один и он неприятный: гипотеза не подтвердилась. Дальше четыре законных исхода, и выбрать надо явно, а не по инерции.
Порядок проверки от дешёвого к дорогому: вывод о гипотезе законен только после первых четырёх шагов.
Исход 1: гипотеза неверна, проблема настоящая. Лишний шаг убрали, конверсия не выросла — значит, люди уходили не из-за шага. Проблема («покупатели не доходят до заказа») осталась, способ оказался не тем. Действие: вернуться к причинам, поговорить с теми, кто ушёл, и сделать другой срез. Это самый частый исход и самый полезный: вы вычеркнули одно объяснение из списка.
Исход 2: проблема оказалась не там, где думали. Хуже и ценнее: выясняется, что названная проблема была неверной. Покупатели доходят, но не платят, потому что не верят в срок доставки. Это возврат к формулировке проблемы, и все дальнейшие срезы вокруг старой гипотезы — потерянное время, если этого не заметить.
Исход 3: эффект есть, но он мал. Метрика сдвинулась на доли процента — то есть, скорее всего, не сдвинулась вовсе. Здесь важно не уговаривать себя: малое изменение при малом трафике неотличимо от нуля, и это тоже результат. Решение по такому исходу принимают по цене поддержки: функция, которая ничего не даёт, но требует внимания, — долг, а не приобретение.
Исход 4: продуктовое решение откатывают целиком. Не потому что сломалось, а потому что не нужно. Это самое трудное решение из четырёх, и его почти никогда не принимают, хотя часто следует. Признаки того, что пора: функция не двигает метрику, ею пользуются единицы, она усложняет интерфейс или код, и вы уже дважды тратили время на её поддержку.
Убрать функцию — нормальное действие. Про это стоит сказать прямо, потому что в продуктах почти не удаляют. Порядок такой же, как при выкате, только в обратную сторону: выключить флаг и подождать — если никто не заметил за две недели, это ответ; сообщить тем единицам, кто пользовался, и предложить замену; удалить код и данные, когда стало понятно, что возврата не будет. Выигрыш измерим: меньше кода, меньше поддержки, проще интерфейс, меньше контекста, который надо держать в голове и в правилах для агента.
И главное — записать. Одна строка: гипотеза, срез, число, вывод, решение. Через полгода это единственная защита от того, что та же идея вернётся как свежая. Неудачные гипотезы — самая ценная часть продуктовой памяти и та, которую не ведёт почти никто.
Что это значит на практике
Пять ошибок, которые ломают петлю:
- Число не зафиксировано до релиза. Смотришь на дашборды «вообще» — и даже хороший результат не засчитываешь, потому что не с чем сравнить. Правило: если не назвал порог и срок до выката, числа после выката ничего не докажут.
- Нет пути назад. Боишься выкатывать малыми шагами и копишь в большую пачку. Сломалось — не понять, что именно; откат — ручная работа на час. Фича-флаги и атомарные деплои решают это дешевле, чем кажется.
- Метрика выпуска вместо бизнес-метрики. «Выкатил — значит сделал», и уходишь на следующую задачу. Тем временем новая кнопка сбивает пользователей с пути, а ты этого не видишь.
- Слишком много аналитики. Двадцать событий, ни одно не привязано к решению. Разглядываешь графики — итерация стоит, а решение не принято.
- Смотришь слишком поздно. «Проверю через неделю» — за неделю проблема может накопить жалобы, а выгода — остаться невидимой за шумом. Через сколько смотреть, зависит от потока: там, где карточку открывают тысячи раз в день, направление видно за сутки-двое; там, где решение зреет неделями — крупная покупка, согласование в B2B, — за 48 часов не будет видно ничего, и срок берут по длине самого цикла. Срок, как и порог, называют до выката; если сигнала к нему нет — либо выборка мала, либо изменение нейтрально.
Глубже: эксперимент и статистика: выборка, контроль и значимостьрасширенное
Пример выше оперирует процентами: 1,2 % стало 1,7 % на десяти процентах трафика. Без нескольких понятий такие числа обманут, и это самая частая причина, по которой продукт-инженер «подтверждает» гипотезу, которой нет.
Контрольная группа. Сравнивать нужно не «до и после», а две группы в одно и то же время: часть пользователей видит новое, часть старое, распределение случайное. Иначе рост объясняется чем угодно: днём недели, рекламой, сезоном. Поэтапный выкат (сначала 10 %, потом всем) это не эксперимент: он защищает от поломки, но группы в нём различаются по времени, и сравнивать их нельзя.
Размер выборки. Разница между 1,2 % и 1,5 % на ста пользователях это шум: один человек меняет процент. Чтобы отличить изменение конверсии на четверть от случайности с обычной уверенностью, нужны десятки тысяч пользователей в каждой группе; грубое правило для доли около одного процента: делить шестнадцать на квадрат ожидаемой разницы в долях. Калькуляторы размера выборки считают это точно, и число смотрят до запуска: если трафика столько нет, эксперимент невозможен, и решают по качественным сигналам и косвенным метрикам.
Длительность и подглядывание. Эксперимент идёт полные недели, чтобы покрыть недельный цикл поведения, и до заранее назначенного числа пользователей. Смотреть каждый день и остановить «как только стало значимо» это способ найти значимость там, где её нет: при постоянном подглядывании ложный результат почти гарантирован. Эффект новизны отдельно: первую неделю новое нажимают из любопытства, и метрика падает обратно.
Что можно без статистики. Изменение в разы (обращений стало вдвое меньше) видно без формул; редкие события (крупная покупка раз в неделю) статистикой не проверяются на разумном сроке, и там смотрят на воронку до события и разговаривают с пользователями. Честная запись результата: «выросло на 0,3 пункта, статистически неотличимо от нуля при нашем трафике» это результат, а не провал.
Глубже: аналитика руками: событие, счётчик, когортарасширенное
«Несколько событий вокруг гипотезы» требует показать, как их завести и где смотреть, потому что для одиночки без аналитика это главный барьер между «выкатил» и «увидел».
Событие. Своя таблица product_event(occurred_at, user_id, name, props jsonb) в той же базе или отдельной, запись из кода в момент действия («отчёт выгружен», с размером и форматом в props), без персональных данных в свойствах и с идентификатором пользователя, а не почтой. Три события на гипотезу: начало, успех, отказ. Готовые сервисы аналитики (свой экземпляр PostHog, счётчики метрик) делают то же и рисуют графики, но требуют согласия пользователя на сбор, о чём статья про контакт с пользователем.
Счётчик. Для метрик вида «сколько в час» проще Micrometer: Counter с тегом исхода в том же Prometheus, где метрики сервиса, и панель рядом с ошибками; выкат виден на графике отметкой, и сдвиг метрики после него виден без запросов.
Когорта и воронка. Вопрос «стали ли новые пользователи выгружать чаще» это когорта: пользователи, пришедшие на неделе N, и их поведение в следующие недели; вопрос «где отваливаются» это воронка: доля дошедших от события к событию. Оба это один SQL по таблице событий с группировкой по неделе первого события, и агент пишет такие запросы хорошо по описанию.
Где смотреть. Один экран на гипотезу: решающее число, страховочная метрика, воронка, и отметка выката; собирают в той же панели, что метрики сервиса, а не в отдельной системе, о которой забудут. Смотрят по расписанию из раздела выше, а не «когда вспомнил».
Глубже: раскатка и откат среза: флаги, доля и план откатарасширенное
«Нет пути назад» в списке ошибок требует процедуры, тем более для среза, который собрал агент, и который вы приняли по контракту, а не написали сами.
Флаг по умолчанию выключен. Код среза едет в прод под флагом функции, выключенным; выкат и включение это разные события, о чём статьи про стратегии релиза. Так поломка при выкате отделена от поломки поведения, а откат поведения это выключение флага за секунды.
Постепенное включение. Внутренним пользователям, потом одному проценту, десяти, всем, с решающим числом и страховочной метрикой на каждом шаге и записанным критерием «откатываем, если». Для среза от агента добавляют один шаг: проверку, что за границей среза ничего не изменилось (метрики соседних сценариев не сдвинулись), потому что «заодно» это его типичная ошибка.
План отката до слияния. Что откатывается флагом, что образом, что не откатывается вовсе (миграция базы совместима с предыдущей версией, данные, записанные новым кодом, читаемы старым). Если план требует «руками поправить данные», срез не готов к выкату. План занимает пять строк в контракте среза, и его читают на приёмке.
Откат как штатная операция. Отключить флаг, убедиться по метрике, записать в задачу, что и почему, потом разбираться. Откат не отменяет гипотезу: он отменяет реализацию, и следующий шаг это либо исправление, либо признание, что гипотеза не подтвердилась, о чём статья про владение результатом.
Откат того, что назад не ходит
План отката выше держится на флаге и на образе, и оба откатываются за секунды. С изменениями в базе это не так: миграция назад не ходит — вернее, ходит не всегда и почти никогда бесплатно. Поэтому «откат одной командой за минуту» верен для кода и неверен для данных, и разницу надо знать до выката, а не после.
Что именно не откатывается:
| Изменение | Можно ли откатить | Что с этим делать |
|---|---|---|
| Добавили столбец с допустимым отсутствием значения | да, безопасно | обычный случай, ничего особенного |
| Добавили таблицу | да | старый код её не знает и не трогает |
| Удалили столбец | нет: данные исчезли | удалять только после того, как код перестал читать, и отдельным выкатом позже |
| Переименовали столбец | практически нет | делать как добавление плюс удаление, в два шага |
| Сузили тип или добавили ограничение | нет, если данные уже не проходят | сначала привести данные, потом ограничение |
| Изменили данные (пересчёт, нормализация) | нет | нужен отдельный обратный сценарий или снимок |
| Новый код записал данные в новом виде | назад не читается старым кодом | совместимость в обе стороны на время перехода |
Отсюда правило, которое и делает откат возможным: схема всегда совместима с предыдущей версией кода. Практически это означает разделение на шаги, где каждый выкат обратим:
- Добавить новое, не удаляя старого. Выкатить. Откат — просто откат кода.
- Писать в оба места, читать из старого. Выкатить. Откат — откат кода, данные согласованы.
- Переключить чтение на новое, продолжая писать в оба. Выкатить. Это точка возврата: откат — переключение чтения обратно.
- Перестать писать в старое. Выкатить.
- Удалить старое — отдельным изменением, через дни или недели, когда точно понятно, что возврата нет.
Важный пункт, который часто пропускают: удаление — это отдельный выкат, а не хвост предыдущего. Столбец, удалённый в том же изменении, где код перестал его читать, лишает вас возможности откатить код.
Что делать с уже изменёнными данными, если откатывать всё же пришлось:
- Снимок перед изменением данных. Не резервная копия всей базы, а копия затронутых записей в отдельную таблицу перед пересчётом. Стоит дёшево, и это единственный способ вернуть как было.
- Обратный сценарий написан заранее. Если пересчёт обратим арифметически, обратный сценарий пишут вместе с прямым и проверяют на копии. Если необратим — см. предыдущий пункт.
- Изменение данных — отдельно от изменения кода. Тогда сломавшийся код откатывается, не трогая данные, и наоборот.
- Изменение данных кусками с возможностью остановиться. Пересчёт миллиона записей одной операцией — это либо всё, либо ничего; кусками по тысяче с отметкой прогресса можно остановить на середине.
И честный вывод для плана отката в контракте. Строка «не откатывается» — законная и нужная: она означает, что этот шаг проверяют внимательнее, чем остальные, и что перед ним делают снимок. Хуже всего не необратимый шаг, а необратимый шаг, который считали обратимым.
Коротко
- Релиз обязан быть дешёвым и обратимым: маленькие выкаты, флаги, атомарный откат; метрика и порог названы до выката.
- Петля «выкатил, увидел, поправил» держится на одном решающем числе и заранее назначенном сроке взгляда.
- Сравнивают группы в одно время, а не «до и после»; выборку считают до запуска, полные недели без подглядывания; изменение в разы видно без статистики, малое при малом трафике неотличимо от нуля, и это результат.
- Аналитика руками: таблица событий без персональных данных, счётчики Micrometer, когорты и воронки одним SQL, один экран на гипотезу рядом с метриками сервиса.
- Срез едет под выключенным флагом, включается долями с критерием отката и проверкой соседних сценариев, план отката записан в контракте до слияния, откат это операция, а не событие.
- Прежде чем признать гипотезу неверной, проверяют четыре вещи: дошло ли изменение до людей, нашли ли его, не сломано ли оно тихо, верно ли считается число.
- Если всё в порядке, а число стоит, исходов четыре: гипотеза неверна при настоящей проблеме, проблема оказалась не там, эффект мал и потому равен нулю, продуктовое решение откатывают целиком.
- Убрать ненужную функцию — штатное действие: выключить флаг и подождать, предупредить единицы пользователей, удалить код и данные; выигрыш в меньшем объёме поддержки и контекста.
- Неудачную гипотезу записывают строкой — иначе через полгода та же идея вернётся как свежая.
- Откат за минуту верен для кода и неверен для данных: схему держат совместимой с предыдущей версией, удаление выносят в отдельный поздний выкат, перед изменением данных делают снимок затронутых записей, а пересчёт ведут кусками.
Что пощупать
Воронка покупки из четырёх шагов, где последний считается по настоящему статусу платежа, а не по нажатию кнопки, реализована в веб-клиенте практикума remodov/marketplace-system: события отмечает компонент магазина, отправка отделена интерфейсом, а расчёт конверсии не делит на ноль на шаге, до которого никто не дошёл. Сравнить числа «10 просмотров, 4 корзины, 2 оформления, 1 оплата» и найти самую неожиданную потерю можно прямо в тесте.
Сделаем сами
Ветка step-14-web-and-funnel — шаги воронки не отмечаются, конверсия не считается, три проверки красные, условие по ссылке в TASK.md.
Что почитать дальше
- Бизнес-метрика — что именно мерить и как выбрать одно решающее число до выката.
- Владение от идеи до пользователя — как устроено владение всем путём от проблемы до релиза.
- Агенты — если в петлю включён агент, принцип тот же: дешёвый выкат, одно число, быстрый откат.