7.7 KiB
События аналитики (Postgres)
Бот пишет значимые действия в Postgres, схема analytics. Дашборд (Superset) и ETL —
отдельный финальный этап; здесь описан контракт событий: какие бывают event_type
и что лежит в payload.
Источник правды в коде —
common/analytics/events.py(EventType). Расхождение со списком ниже ловитtests/test_analytics.py. Добавляешь событие — добавь строку в таблицу.
Схема в Postgres
DDL — sql/V26__analytics_schema.sql.
analytics.events— события,PARTITION BY RANGE (event_time)по дням. Партиции создаёт приложение (бот и админка) на несколько дней вперёд; старые партиции не удаляются — история хранится бессрочно.analytics.messages_daily— количество сообщений пользователя по дням:day,user_id,username,chat_id,count; PK(day, user_id, chat_id). На каждое сообщение выполняетсяINSERT ... ON CONFLICT DO UPDATE count + 1, поэтому вместо строки на каждое сообщение растёт только счётчик за день.
Сообщения не пишутся в analytics.events отдельным событием message.
Хранение
| Что | Где | Сколько |
|---|---|---|
| События | Postgres, analytics.events |
бессрочно (партиции по дням) |
| Счётчики сообщений | Postgres, analytics.messages_daily |
бессрочно (партиции по дням) |
Бэкап — общий бэкап Postgres. Отдельного пайплайна ClickHouse/Parquet больше нет. При росте базы нужно следить за диском: автоматической очистки нет.
Уведомления в чат логов
Помимо записи в Postgres часть событий дублируется сообщением в ALERT_CHAT_ID
(если он задан; 0 — выключено). Отправка не критична: ошибки только логируются.
| Уведомление | Когда | Событие |
|---|---|---|
| Вход участника | кто-то вошёл в основной чат | join |
| Выход участника | кто-то вышел из основного чата | leave |
| Очищение | успешное /очищение (+опыт, +монеты) |
purification |
| Пинг «бот живой» | раз в час | bot_alive |
| Попытка доступа извне | не участник чата стучится в бота | access_denied |
Поля события
| Поле | Тип | Описание |
|---|---|---|
| event_time | timestamptz |
Момент события (UTC) |
| event_type | text |
Тип события (см. каталог ниже) |
| user_id | bigint |
Telegram-ID пользователя (0 — системное событие) |
| username | text |
@username на момент события (может быть пустым) |
| chat_id | bigint |
ID чата (обычно основной чат клана) |
| payload | jsonb |
Дополнительные данные события |
| bot_version | text |
Версия бота из common/version.py |
| prod | boolean |
true — прод-контур, false — тестовый |
Каталог событий
| Тип события | Когда пишется | payload |
|---|---|---|
command |
Использование команды (/инфо, /очищение, …) |
command, args_count |
purification |
Ежедневное очищение (/очищение) |
success, exp_add, money_add |
exp_accrual |
Начисление опыта за очищение | amount, streak |
level_up |
Повышение уровня по итогам очищения | level_from, level_to |
money_accrual |
Начисление валюты (монетка за активность, награда за очищение) | amount, reason, action_id |
money_spend |
Трата валюты (покупка, обнуление баланса при удалении) | amount, reason, product_id |
eggs |
Получение яичек (/яички) |
amount |
purchase_title |
Покупка звания | title_id, price, success |
purchase_donat |
Покупка доната | donat_id, price, success |
join |
Вход участника в основной чат | chat_title |
leave |
Выход/исключение участника | chat_title, kicked, by_id, by_username |
nick_set |
Смена ника (/ник) |
nick |
subscribe |
Подписка на рассылки (/подписка) |
— |
unsubscribe |
Отписка от рассылок (/отписка) |
— |
access_denied |
Отказ в доступе (не участник чата и не админ) | reason |
bot_start |
Старт бота | app_env |
bot_stop |
Остановка бота | app_env, uptime_seconds |
bot_alive |
Ежечасный пинг «бот живой» | app_env, uptime_seconds |
error |
Необработанная ошибка при обработке апдейта | error_type, error, update_type |
Поведение при недоступности Postgres
Аналитика — некритичная подсистема: её сбой не должен влиять на бота.
- события и счётчики копятся в буфере и записываются батчами (по размеру и по таймеру);
- если Postgres недоступен, буфер не растёт бесконечно: при переполнении самые старые события отбрасываются;
- если записать батч не удалось, он выводится в stdout (
WARNING, первые несколько строк + общее количество) и отбрасывается — данные не пропадают молча; - при остановке бота выполняется финальный сброс буфера;
- выключение целиком —
ANALYTICS_ENABLED=false(тогдаrecord()/record_message()— no-op).
Примеры запросов
-- события по дням
SELECT date_trunc('day', event_time) AS day, count(*)
FROM analytics.events GROUP BY day ORDER BY day;
-- топ команд за неделю
SELECT payload->>'command' AS cmd, count(*) AS uses
FROM analytics.events
WHERE event_type = 'command' AND event_time >= now() - interval '7 days'
GROUP BY cmd ORDER BY uses DESC;
-- покупки за месяц
SELECT event_type, count(*), sum((payload->>'price')::int)
FROM analytics.events
WHERE event_type IN ('purchase_title', 'purchase_donat')
AND event_time >= now() - interval '30 days'
GROUP BY event_type;
-- сообщения по дням
SELECT day, sum(count) FROM analytics.messages_daily
WHERE day >= current_date - 30 GROUP BY day ORDER BY day;
-- ошибки
SELECT event_time, payload->>'error_type', payload->>'error'
FROM analytics.events WHERE event_type = 'error'
ORDER BY event_time DESC LIMIT 50;