A/B в SQL: если вы собрали выборку так, статистика уже не спасёт тест.
Типичная картина: в дашборде “победа”, в продукте ничего не меняется, а потом выясняется, что выборка была собрана с красивой логикой… но с одним маленьким читом. В A/B самый дорогой баг часто не в методе, а в join’ах и фильтрах. Потому что вы подменяете вопрос “что изменил эксперимент” на “что осталось в выборке”.
Почему это ломается: assignment (кто в какой группе) живёт во времени, экспозиция живёт во времени, события живут во времени. SQL же легко превращает это в одну плоскую таблицу, где будущее подмешалось в прошлое, а отсутствие событий стало “нулём”.
5 сигналов, что выборка собрана неправильно:
-
Утечка будущего Вы фильтруете по событиям, которые могли случиться только после экспозиции, а потом считаете метрики “как будто честно”. Любой WHERE по post-event в таблице, из которой строится cohort, должен настораживать.
-
Фильтры “после факта” “Оставим только тех, кто открыл экран/дошёл до шага/сделал покупку” — и это применяется к обеим группам. Это уже не A/B, а сравнение выживших.
-
Неправильное окно экспозиции Экспозиция считается “когда угодно в периоде”, а метрики — “за весь период”. Или наоборот: пользователь попал в группу сегодня, а вы тянете его события за вчера.
-
“Выпавшие” события После join’ов внезапно пропадает часть пользователей или событий: inner join на редкую таблицу, фильтр по non-null, дедуп по неправильному ключу. На графиках тишина, в логах дырка.
-
Несогласованность join’ов Группу джойните по user_id, события по device_id, а конверсию по order_id. Или один и тот же пользователь размножается из-за many-to-many, и метрика раздувается.
Что проверять первым, до любых p-value:
- Единица анализа: одна строка на пользователя (или на сессию) там, где вы считаете метрику. Убедитесь, что не размножили сущность.
- Временные границы: assignment_time, first_exposure_time, start/end окна метрик. События до экспозиции не должны попадать в post-окно.
- Потери: анти-джойн от assignment к финальной таблице и обратно. Сколько “не доехало” и почему.
- Баланс: количество пользователей по группам до и после всех фильтров. Если баланс “исправился” фильтрами — это красный флаг.
- Дедуп: явное правило, какая запись assignment считается истинной (первая, последняя), и одинаковое правило везде.
Правильная практика выглядит скучно: сначала фиксируете когорту по assignment, потом фиксируете факт экспозиции, потом режете окна метрик, и только потом агрегируете. Спорят обычно про детали окна и дедупа, но если у вас есть утечка будущего или post-фильтры, спорить уже не о чем.