DispelEvilBot/docs/events.md

125 lines
8.7 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# События аналитики (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`, `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` |
## Поведение при недоступности ClickHouse
Аналитика — **некритичная** подсистема: её сбой не должен влиять на бота.
- события копятся в буфере и отправляются батчами (по размеру и по таймеру);
- если ClickHouse недоступен, буфер не растёт бесконечно: при переполнении самые
старые записи отбрасываются со счётчиком в логе;
- если записать батч не удалось, события выводятся в stdout (`WARNING`, первые
несколько строк + общее количество) и отбрасываются — данные не пропадают молча;
- при остановке бота выполняется финальный сброс буфера;
- выключение целиком — `CLICKHOUSE_ENABLED=false` (тогда `record()` — no-op).
## Примеры запросов
```sql
-- активность по дням
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;
```