
Вчера в отчёте было 12 480, сегодня 12 517: пять причин, по которым цифры за прошлый месяц продолжают меняться
badcasedaily1 только что Вчера в отчёте было 12 480, сегодня 12 517: пять причин, по которым цифры за прошлый месяц продолжают меняться Средний 8 мин 14 Блог компании OTUS Анализ и проектирование систем *...
18 Temmuz'da Wellington'da en yüksek sıcaklık 14°C olacak mı?
Значимый прорыв формирует отрасль ИИ: badcasedaily1 только что Вчера в отчёте было 12 480, сегодня 12 517: пять причин, по которым цифры за прошлый месяц продолжают меняться Средний 8 мин 14 Блог компании OTUS Анализ и проектирование систем * Программирование * Python * SQL * Туториал Привет, Хабр! Есть особый жанр сообщений в рабочем чате: «А почему в отчёте за март другая выручка, чем на прошлой неделе? Отвечать на это неприятно, потому что ответ звучит как «Не знаю, сейчас разберусь», а разбираться приходится по всему пайплайну.
Почти всегда виновата модель данных, построенная на молчаливом допущении, что данные приезжают вовремя, приезжают один раз и больше не меняются. Ни одно из трёх допущений в проде не выполняется. Разберём пять мест, где цифры уезжают.
Технические детали
Код в основном SQL плюс немного Python, но суть везде про логику загрузки, а не про конкретный движок. Инкремент по времени загрузки, который теряет опоздавшие событияКлассическая инкрементальная витрина. Каждый час забираем то, что появилось с прошлого запуска.
INSERT INTO events_daily SELECT date_trunc('day', event_time) AS d, count(*) AS cnt, sum(amount) AS revenue FROM raw_events WHERE event_time > (SELECT max(processed_until) FROM etl_state) GROUP BY 1;Работает ровно до первого события, которое приехало с опозданием. Событие с event_time за вчера приходит в хранилище сегодня. Условие event_time > processed_until его не поймает — эта граница уже проехала.
Событие потеряется навсегда, и вчерашняя цифра останется заниженной. Первое, что здесь нужно — перестать путать два времени. У каждого события их два: event_time (когда оно произошло у пользователя) и ingested_at (когда оно физически легло к вам).
Отраслевые последствия
Инкремент отслеживается по ingested_at, а группировка и все бизнес‑расчёты делаются по event_time. WITH new_rows AS ( SELECT * FROM raw_events WHERE ingested_at > (SELECT max(processed_until) FROM etl_state) ) SELECT date_trunc('day', event_time) AS d, ... FROM new_rows GROUP BY 1;Теперь опоздавшее событие попадает в обработку (оно новое по ingested_at), но приземляется в правильный день (по event_time).
Соответственно, пересчитывать надо не только сегодняшний день, а все дни, которых коснулись новые строки:SELECT DISTINCT date_trunc('day', event_time) FROM new_rows;Плюс окно перезаписи. Смотрим на реальность: снимаем гистограмму задержки и решаем, сколько ждём. SELECT date_diff('hour', event_time, ingested_at) AS lag_hours, count(*) FROM raw_events WHERE ingested_at > now() - interval '30' day GROUP BY 1 ORDER BY 1;Если 99% событий приезжают в течение шести часов, а хвост тянется до трёх суток, разумно каждый запуск пересчитывать последние трое суток целиком.
Данные старше — считаем закрытыми, и это осознанное решение, а не случайность.
Этот прогресс даёт важные сигналы о будущем отрасли, и технологический мир внимательно наблюдает.





