Контекст: SaaS-инструмент и 2.4M юзеров
Продукт — SaaS для команд (что-то между Notion и Figma). Подписки трёх уровней: Free, Pro ($12/мес), Team ($24/мес за seat). Команда роста копает в направлении «как лучше монетизировать Free-юзеров»: их 2 млн, доля апгрейдов в Pro — 0.22% в неделю.
Гипотеза разумная: апгрейд действительно был на третьем экране настроек. Решили выкатить заметную плашку прямо в шапке — кнопку Upgrade your workspace →, ведущую на страницу /plans, где можно выбрать тариф.
Что и как мерили
Сплит — стандартный 50/50 на платформенном уровне (по user_id, иначе говоря — на всех юзерах в системе, не только Free). Длительность — 14 дней, пилот первые 3 дня на 5% трафика. MDE по целевой — относительно скромные 5%.
Upgrade и Free→Pro conversion за 14 днейuser_id, по 1.2M юзеров в каждой ячейкеПромежуточный замер: всё хорошо
Через неделю смотрят промежуточный дашборд. Целевая радует:
Команда роста в чате пишет «пилотный замер красивый, идём до конца теста и потом раскатываем». Никто не спорит, продакт ставит лайк. Эксперимент продолжается ещё 7 дней.
Финальный замер: что-то не так
Финальный отчёт перед раскаткой. Аналитик готовит апдейт и решает заодно «прихватить» revenue per user — не как формальный guard, а просто чтобы было красивее в презентации. И там:
Картина странная: целевая выросла, побочка на guard-метриках Free — норма, а общий revenue per user (через все сегменты в ячейке) упал. И статсиг.
Первая реакция команды: «наверное, статистический шум, давайте раскатим, на полной выборке выровняется». Аналитик берёт паузу — на пятницу вечером ничего не катить, разбираемся в понедельник.
Понедельник: четыре гипотезы
В понедельник аналитик раскладывает диагностику. Алгоритм — стандартный для «метрика упала»: от дешёвых проверок к дорогим. Подробно про подход — в статье «Декомпозиция метрики».
| # | Гипотеза | Что проверял | Вердикт |
|---|---|---|---|
| H1 | Шум данных / sample ratio mismatch | Доли control/treatment по плану (Free/Pro/Team) — 49.97% / 50.03%, как и должно быть | не он |
| H2 | Возвраты и refunds выросли в treatment | Refund-таблица: control 0.21%, treatment 0.20% — нет связи с тестом | не он |
| H3 | Изменения цен / промо в период теста | Никаких прайсинговых изменений за 14 дней не было — ни глобально, ни по сегментам | не он |
| H4 | Сегмент Pro/Team в treatment вёл себя по-другому | Срез revenue по плану: Free одинаково, Pro −4.8%, Team −1.2% | в нём дело |
Стоп. Сегмент Pro в treatment просел на 4.8%. Но эксперимент задумывался про Free-юзеров. Pro вообще не должны были «увидеть» тест.
Аналитик идёт в код экспериментальной системы и обнаруживает простое: плашка Upgrade your workspace рисовалась через layout-компонент, и условие показа было только variant === 'treatment'. Без проверки текущего тарифа. То есть Pro-юзеры в treatment-ячейке тоже видели плашку «Upgrade your workspace», и часть из них кликала — кто из любопытства, кто думая что это про upgrade в Team.
Куда вёл клик? На страницу /plans, где честно отрисовывались все три тарифа — Free, Pro, Team — с кнопкой «Switch to this plan» под каждым. Дальше — следующий SQL.
SQL, который раскрыл картину
Аналитик собирает воронку «по тарифу на момент захода» × «куда переключился по итогу 14 дней» в обеих ячейках:
WITH entry AS ( -- тариф на старте теста SELECT user_id, plan_at_start, ab_group FROM ab_assignments WHERE experiment = 'upgrade_banner_v1' ), final_plan AS ( -- тариф на 14-й день SELECT user_id, plan AS plan_at_end FROM subscriptions_snapshot WHERE snapshot_date = '2026-04-28' ) SELECT e.ab_group, e.plan_at_start, f.plan_at_end, COUNT(*) AS users, ROUND(COUNT(*) * 100.0 / SUM(COUNT(*)) OVER ( PARTITION BY e.ab_group, e.plan_at_start ), 2) AS pct FROM entry e JOIN final_plan f USING (user_id) GROUP BY e.ab_group, e.plan_at_start, f.plan_at_end ORDER BY e.ab_group, e.plan_at_start, f.plan_at_end;
Что видим в результатах:
| Стартовый план | Куда ушёл | Control | Treatment |
|---|---|---|---|
| Free | → Pro | 0.22% | 0.247% (+12%) |
| Pro | остался Pro | 99.5% | 96.7% |
| Pro | → Team (upgrade) | 0.3% | 0.4% |
| Pro | → Free (downgrade) | 0.2% | 2.9% (×14) |
Upgrade your workspace, попадали на /plans — и впервые за всё время видели Free-план «в явном виде» с понятной кнопкой «Switch». Часть из тех, кто давно сомневался в подписке, нажимала «а зачем я плачу» и переходила на Free. То, что задумывалось как фича роста для Free, превратилось в downgrade-канал для Pro.Считаем удар по выручке. В каждой ячейке 200К Pro-юзеров. Лишних downgrade в treatment: (2.9% − 0.2%) × 200K ≈ 5400 юзеров. Каждый — это −$12/мес повторяющегося дохода. Месячная потеря: ≈ $65 000. Это и тащит revenue per user вниз на 2.9%, потому что по абсолютным деньгам Pro весит во много раз больше, чем мелкий Free→Pro прирост.
Что починили и что добавили в процесс
В понедельник вечером эксперимент откатывают. Команда садится решать в три приёма: что сделать с самой фичей, что — с этим конкретным A/B, и что — на уровне процесса.
1. Сама фича
Очевидное: добавить условие plan === 'free' на серверной стороне. Плашка показывается только Free-юзерам. Через 3 дня перезапуск с этим гейтом — целевая по-прежнему растёт (+8.4% к CTR, +11.7% к Free→Pro), а revenue per user в треатменте теперь нулевой к контролю. Раскатка одобрена.
2. Этот конкретный A/B
Перепроверили все недавние тесты этой команды на ту же ошибку: плашки и баннеры в layout-уровне без сегментного гейта. Нашли ещё два — оба не давали такого катастрофического эффекта, но в одном тоже была лёгкая каннибализация (∼0.5% к downgrade). Оба переписали.
3. Процесс
Главный артефакт после кейса — обновлённый чек-лист дизайна A/B. До этого guard-метрики формулировались как «что не должно сломаться у целевого сегмента». После кейса — добавили обязательный пункт.
Что стоит унести из этого кейса
Кейс на собесе обычно сводится к одной фразе: «эффект на воронке ≠ эффект на бизнесе». Это правда, но мне нравится более операционная формулировка — её можно сразу применить.
Связанные материалы
Главное про каннибализацию в A/B
Каннибализация — самая дорогая ошибка в продуктовых экспериментах: целевая метрика выглядит зелёной, фичу раскатывают, и только через месяц на квартальном ревью видят «странность» в общей выручке. Кейс выше — синтетический, но я видел почти такую же историю в реальных командах не один раз. Сценарий повторяется.
Защита — простая и почти бесплатная: обязательный набор guard-метрик на соседних сегментах и на «симметричном» обратном действии, плюс глобальный revenue per user в каждом тесте про монетизацию. Это никак не замедляет эксперимент и ловит почти всё, что может пойти не так в этой плоскости.
Следующий шаг: возьми последний A/B, который у тебя в работе, и проверь — есть ли в нём guard-метрика на сегменте, который вы «не имели в виду»? Если нет — добавь. Стоит 10 минут, спасает квартальное OKR.