8 lines
18 KiB
JSON
8 lines
18 KiB
JSON
{
|
||
"index": 89,
|
||
"slug": "editorial-2025-07-mechanism-product-metrics",
|
||
"title": "Рост метрики не равен улучшению продукта: проверяем denominator и guardrail",
|
||
"excerpt": "Практический способ проверить продуктовую метрику до решения: зафиксировать событие, cohort, attribution, окно и denominator, затем сопоставить локальный сигнал с guardrail и остановить вывод при разрыве данных.",
|
||
"contentHtml": "<p>На дашборде treatment показывает conversion 52%, а control — 48%. Команда готовит выпуск. Через день выясняется, что в treatment считали уникальных открывших экран, а в control — все строки события. Ещё часть подтверждений пришла без связи с вариантом. Числа выглядят аккуратно, но сравнивают разные множества.</p>\n<p>Цена ошибки — не только неверный график. Команда может раскатить изменение, которого пользователь не заметил, потерять доверие к аналитике и потратить следующий спринт на поиск причины. Если рядом выросли отказы, задержка или отмены, локальный рост conversion скрывает ущерб.</p>\n<p>Тезис простой: метрика становится основанием для решения только вместе с контрактом измерения. Контракт называет субъектов, событие, cohort, attribution, период, numerator, denominator и guardrail. Любое нарушение контракта должно остановить product decision. Пустой или неполный результат не следует трактовать как нулевой эффект.</p>\n<h2>Механизм: дробь отвечает только на свой вопрос</h2>\n<p>Запись <code>52 / 100</code> ничего не говорит без описания ста субъектов и пятидесяти двух действий. В продуктовой метрике нужно сначала определить population, затем выбрать единицу счёта. Если один пользователь повторил событие три раза, число строк и число пользователей отвечают на разные вопросы.</p>\n<p>Учебный пример ниже считает conversion по уникальным субъектам. Numerator — субъекты с <code>checkout_confirmed</code>. Denominator — субъекты с <code>checkout_opened</code>. Обе группы ограничены одним cohort и одним днём. Guardrail считает <code>render_failed</code> среди открывших. Это фиксированные значения для иллюстрации. Они не описывают production и не доказывают эффект.</p>\n<pre><code>const events = [\n { event: 'checkout_opened', subject: 'u-1', cohort: 'control', period: '2025-07-14' },\n { event: 'checkout_confirmed', subject: 'u-1', cohort: 'control', period: '2025-07-14' },\n { event: 'checkout_opened', subject: 'u-2', cohort: 'control', period: '2025-07-14' },\n { event: 'render_failed', subject: 'u-2', cohort: 'control', period: '2025-07-14' },\n { event: 'checkout_opened', subject: 'u-3', cohort: 'treatment', period: '2025-07-14' },\n { event: 'checkout_confirmed', subject: 'u-3', cohort: 'treatment', period: '2025-07-14' },\n { event: 'checkout_opened', subject: 'u-4', cohort: 'treatment', period: '2025-07-14' },\n];\n\nconst unique = (name, cohort) => new Set(\n events.filter((x) => x.event === name && x.cohort === cohort)\n .map((x) => x.subject),\n).size;\n\nconst conversion = unique('checkout_confirmed', 'treatment')\n / unique('checkout_opened', 'treatment');\nconst guardrail = unique('render_failed', 'control')\n / unique('checkout_opened', 'control');\n\n// Учебный результат: treatment conversion = 0.5,\n// control guardrail = 0.5. Это не production-вывод.</code></pre>\n<p>В этом наборе treatment conversion равна 1 из 2, а control guardrail — 1 из 2. Эти дроби нужны, чтобы показать форму вычисления, а не чтобы объявить treatment лучше. В реальной системе дополнительно проверяют распределение вариантов, задержку доставки, повторные события, идентификаторы, окно наблюдения и статистическую неопределённость.</p>\n<p><code>attribution</code> связывает действие с вариантом. Например, подтверждение можно отнести к treatment, если у события есть тот же subject и request или сохранённый exposure key. Если связи нет, система не должна угадывать. Она возвращает <code>hold</code> и причину <code>missing-attribution</code>.</p>\n<figure><img src=\"/assets/editorial/2025/product-metrics-2025-metric-guardrail-matrix.svg\" alt=\"Матрица проверки продуктовой метрики: local signal, success metric и guardrail проходят через проверки cohort, периода, attribution и denominator; нарушение ведёт к остановке решения.\" loading=\"lazy\" /><figcaption>Метрика не заканчивается на delta. Сначала проверяется контракт данных, затем сопоставляются сигнал успеха и guardrail.</figcaption></figure>\n<h2>Симптомы требуют разных проверок</h2>\n<div class=\"table-scroll\"><table><caption>Симптом → причина → проверка → действие</caption><thead><tr><th scope=\"col\">Симптом</th><th scope=\"col\">Причина</th><th scope=\"col\">Проверка</th><th scope=\"col\">Действие</th></tr></thead><tbody><tr><td>Conversion выросла сразу после изменения tracking</td><td>Пропали открытия или изменился способ deduplication</td><td>Сравнить число субъектов, строк и долю доставки каждого события до и после изменения</td><td>Остановить интерпретацию; проверить instrumentation и denominator</td></tr><tr><td>Treatment и control имеют разные размеры</td><td>Нарушилось распределение вариантов или одна группа потеряла события</td><td>Сверить ожидаемое и наблюдаемое соотношение, exposure и data-quality metric</td><td>Не объявлять победителя; найти источник mismatch</td></tr><tr><td>Подтверждение не содержит cohort или request</td><td>Нет правила attribution</td><td>Проверить event schema и цепочку от exposure до outcome</td><td>Вернуть hold; не приписывать outcome варианту</td></tr><tr><td>Local metric растёт, но растут ошибки рендера</td><td>Выигрыш куплен ухудшением соседнего шага</td><td>Посчитать guardrail по той же population и тому же окну</td><td>Сверить порог с владельцем риска и остановить выпуск при нарушении</td></tr><tr><td>В одном отчёте смешаны два дня</td><td>Query собрал разные окна</td><td>Проверить period на каждой записи и границы окна</td><td>Пересобрать выборку; не усреднять разрыв молча</td></tr></tbody></table></div>\n<h2>Положительный и отрицательный путь</h2>\n<p>Положительный путь означает не «метрика хорошая». Он означает, что измерение прошло базовые проверки и может попасть к владельцу решения. Минимальный результат содержит definition, population, период, attribution, guardrail и список ограничений.</p>\n<pre><code>function inspect(report) {\n const reasons = [];\n\n if (!report.attribution) reasons.push('missing-attribution');\n if (report.denominator !== 'unique-opened-subjects') {\n reasons.push('wrong-denominator');\n }\n if (report.periods.length !== 1) reasons.push('mixed-period');\n if (report.guardrailRate > report.guardrailLimit) {\n reasons.push('guardrail-breached');\n }\n\n return reasons.length === 0\n ? { status: 'eligible-for-human-review', reasons: [] }\n : { status: 'hold', reasons };\n}\n\n// Код иллюстрирует stop conditions.\n// Он не читает telemetry и не принимает решение о выпуске.</code></pre>\n<p>В отрицательном пути нет попытки «починить» данные средним значением или подстановкой default cohort. Если период смешан, выборку пересобирают. Если denominator изменился, заново описывают метрику. Если нет attribution, чинят схему события или правила связи. Если guardrail превышен, владелец риска решает, допустимо ли продолжать. Функция не должна скрывать эти причины в <code>null</code> или в зелёном статусе.</p>\n<p>Такое разделение защищает от двух подмен. Первая — движение локальной метрики превращают в причинное объяснение. Вторая — техническую проверку превращают в автоматический ship. Инспектор может сказать «условия расчёта выполнены» или «расчёт остановлен». Он не может доказать, что изменение вызвало результат, если дизайн и данные этого не показывают.</p>\n<h2>Почему guardrail нужен рядом с успехом</h2>\n<p>Success metric отвечает на вопрос о желаемом результате. Local metric помогает понять ближайший шаг. Guardrail ограничивает цену улучшения. Например, форма может увеличить число подтверждений, но одновременно повысить ошибки рендера или отмены. Если guardrail появляется только после обсуждения успеха, команда уже выбрала удобную рамку.</p>\n<p>Guardrail должен иметь population, окно, единицу счёта и владельца порога. «Ошибок стало больше» недостаточно. Нужны доля, база и правило: например, <code>render_failed unique subjects / checkout_opened unique subjects</code> в том же cohort и периоде. Порог задают до интерпретации результата. Его не следует подбирать после того, как local metric уже выросла.</p>\n<p>Отдельная data-quality metric проверяет, можно ли доверять самой выборке. Она не является guardrail пользовательского опыта. Sample-ratio mismatch, потеря exposure или резкий провал доставки событий могут остановить анализ раньше, чем команда посмотрит conversion. Это отрицательный путь измерения, а не доказательство плохого продукта.</p>\n<h2>Порядок проверки перед решением</h2>\n<ol><li><strong>Назовите решение.</strong> Запишите, какое действие возможно: продолжить наблюдение, остановить rollout или передать данные владельцу.</li><li><strong>Опишите population.</strong> Укажите субъект, inclusion rule, cohort и период до расчёта.</li><li><strong>Разложите дробь.</strong> Напишите словами numerator и denominator. Проверьте deduplication и повторные события.</li><li><strong>Проверьте attribution.</strong> У каждого outcome должна быть воспроизводимая связь с exposure или вариантом.</li><li><strong>Посмотрите data quality.</strong> Сверьте доставку событий, expected ratio групп, пропуски и задержку.</li><li><strong>Положите рядом guardrail.</strong> Используйте совместимые population и окно. Заранее назовите порог и владельца.</li><li><strong>Прогоните отрицательный вход.</strong> Подайте mixed period, wrong denominator или missing attribution. Ожидаемый ответ — hold с конкретной причиной.</li><li><strong>Передайте человеку.</strong> Только после проверок владелец продукта оценивает риск, ограничения и дальнейший rollout.</li></ol>\n<h2>Ограничения</h2>\n<p>Контракт метрики не заменяет дизайн эксперимента. Он не доказывает случайное распределение, достаточную мощность, отсутствие сезонности или причинный эффект. Небольшой fixed набор в примере не моделирует реальный трафик. Имена <code>u-1</code> и <code>u-2</code> не являются советом хранить открытые идентификаторы пользователя.</p>\n<p>Событие с корректным именем всё равно может потеряться в клиенте, задержаться в очереди или попасть в другую систему времени. Поэтому проверка схемы должна сопровождаться проверкой доставки и задержки. Для финансовых, медицинских и других чувствительных сценариев нужны отдельные правила приватности, retention и доступа.</p>\n<p>Проверяемый критерий готовности такой: независимый инженер по записи может восстановить population, numerator, denominator, cohort, период, attribution и guardrail. На валидном наборе система возвращает <code>eligible-for-human-review</code>, а на каждом специально испорченном наборе — <code>hold</code> с причиной. Ни один путь не публикует результат и не запускает rollout автоматически. Если критерий не выполняется, сначала ремонтируют измерение.</p>\n<h2>Проверяемые источники</h2><ul><li><a href=\"https://opentelemetry.io/docs/specs/semconv/general/events/\" target=\"_blank\" rel=\"noopener noreferrer\">OpenTelemetry: Semantic conventions for events</a> — официальная спецификация описывает именованные события, timestamp и документированные attributes. Она поддерживает дисциплину event schema, но не задаёт product metric, denominator или порог guardrail.</li><li><a href=\"https://www.microsoft.com/en-us/research/publication/safe-velocity-a-practical-guide-to-software-deployment-at-scale-using-controlled-rollout/\" target=\"_blank\" rel=\"noopener noreferrer\">Microsoft Research: Safe Velocity</a> — первичная публикация о controlled rollout, exposed populations, длительности наблюдения и pass criteria. Она не подтверждает учебные числа и не заменяет дизайн конкретного эксперимента.</li><li><a href=\"https://www.microsoft.com/en-us/research/publication/a-dirty-dozen-twelve-common-metric-interpretation-pitfalls-in-online-controlled-experiments/\" target=\"_blank\" rel=\"noopener noreferrer\">Microsoft Research: A Dirty Dozen</a> — первичная работа о типичных ошибках интерпретации метрик в controlled experiments. Она обосновывает необходимость проверять состав наблюдений, но не доказывает результат этой статьи.</li></ul>"
|
||
}
|