8.7 KiB
События аналитики (ClickHouse)
Бот пишет все значимые действия в ClickHouse — единую широкую таблицу
analytics.events. Дашборд (Superset) и ETL — отдельный финальный этап; здесь
описан контракт событий: какие бывают event_type и что лежит в payload.
Источник правды в коде —
common/analytics/events.py(EventType). Расхождение со списком ниже ловитtests/test_analytics.py. Добавляешь событие — добавь строку в таблицу.
Схема в ClickHouse
DDL — clickhouse/init/001_events.sql (применяется при первой инициализации контейнера).
analytics.events— сырые события,MergeTree,PARTITION BY toYYYYMMDD(event_time),ORDER BY (event_time, event_type, user_id). Партиционирование по дню выбрано специально: сырые партиции ежедневно удаляются после бэкапа (дешёвыйDROP PARTITION, без mutation).analytics.events_daily— агрегат (SummingMergeTree):day,event_type,user_id,chat_id,events. Строится материализованным представлениемevents_daily_mv.analytics.money_daily— агрегат по деньгам:day,event_type,user_id,amount_sum,events(изpayload.amountсобытийmoney_accrual/money_spend).
Агрегаты остаются на VPS навсегда, поэтому активность, топ команд и денежные потоки
доступны и после удаления сырых событий. Запросы к агрегатам суммируют events/amount_sum
(фон слияний SummingMergeTree не гарантирует полную свёртку в любой момент).
Хранение и бэкап
| Что | Где | Сколько |
|---|---|---|
| Сырые события | VPS, analytics.events |
последние BACKUP_GRACE_DAYS дней (по умолчанию 7) |
| Агрегаты | VPS | постоянно |
| Полная история | домашний сервер, свой ClickHouse | архивы events_YYYY-MM-DD.parquet.gz (ежедневно) |
Каждую ночь (по Москве) домашний сервер сам забирает вчерашнюю партицию с VPS (pull
по ssh), импортирует её в свой ClickHouse и сверяет количество строк; только после
успешной сверки он просит VPS удалить партиции старше грейса. Ошибка бэкапа данные не
удаляет. Подробности — DEPLOY.md и deploy/ssh-access.md.
Уведомления в чат логов
Помимо записи в ClickHouse часть событий дублируется сообщением в ALERT_CHAT_ID
(если он задан; 0 — выключено). Отправка не критична: ошибки только логируются.
| Уведомление | Когда | Событие в ClickHouse |
|---|---|---|
| Вход участника | кто-то вошёл в основной чат | join |
| Выход участника | кто-то вышел из основного чата | leave |
| Очищение | успешное /очищение (+опыт, +монеты) |
purification |
| Пинг «бот живой» | раз в час | bot_alive |
| Попытка доступа извне | не участник чата стучится в бота | access_denied |
Поля события
| Поле | Тип | Описание |
|---|---|---|
| event_time | DateTime('UTC') |
Момент события (UTC, без таймзоны) |
| event_type | LowCardinality(String) |
Тип события (см. каталог ниже) |
| user_id | Int64 |
Telegram-ID пользователя (0 — системное событие) |
| username | String |
@username на момент события (может быть пустым) |
| chat_id | Int64 |
ID чата (обычно основной чат клана) |
| payload | String |
Дополнительные данные события в виде JSON |
| bot_version | String |
Версия бота из common/version.py |
| prod | UInt8 |
1 — прод-контур, 0 — тестовый |
Каталог событий
| Тип события | Когда пишется | payload |
|---|---|---|
message |
Любое сообщение в основном чате | message_id, chat_type, content_type, text_len, is_command |
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 |
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 |
Поведение при недоступности ClickHouse
Аналитика — некритичная подсистема: её сбой не должен влиять на бота.
- события копятся в буфере и отправляются батчами (по размеру и по таймеру);
- если ClickHouse недоступен, буфер не растёт бесконечно: при переполнении самые старые записи отбрасываются со счётчиком в логе;
- если записать батч не удалось, события выводятся в stdout (
WARNING, первые несколько строк + общее количество) и отбрасываются — данные не пропадают молча; - при остановке бота выполняется финальный сброс буфера;
- выключение целиком —
CLICKHOUSE_ENABLED=false(тогдаrecord()— no-op).
Примеры запросов
-- активность по дням
SELECT toDate(event_time) AS day, count() FROM analytics.events GROUP BY day ORDER BY day;
-- топ команд за неделю
SELECT JSONExtractString(payload, 'command') AS cmd, count() AS uses
FROM analytics.events
WHERE event_type = 'command' AND event_time >= now() - INTERVAL 7 DAY
GROUP BY cmd ORDER BY uses DESC;
-- покупки за месяц
SELECT event_type, count(), sum(JSONExtractInt(payload, 'price'))
FROM analytics.events
WHERE event_type IN ('purchase_title', 'purchase_donat') AND event_time >= now() - INTERVAL 30 DAY
GROUP BY event_type;
-- ошибки
SELECT event_time, JSONExtractString(payload, 'error_type'), JSONExtractString(payload, 'error')
FROM analytics.events WHERE event_type = 'error' ORDER BY event_time DESC LIMIT 50;