Прямой ответ
A/B-тест — рандомизированный эксперимент: пользователи делятся на варианты (A — контроль, B — изменение), разница метрик приписывается изменению (рандомизация уравнивает группы — причинность, а не корреляция); для разработчика это инженерная система из четырёх частей. (1) Рандомизация консистентная: назначение варианта — детерминированная функция от стабильного ключа (hash(user_id + experiment_id) % 100 < 50 — один пользователь всегда в одном варианте — перескок между вариантами ломает и опыт, и статистику; соль experiment_id — независимость разных тестов; анонимы — по cookie/device_id с теми же свойствами). (2) Инфраструктура назначения — feature-flag с процентной логикой (свой раннер на хеше или сервис (Unleash/GrowthBook/Unleash-самохост — «флаги + эксперименты» — одна механика); назначение на бэкенде предпочтительнее клиента (консистентность/логирование/без «подбора варианта» в devtools); логирование факта экспозиции — событие «user X показан вариант B» — анализ по фактически увидевшим, не по назначенным — dilution-эффект реален). (3) Событийная аналитика: целевая метрика считается по событиям (структурированные логи/трекинг → хранилище — ClickHouse/DuckDB-разбор (аналитика)), и до старта фиксируется: целевая метрика, guardrail-метрики (конверсия не хуже в доходах/оттоке — «улучшили клики, уронили оплату»), размер выборки и длительность (power-анализ до, не «посмотрим через неделю» — подглядывание и ранние остановки — главный источник ложных побед). (4) Статистическая гигиена: значимость при достаточной выборке (z/t-тест пропорций — p < 0.05 как конвенция + доверительный интервал эффекта — «+0.3% [0.01…0.6]» против «+0.3% p=0.04» — интервал показывает практическую ценность), длительность — полные недели (сезонность будни/выходные), без «остановили, когда стало значимо» (multiple looks — инфляция ложных срабатываний — фиксированный горизонт или последовательные методы), SRM-проверка (sample ratio mismatch — фактическое соотношение 50/50 с точностью до теста — отклонение = баг рандомизации/логирования — результаты теста невалидны, чинить механику). Типовые ошибки инженера: «подглядывание» в метрики ежедневно с ранней остановкой, тесты на слишком малом трафике («вывод» на 300 пользователях — шум), несколько метрик без поправки (20 метрик — одна «значима» случайно), кеш/CDN, отдающие вариант A пользователям B (проверка доставки — часть теста).
Механика: рандомизация, экспозиция, флаги
Код назначения — маленькое ядро системы: детерминированный хеш (md5/murmur от f"{user_id}:{exp_id}:{salt}" — равномерность распределения проверить тестом на 100k синтетических id — тесты — «рандомер» с перекосом 55/45 ломает валидность молча); консистентность важнее «честного рандома» (тот же пользователь — тот же вариант навсегда — до конца теста; смена ключа (логин после анонима) — идентификационный мостинг или принятие разбиения — знать ограничение); процентная раскатка — gradual rollout как подмножество (флаги: 5% → 20% → 50% — механика та же, цель другая — безопасность против измерения); экспозиция — событие в момент фактического показа варианта (клиент увидел B — logged exposure — анализ «показанные B против показанные A»; назначен, но не дошёл (страница не открыта) — исключается — иначе эффект размывается); приоритеты/взаимоисключения — одновременные тесты на одном экране — слои экспериментов (ортогональные — независимые метрики; конфликтующие — взаимоисключающие очереди — «два теста меняют одну кнопку» — результат неинтерпретируем; планирование календаря тестов — продуктовая дисциплина, инженер обеспечивает isolation-механикой); конфигурация теста — данные (метаданные эксперимента: доли/даты/метрики — в БД/конфиг-сервисе, не в коде — старт/стоп без деплоя — конфиг-паттерн); killswitch — мгновенное выключение B при деградации guardrail (алерты на guardrail-метрики во время теста — «тест ломает оплату» — остановка за минуты, не за день).
Статистика без магии: что обязан знать инженер
Минимум, спасающий от ложных выводов: размер выборки — до старта считается (power-анализ: baseline-конверсия, минимально интересный эффект MDE, α=0.05, power=80% — калькулятор/scipy — «конверсия 2%, хотим поймать +0.2% п.п.» — десятки тысяч на группу — при 500 пользователей в день — тест на месяцы или MDE крупнее — честный выбор до старта); p-value правильно — вероятность данных при отсутствии эффекта (не «вероятность, что B лучше» — частая подмена; p<0.05 — конвенция порога, не закон природы); доверительный интервал — диапазон правдоподобных значений эффекта (нуль внутри — эффект не отличим от шума независимо от p; весь интервал выше нуля и практически значим — победа; «+0.01% [0.001…0.02]» — статзначимо, практически ничтожно — интервал показывает); подглядывание — проверка значимости каждый день с остановкой «когда зелёное» — многократные проверки раздувают долю ложных срабатываний до 20–30% (фиксированный горизонт — решение в назначенную дату; или sequential-методы (always-valid p/mSPRT) — легальный «смотреть по ходу» — библиотеки существуют); множественные сравнения — 20 метрик одновременно — одна «выстрелит» случайно (главная целевая метрика — одна, объявлена до; остальные — exploratory, без громких заявлений); SRM — соотношение выборок проверяется хи-квадратом (ожидали 50/50 — получили 51.4/48.6 на 100k — p<0.001 — рандомизация/логирование сломаны — результаты в мусор — самый дешёвый sanity-check, пропускаемый чаще всего); сегменты — «в целом нейтрально, но в мобильном +2%» — гипотеза для следующего теста, не вывод этого (сегментный анализ постфактум — генератор ложных открытий). Инструменты: scipy (ttest/z-тесты — научный стек рядом), statsmodels (power-анализ/поправки), готовые платформы (GrowthBook/Eppo — статистика из коробки — self-hosted варианты — флаг-экосистема); расчёт-однострочник до теста — обязательный ритуал («тест на 300 юзерах» не начинается вовсе).
Практический сценарий: первый честный A/B в продукте
Исходник: SaaS-продукт, 40k MAU; «перекрасили кнопку и поставили выше» прошлый раз «по метрикам лучше» (смотрели 3 дня, 900 пользователей — шум); задача — система экспериментов. Построение: (1) раннер — сервис-флагов с процентной логикой (hash-based, соль на эксперимент — самописный модуль 80 строк + конфиг в БД — GrowthBook оценили и отложили: инфраструктурно не доросли, механика простая); (2) экспозиции — middleware пишет событие exposure(user_id, exp_id, variant) в поток → ClickHouse (событийное хранилище); продуктовые события (signup/активация/оплата) — туда же; (3) аналитика-шаблон — DuckDB-ноутбук (аналитика): SRM-проверка, конверсии по вариантам с CI, guardrail-панель в Grafana (дашборд на время теста); (4) процесс — карточка эксперимента: гипотеза («форма в 2 шага повысит завершение регистрации с 31% до ≥34%»), метрики (primary — завершение регистрации; guardrail — время до первой оплаты, support-тикеты), MDE 3 п.п., power-расчёт — 8 700 на группу → при 700 регистраций/день — 3.5 недели — длительность 4 недели (целые); (5) первый тест прогнан честно: SRM чист (50.2/49.8, p=0.71), к 28-му дню — 33.1% против 31.0% — разница +2.1 п.п., CI [−0.6…+4.8] — не значимо (MDE не пойман — эффект меньше заявленного или отсутствует); решение — не раскатывать (прошлый «показалось лучше» закончился бы раскаткой шума); второй тест (упрощение первого шага) — +4.2 п.п. [1.8…6.6] — значимо и практически ценно — раскатано на 100% через флаг с мониторингом guardrail неделю. Итоги квартала системы: 6 экспериментов — 2 победы, 3 нейтрально (идеи закрыты данными вместо месяцев «внедрим и посмотрим»), 1 поражение с быстрым откатом (guardrail-алерт сработал на 2-й день — поддержка захлебнулась — выключено за 10 минут — killswitch окупился сразу); культура сдвинулась: «а что показал тест?» вместо «мне кажется, стало лучше» — главная нематериальная победа (data-культура — решения документируются с цифрами).
FAQ
Нужен ли A/B при малом трафике (сотни пользователей)? Классический A/B — нет (мощности нет — месяцы на один тест — решения устареют); альтернативы малых объёмов: switchback-тесты (варианты чередуются во времени на всей аудитории — «неделя A, неделя B» — сезонность контролируется парами периодов), юзабилити-наблюдения/интервью (качественные данные вместо количественных), постепенная раскатка с guardrail-метриками (не «доказали лучше», но «не сломали» — безопасность без статистики), накопительные эксперименты (несколько изменений в одном направлении — суммарный эффект ловится легче); «провели A/B на 300 юзерах и решили» — худший вариант (иллюзия строгости хуже честного «решили качественно»).
A/B-тест против feature flag — в чём разница? Механика одна (условный показ функциональности), цели разные: флаг — доставка/безопасность (раскатка, killswitch, перс-включения — флаги), эксперимент — измерение причинного эффекта (рандомизация + статистика + гипотеза); «раскатали на 50% через флаг и сравнили метрики» — почти A/B (если назначение хеш-детерминировано и экспозиции логируются) — недостающее — power-расчёт/гигиена выводов; платформы флагов с A/B-модулем (GrowthBook/Unleash) — обе цели одной системой — зрелый выбор.
Как тестировать изменения для новых пользователей против всех? Сегментная рандомизация: эксперимент только на когорте (новые регистрации — ключ рандомизации user_id, фильтр входа — «первая сессия» — экспозиция фиксируется на входе в когорту); нюансы: когортные эффекты (новые ≠ старые — результаты не переносятся автоматически), накопление выборки медленнее (поток новых ограничен — длительность растёт), «тест на новых, раскатка на всех» — экстраполяция — отдельное решение (часто разумное, но заявляемое явно); технически — условие участия в раннере + отдельный счётчик метрик когорты — аналитика та же.
Что делать с результатами, которые не воспроизводятся? Невоспроизведение — норма для слабых эффектов (первый тест «поймал» шум/сегментный артефакт — повторный тест честнее исходного); процесс: повторить тест (тот же MDE/длительность — «репликация» — результат воспроизвёлся — верить; нет — эффект считать нулевым); ретроспектива первого: подглядывание? SRM? множественные метрики? (обычно находится); если «победное» изменение уже раскатано — откат и чистый повторный тест (политика «раскатка только после значимого результата на полной длительности» предотвращает такие ситуации — процесс-дисциплина — чек-лист эксперимента в карточке); системный вывод: единичный тест — слабое доказательство, серия репликаций — сильное — для критичных решений (редизайн чекаута) — два независимых подтверждения.
Источники: A/B testing (обзор методологии), документация scipy.stats, featureflags.io — ссылки в шапке.