В отчёте дневная конверсия регистрации в покупку занижена для ранних дат. Какой механизм создаёт ошибку в э...

В отчёте дневная конверсия регистрации в покупку занижена для ранних дат. Какой механизм создаёт ошибку в этом запросе?

WITH events(user_id, event_name, event_time, loaded_at) AS (
  VALUES
    (1, 'signup',   TIMESTAMP '2025-05-01 10:00', TIMESTAMP '2025-05-01 10:01'),
    (1, 'purchase', TIMESTAMP '2025-05-02 12:00', TIMESTAMP '2025-05-04 09:00'),
    (2, 'signup',   TIMESTAMP '2025-05-01 11:00', TIMESTAMP '2025-05-01 11:01'),
    (2, 'purchase', TIMESTAMP '2025-05-01 12:00', TIMESTAMP '2025-05-01 12:01')
)
SELECT s.loaded_at::date,
       COUNT(p.user_id) * 1.0 / COUNT(*) AS conversion
FROM events s
LEFT JOIN events p
  ON p.user_id = s.user_id
 AND p.event_name = 'purchase'
 AND p.loaded_at::date = s.loaded_at::date
WHERE s.event_name = 'signup'
GROUP BY s.loaded_at::date;
Проходите собеседования с ИИ помощником Hintsage

Краткий ответ

Ошибка возникает из-за смешения времени события и времени загрузки события в хранилище. Покупка пользователя 1 произошла 2 мая, но была загружена 4 мая, поэтому запрос не засчитывает её в конверсию регистрации 1 мая.

Когорту и окно конверсии нужно строить по event_time, а loaded_at использовать для контроля полноты данных и задержки доставки. Иначе поздние события искусственно занижают ранние даты и могут создавать ложную динамику продукта.

Исторический контекст

Событийная аналитика обычно собирает данные из мобильных приложений, веб-клиентов и внешних систем. Эти источники не гарантируют, что событие попадёт в хранилище сразу после фактического действия пользователя: возможны офлайн-режим, повторы отправки, очереди и задержки обработки.

Поэтому в аналитических системах различают event time — когда действие произошло, и processing time или время загрузки — когда запись стала доступна для анализа. Это разделение появилось как практический способ не путать поведение пользователя с работой конвейера данных.

Постановка проблемы

Если группировать регистрации и покупки по loaded_at, отчёт измеряет не пользовательскую конверсию, а совпадение дат поступления событий. Чем позже событие покупки доставлено, тем сильнее занижается конверсия регистрации в ранних периодах.

Такая ошибка особенно опасна для свежих дат, каналов с нестабильной доставкой событий и мобильных пользователей, которые долго находятся офлайн. Команда может ошибочно решить, что после релиза или рекламной кампании конверсия упала, хотя изменилось только время поступления данных.

Подробное решение

Сначала нужно определить аналитическую сущность: например, конверсию пользователей, зарегистрировавшихся в день D, в течение семи дней после регистрации. Регистрация задаёт когорту по event_time, а покупка засчитывается, если её event_time попадает в заданное окно.

WITH events(user_id, event_name, event_time) AS ( VALUES (1, 'signup', TIMESTAMP '2025-05-01 10:00'), (1, 'purchase', TIMESTAMP '2025-05-02 12:00'), (2, 'signup', TIMESTAMP '2025-05-01 11:00'), (2, 'purchase', TIMESTAMP '2025-05-01 12:00') ), signups AS ( SELECT user_id, event_time AS signup_time FROM events WHERE event_name = 'signup' ) SELECT COUNT(DISTINCT CASE WHEN p.user_id IS NOT NULL THEN s.user_id END) * 1.0 / COUNT(DISTINCT s.user_id) AS conversion FROM signups s LEFT JOIN events p ON p.user_id = s.user_id AND p.event_name = 'purchase' AND p.event_time >= s.signup_time AND p.event_time < s.signup_time + INTERVAL '7 days';

В реальном отчёте также нужно дедуплицировать события и явно определить, считается ли первая покупка, любая покупка или уникальный пользователь с хотя бы одной покупкой. Метрика должна иметь фиксированное окно атрибуции; сравнивать незрелую когорту с полностью созревшей некорректно.

loaded_at не следует игнорировать. По нему полезно строить контроль задержки доставки, долю поздних событий и дату готовности когорты. Например, данные за вчера могут быть технически неполными, поэтому отчёт либо публикуют после контрольного лага, либо помечают как предварительный и пересчитывают после дозагрузки.

Есть компромисс между оперативностью и стабильностью. Ожидание полной дозагрузки повышает точность, но задерживает отчёт; ранняя публикация ускоряет реакцию, но требует явной маркировки неполноты и последующих пересчётов.

Ситуация из практики

У сервиса доставки после запуска новой версии дневная конверсия регистрации в первый заказ снизилась с 18% до 14%. При проверке выяснилось, что пользователи новой версии чаще оформляли заказ при нестабильном соединении, а события покупки загружались с задержкой до двух дней.

Команда рассматривала два варианта. Первый — считать конверсию по дате загрузки: это позволяло быстро получать отчёт, но смешивало поведение пользователей с задержками доставки. Второй — считать когорты и заказы по времени события, а свежие когорты временно исключать до завершения семидневного окна; этот вариант был медленнее, зато сохранял смысл метрики.

Выбрали второй вариант и добавили отдельный мониторинг лага между event_time и loaded_at. После дозагрузки данных конверсия вернулась к прежнему уровню, а исходное падение оказалось артефактом неполных когорт, а не ухудшением продукта.

Что кандидаты часто упускают

  1. Достаточно ли просто заменить loaded_at на event_time?

Нет. Нужно также определить окно конверсии и проверить полноту данных. Если покупка может произойти в течение семи дней, когорту первого мая нельзя окончательно сравнивать с когортой пятнадцатого мая до завершения соответствующего окна.

  1. Что делать с событием, пришедшим задним числом после публикации отчёта?

Нужно поддержать пересчёт исторических периодов или корректировки. Практически применяют водяной знак, задержку публикации и статус зрелости данных; простое игнорирование позднего события оставляет систематическое занижение метрики.

  1. Может ли изменение задержки доставки само по себе быть продуктовой проблемой?

Да, если задержка вызвана изменением клиентского поведения или надёжности отправки событий после релиза. Поэтому нужно разделять два вывода: продуктовую метрику считать по времени события, а качество аналитического конвейера оценивать по задержке, потерям, дублям и доле поздних записей.