{ "index": 88, "slug": "editorial-2025-07-field-product-metrics", "title": "Когда рост conversion не означает улучшение продукта", "excerpt": "Conversion имеет смысл только вместе с определением событий, cohort, периодом, denominator и guardrail. Разбираем механизм ошибки, отрицательные пути и проверяемый критерий готовности.", "contentHtml": "

На дашборде растёт conversion, а число ошибок рендера растёт вместе с ним. Пользователь открывает форму повторно, но повторное открытие уже не попадает в denominator. Команда видит красивую дробь и оставляет новый вариант. Через день выясняется, что сравнивались разные множества событий. Цена ошибки — неверное решение, повторный сбор данных и часы спора о том, что именно измерила система.

\n

Тезис простой: метрика становится основанием для решения только вместе с условиями её получения. Нужно связать изменение, событие, cohort, period, numerator, denominator и guardrail. Если связь не доказана, расчёт должен остановиться с понятной причиной. Неполный результат лучше честного на вид числа, которое отвечает на другой вопрос.

\n

Механизм ошибки

\n

Conversion — это отношение numerator к denominator. Например, numerator может считать уникальных субъектов с событием checkout_confirmed, а denominator — уникальных субъектов с событием checkout_opened. Дробь отвечает на вопрос «какая доля открывших подтвердила действие» только при одинаковых правилах отбора.

\n

Cohort задаёт сравниваемую группу: control или treatment. Period задаёт единое окно времени. Attribution связывает подтверждение с конкретным открытием и вариантом. Guardrail показывает ущерб, который не должен расти ради локального улучшения. Если один элемент выпадает, значение conversion меняет смысл.

\n

Предзагрузка формы может сократить ожидание. Она не доказывает, что пользователь чаще завершает действие. Для такого вывода нужны как минимум открытие, подтверждение и правило их связи. Отдельно нужно считать сбой рендера, отмену или другой риск, который может скрыться за ростом локальной метрики.

\n

Контракт события

\n

Событие должно иметь стабильное имя и отдельные атрибуты. Динамические значения нельзя зашивать в имя: запрос не сможет надёжно сгруппировать такие записи. OpenTelemetry формулирует то же правило для semantic conventions: имя события должно однозначно описывать структуру, а переменные значения должны жить в attributes.

\n
event: product.checkout_opened; subject: subject-a; cohort: treatment; period: 2025-07-14; requestId: request-2;
\n

Вызов подтверждения должен содержать совместимые поля: event, subject, cohort, period и requestId. Тогда запрос может проверить, что открытие и подтверждение относятся к одному субъекту, одной попытке и одному окну. Если requestId отсутствует, запрос не должен угадывать связь.

\n

Значения в примере фиксированы и нужны только для объяснения механизма. Они не задают политику идентификации. В реальной системе отдельно определяют допустимый идентификатор, срок хранения, доступ к данным и правила обработки задержанных событий. Нельзя переносить строку subject-a в действующий сбор без такой проверки.

\n

Конкретный расчёт

\n

Возьмём малый набор событий. В control два открытия и одно подтверждение. В treatment два открытия и одно подтверждение. В control один экран завершился событием product.render_failed. Расчёт считает уникальных субъектов внутри cohort и period.

\n
const events = [\n  { event: 'product.checkout_opened', subject: 'a', cohort: 'control', period: '2025-07-14', requestId: 'r-1' },\\n  { event: 'product.checkout_confirmed', subject: 'a', cohort: 'control', period: '2025-07-14', requestId: 'r-1' },\\n  { event: 'product.checkout_opened', subject: 'b', cohort: 'treatment', period: '2025-07-14', requestId: 'r-2' },\\n  { event: 'product.checkout_confirmed', subject: 'b', cohort: 'treatment', period: '2025-07-14', requestId: 'r-2' },\\n  { event: 'product.checkout_opened', subject: 'c', cohort: 'treatment', period: '2025-07-14', requestId: 'r-3' },\\n  { event: 'product.checkout_opened', subject: 'd', cohort: 'control', period: '2025-07-14', requestId: 'r-4' },\\n  { event: 'product.render_failed', subject: 'd', cohort: 'control', period: '2025-07-14', requestId: 'r-4' }\n];
\n

В treatment conversion равна 1/2. В control conversion тоже равна 1/2. Для control guardrail равен 1/2. Эти значения показывают только работу дроби на фиксированных данных. Они не оценивают эффект, статистическую значимость или поведение пользователей.

\n

Смысл примера раскрывается на ошибочном пути. Если подтверждение приходит без requestId, его нельзя приписать открытию. Если одно событие имеет следующий period, его нельзя молча объединить с предыдущим. Если denominator заменили на all-events, старое имя conversion больше не описывает новый расчёт.

\n

Симптом → причина → проверка → действие

\n
Проверки перед интерпретацией метрики
СимптомПричинаПроверкаДействие
Conversion выросла после изменения запросаИзменился denominator или deduplicationВывести numerator и denominator по cohortОстановить сравнение и исправить правило inclusion
Подтверждение есть, вариант не определёнНет attribution или requestIdПроверить subject, requestId и cohortВернуть ошибку владельцу событий
Группы имеют разные датыСмешан period или пришли задержанные событияПроверить period каждой записи до агрегацииРазделить окна или собрать данные заново
Local metric растёт вместе с ошибкамиGuardrail считает другую populationПосчитать failure на том же срезеНе считать локальный рост улучшением
Расчёт нельзя повторитьDefinition хранится только в сообщенияхВосстановить запрос по полям метрикиЗафиксировать definition, owner и limitation
\n

Таблица задаёт маршрут диагностики. Она не заменяет проверку данных. Каждый ответ должен вести к действию: пересчитать дробь, исправить событие, разделить period, пересмотреть guardrail или остановить решение.

\n

Иллюстрация цепочки доказательств

\n
\"Цепочка
Схема показывает, какие условия нужно проверить до решения. Финальная остановка сохраняет причину, которую можно исправить.
\n

Человек находится в конце цепочки не случайно. Код может проверить обязательные поля, период, denominator и известные отрицательные пути. Он не может по одной дроби выбрать допустимый продуктовый риск. Локальный рост может сопровождаться отказами, отменами или недоступностью. Guardrail делает такую цену видимой, но не выбирает порог вместо владельца.

\n

Отрицательный путь в коде

\n

Проверяющая функция должна отклонять неполный контракт. Ниже приведён ограниченный пример с фиксированными данными в памяти. Он не читает сеть или файл и не отправляет события.

\n
const report = inspectProductMetric(\\n  createProductMetricInput('mixed-period-example'),\\n);\\n\\nconst decision = prepareProductDecision(report);\\n\\nconsole.log(decision);\\n// {\\n//   status: 'stop-before-decision',\\n//   reasons: ['mixed-cohort-or-period']\\n// }
\n

Смешанный period не превращается в null и не замазывается средним. Проверка возвращает короткую причину. Она направляет действие: владелец событий проверяет instrumentation, владелец запроса проверяет population, владелец варианта проверяет cohort и окно.

\n

То же правило действует для неверного denominator и отсутствующего attribution. Ветка wrong-denominator не чинит запрос автоматически и не разрешает сохранить старое имя метрики. Ветка missing-attribution-rule не угадывает связь между открытием и подтверждением. Такая строгость защищает от убедительного числа, собранного из несвязанных фактов.

\n

Порядок действий

\n
  1. Назвать решение. Записать, что можно оставить, остановить или повторить. Не начинать с самого удобного графика.
  2. Описать цепочку. Связать изменение с событием, действием, outcome и возможным ущербом. Отделить гипотезу от наблюдаемого факта.
  3. Зафиксировать event contract. Записать имена событий, обязательные поля, deduplication, attribution, cohort и period.
  4. Разложить ratio. Показать numerator и denominator отдельно для каждой группы. Проверить одно правило inclusion.
  5. Положить рядом guardrail. Использовать тот же срез или явно назвать отличие. Указать владельца порога и error path.
  6. Проверить отрицательные входы. Подать missing attribution, mixed period и wrong denominator. Каждый случай должен вернуть остановку с причиной.
  7. Сохранить доказательства. Оставить definition, запрос, источник данных, limitation, owner и дату следующей проверки. Если другой инженер не восстановит расчёт, вернуться к шагу 3.
\n

Ограничения

\n

Контракт событий не доказывает причинность. Он не заменяет randomization, расчёт мощности, проверку задержки доставки, privacy, retention и качества identity. Guardrail не делает эксперимент безопасным автоматически. Он заранее называет ущерб, который нельзя скрыть локальным ростом.

\n

Малый набор не говорит о размере эффекта. В нём нет реальных пользователей, денег, сети, clock, действующего запроса или инцидента. Его можно использовать для детерминированной проверки веток и отсутствия побочных действий. Нельзя выдавать его значения за наблюдение действующего продукта.

\n

Корректный контракт тоже может устареть после изменения клиента, схемы или pipeline. Проверяйте смысл события рядом с кодом отправки и запросом. Если событие меняет смысл, меняйте definition и отмечайте несовместимость. Молчаливое сохранение старого названия опаснее явной остановки.

\n

Проверяемый критерий готовности

\n

Измерение готово к продуктовому решению, если другой инженер по одной карточке восстанавливает population, numerator, denominator, attribution, period и guardrail. Он видит входящие события, условие остановки и владельца каждой ошибки. Если нужен устный контекст, контракт измерения ещё не готов.

\n

Минимальный проверяемый результат таков: корректный фиксированный пример получает статус needs-human-decision; missing attribution, mixed period и wrong denominator получают stop-before-decision с разными причинами; ни один вызов не отправляет данные и не меняет состояние системы. Для действующего проекта добавьте отдельные проверки схемы, задержки, прав доступа и повторяемости запроса.

\n

Проверяемые источники

" }