В продуктовой воронке один пользователь может отправить событие шага несколько раз. Какую ошибку в конверсии создаёт этот расчёт?
WITH events(user_id, step) AS (
VALUES
(1, 'open'), (1, 'checkout'), (1, 'checkout'),
(2, 'open'), (2, 'checkout'),
(3, 'open')
)
SELECT step,
COUNT(*) * 1.0 /
(SELECT COUNT(*) FROM events WHERE step = 'open') AS conversion
FROM events
GROUP BY step;
Расчёт считает события, а не пользователей. Повторная отправка одного шага одним пользователем увеличивает числитель несколько раз, поэтому конверсия может быть завышена и даже превысить 100%.
Для пользовательской воронки нужно сначала дедуплицировать события на уровне user_id и шага, а затем считать COUNT(DISTINCT user_id). Иначе повторы, ретраи или возврат пользователя к экрану будут ошибочно интерпретироваться как новые пользователи.
Первые продуктовые отчёты часто строились непосредственно по журналам событий: просмотр строки события означал действие пользователя, а агрегирование таких строк казалось простым способом получить объём активности. Такой подход хорошо подходит для подсчёта действий, но не для ответа на вопрос, сколько пользователей достигли шага.
По мере роста числа повторных действий, мобильных ретраев и многократных посещений понадобилась идентификация пользователя в аналитике. Разделение метрик событий и метрик пользователей стало базовым принципом воронок, retention-отчётов и продуктовых экспериментов.
В примере пользователь с user_id = 1 дважды отправил событие checkout. Запрос посчитает три события checkout, хотя этот шаг достигли только два пользователя.
Числитель равен 3, знаменатель — числу событий open, то есть 3. В этом конкретном наборе получится 100%, хотя корректная пользовательская конверсия составляет 2 / 3, или примерно 66,7%.
Такая ошибка искажает оценку шага воронки. Команда может ошибочно решить, что проблема решена, изменить приоритеты разработки или сравнить варианты продукта по некорректной метрике.
Сначала следует определить единицу анализа. Если вопрос звучит как «какая доля пользователей достигла шага», единицей является пользователь, а не событие.
Минимальный корректный вариант выглядит так:
DISTINCT оставляет одну запись на пару пользователь–шаг. После этого checkout учитывается один раз для пользователя с идентификатором 1, и конверсия отражает достижение шага пользователями.
Важно заранее определить знаменатель. Если нужна конверсия от начала воронки, для каждого шага используется число пользователей, достигших первого шага. Если нужна пошаговая конверсия, знаменателем служит число пользователей на предыдущем шаге. Это разные метрики, и смешивать их нельзя.
Дедупликация не должна бездумно удалять все повторные события в любых отчётах. Для метрики числа попыток, повторных заказов или частоты использования повторы могут быть содержательно важны. Поэтому сначала фиксируют семантику метрики, затем выбирают уровень агрегации.
Также необходимо учитывать окно анализа и порядок событий. Пользователь должен достичь шага в заданный период и после предыдущего шага, иначе воронка может приписать конверсию событию, произошедшему раньше входа в неё.
Команда обнаружила, что конверсия из открытия формы в отправку заявки выросла с 42% до 78% после обновления мобильного приложения. При проверке выяснилось, что приложение повторно отправляло событие submit при сетевом тайм-ауте, хотя заявка создавалась только один раз.
Рассматривались два варианта. Первый — фильтровать повторы по времени, например считать только одно событие за минуту. Это быстро, но интервал является эвристикой: он может удалить реальные повторные попытки или сохранить дубликаты после более долгого сбоя.
Второй — считать пользователей, достигших шага, и отдельно контролировать идентификатор заявки. Этот вариант лучше отражает пользовательскую конверсию, но требует корректных идентификаторов и дополнительной проверки качества данных.
Выбрали второй вариант: основную воронку перевели на уникальные пары user_id–step, а количество отправленных заявок стали считать по уникальному application_id. В результате показатель вернулся к сопоставимому уровню, а технические дубликаты начали отслеживаться отдельным мониторингом событий.
COUNT(DISTINCT user_id) без проверки времени события?Нет. Уникальность пользователя устраняет дубли, но не гарантирует корректную последовательность. Если пользователь достиг checkout до open в выбранном наборе данных или событие пришло с неправильной временной меткой, такой пользователь всё равно может попасть в числитель. Нужно проверять порядок шагов и временное окно.
Технически можно получить число уникальных идентификаторов, но это будет не число людей, а число наблюдаемых профилей. Разрыв идентичности приводит к занижению конверсии и некорректному сравнению платформ. Если бизнес-вопрос относится к пользователю, нужна согласованная стратегия склейки идентификаторов и одинаковое правило её применения к группам сравнения.
Потому что метрики имеют разные единицы наблюдения. Для воронки обычно важен факт достижения шага пользователем, для заказов — число уникальных заказов, а для вовлечённости — частота действий или число сессий. Универсальное удаление повторов может исправить одну метрику и одновременно исказить другую; уровень агрегации должен следовать из формулировки бизнес-вопроса.