Построчный журнал сообщений больше не нужен
Счётчики сообщений теперь живут только в analytics.messages_daily: /инфо, /рейтинг и админка суммируют дневные значения, а bot.add_user больше не пишет строку на каждое сообщение. Старые строки bot.messages один раз перенесены в дневную таблицу и сама таблица удалена; в карточке пользователя «последнее сообщение» стало датой.
This commit is contained in:
parent
d7bbacb67c
commit
cf14dfa80d
|
|
@ -121,6 +121,7 @@ HttpOnly-cookie (`ADMIN_SESSION_SECRET`), все формы защищены CSR
|
|||
- схема — `sql/V26__analytics_schema.sql`;
|
||||
- клиент и батчинг — `common/analytics/` (запись неблокирующая, сбой Postgres не влияет на бота);
|
||||
- сообщения не пишутся поштучно: на каждое сообщение инкрементится дневной счётчик;
|
||||
- построчная `bot.messages` удалена (V27), старые данные перенесены в `analytics.messages_daily`;
|
||||
- партиции создаются приложением и **не удаляются** — история хранится бессрочно;
|
||||
- выключается флагом `ANALYTICS_ENABLED=false`.
|
||||
|
||||
|
|
@ -170,7 +171,7 @@ pytest
|
|||
|
||||
## Версионирование
|
||||
|
||||
- Единый источник версии — `common/version.py` (`__version__`), текущая — `0.7.0`.
|
||||
- Единый источник версии — `common/version.py` (`__version__`), текущая — `0.7.1`.
|
||||
- Схема — SemVer-подобная (`MAJOR.MINOR.PATCH`); git-тег = `v` + версия (`v0.1`).
|
||||
- Docker-образы тегируются версией (Блок 2), события аналитики получают `bot_version`.
|
||||
- Выпуск: изменить `common/version.py` → коммит → аннотированный тег `vX.Y` → `git push --tags`.
|
||||
|
|
|
|||
|
|
@ -68,7 +68,7 @@
|
|||
<div class="text-xs uppercase tracking-wide text-slate-500">Сообщения</div>
|
||||
<div class="mt-1 text-xl font-semibold">{{ user.msg_count }}</div>
|
||||
<div class="text-xs text-slate-500">
|
||||
последнее: {{ user.last_message_at.strftime("%d.%m.%Y %H:%M") if user.last_message_at else "—" }}
|
||||
последняя активность: {{ user.last_message_at.strftime("%d.%m.%Y") if user.last_message_at else "—" }}
|
||||
</div>
|
||||
</div>
|
||||
<div class="rounded-lg border border-slate-200 bg-white p-4">
|
||||
|
|
|
|||
|
|
@ -102,7 +102,13 @@ async def fetch_messages(
|
|||
|
||||
async with get_pool().connection() as conn:
|
||||
cur = await conn.execute(
|
||||
f"SELECT count(*) AS total FROM analytics.messages_daily{where}",
|
||||
f"""
|
||||
SELECT count(*) AS total
|
||||
FROM (
|
||||
SELECT 1 FROM analytics.messages_daily{where}
|
||||
GROUP BY day, user_id
|
||||
) AS grouped
|
||||
""",
|
||||
tuple(params),
|
||||
)
|
||||
row = await cur.fetchone()
|
||||
|
|
@ -110,9 +116,11 @@ async def fetch_messages(
|
|||
|
||||
cur = await conn.execute(
|
||||
f"""
|
||||
SELECT day, user_id, username, chat_id, count
|
||||
SELECT day, user_id, max(username) AS username, max(chat_id) AS chat_id,
|
||||
sum(count)::bigint AS count
|
||||
FROM analytics.messages_daily{where}
|
||||
ORDER BY day DESC, count DESC
|
||||
GROUP BY day, user_id
|
||||
ORDER BY day DESC, sum(count) DESC
|
||||
LIMIT %s OFFSET %s
|
||||
""",
|
||||
(*params, limit, offset),
|
||||
|
|
|
|||
|
|
@ -16,9 +16,9 @@ _FROM = """
|
|||
LEFT JOIN bot.de_users AS du ON du.id_user = tg.id_user
|
||||
LEFT JOIN bot.titles AS t ON t.id_title = du.id_title
|
||||
LEFT JOIN (
|
||||
SELECT id_user, count(*) AS cnt, max(dt) AS last_dt
|
||||
FROM bot.messages GROUP BY id_user
|
||||
) AS msg ON msg.id_user = tg.id_user
|
||||
SELECT user_id, sum(count) AS cnt, max(day) AS last_dt
|
||||
FROM analytics.messages_daily GROUP BY user_id
|
||||
) AS msg ON msg.user_id = tg.id_user
|
||||
LEFT JOIN (
|
||||
SELECT id_user, sum(amount) AS balance
|
||||
FROM bot.money GROUP BY id_user
|
||||
|
|
|
|||
|
|
@ -8,7 +8,7 @@ from common.db.pool import get_pool
|
|||
|
||||
|
||||
async def add_user(id_user: int, username: str, first_name: str, last_name: str) -> None:
|
||||
"""Создать/обновить пользователя и добавить запись о сообщении."""
|
||||
"""Создать/обновить пользователя (счётчик сообщений ведёт аналитика)."""
|
||||
async with get_pool().connection() as conn, conn.cursor() as cur:
|
||||
await cur.execute(
|
||||
"CALL bot.add_user(%s, %s, %s, %s)",
|
||||
|
|
|
|||
|
|
@ -7,4 +7,4 @@
|
|||
|
||||
from __future__ import annotations
|
||||
|
||||
__version__ = "0.7.0"
|
||||
__version__ = "0.7.1"
|
||||
|
|
|
|||
|
|
@ -21,6 +21,8 @@ DDL — `sql/V26__analytics_schema.sql`.
|
|||
поэтому вместо строки на каждое сообщение растёт только счётчик за день.
|
||||
|
||||
Сообщения **не** пишутся в `analytics.events` отдельным событием `message`.
|
||||
Построчная таблица `bot.messages` удалена миграцией `V27`: старые строки один раз
|
||||
пересчитаны в `analytics.messages_daily`, новые пишутся только счётчиком.
|
||||
|
||||
## Хранение
|
||||
|
||||
|
|
|
|||
|
|
@ -6,10 +6,10 @@ CREATE OR REPLACE FUNCTION bot.get_top_list()
|
|||
AS $$
|
||||
BEGIN
|
||||
RETURN QUERY
|
||||
SELECT tu.username, du.nick, count(*) AS cnt
|
||||
FROM bot.messages m
|
||||
INNER JOIN bot.tg_users tu ON m.id_user = tu.id_user
|
||||
INNER JOIN bot.de_users du ON m.id_user = du.id_user
|
||||
SELECT tu.username, du.nick, sum(m.count)::bigint AS cnt
|
||||
FROM analytics.messages_daily m
|
||||
INNER JOIN bot.tg_users tu ON m.user_id = tu.id_user
|
||||
INNER JOIN bot.de_users du ON m.user_id = du.id_user
|
||||
GROUP BY tu.username, du.nick
|
||||
ORDER BY 3 DESC
|
||||
LIMIT 10;
|
||||
|
|
|
|||
|
|
@ -75,15 +75,15 @@ BEGIN
|
|||
LEFT JOIN bot.de_users de ON tg.id_user = de.id_user
|
||||
LEFT JOIN bot.titles t ON de.id_title = t.id_title
|
||||
LEFT JOIN (
|
||||
SELECT id_user, count(*) AS msg_count
|
||||
FROM bot.messages
|
||||
GROUP BY id_user
|
||||
) m ON tg.id_user = m.id_user
|
||||
SELECT user_id, sum(count)::bigint AS msg_count
|
||||
FROM analytics.messages_daily
|
||||
GROUP BY user_id
|
||||
) m ON tg.id_user = m.user_id
|
||||
WHERE tg.id_user = p_id_user;
|
||||
END;
|
||||
$$;
|
||||
|
||||
-- Создать/обновить пользователя и записать сообщение.
|
||||
-- Создать/обновить пользователя.
|
||||
CREATE OR REPLACE PROCEDURE bot.add_user(
|
||||
IN p_id_user bigint,
|
||||
IN p_username character varying,
|
||||
|
|
@ -102,8 +102,6 @@ BEGIN
|
|||
INSERT INTO bot.de_users (id_user, nick)
|
||||
VALUES (p_id_user, NULL)
|
||||
ON CONFLICT (id_user) DO NOTHING;
|
||||
|
||||
INSERT INTO bot.messages (id_user) VALUES (p_id_user);
|
||||
END;
|
||||
$$;
|
||||
|
||||
|
|
@ -127,7 +125,7 @@ DECLARE
|
|||
BEGIN
|
||||
DELETE FROM bot.tg_users WHERE id_user = p_id_user;
|
||||
DELETE FROM bot.de_users WHERE id_user = p_id_user;
|
||||
DELETE FROM bot.messages WHERE id_user = p_id_user;
|
||||
DELETE FROM analytics.messages_daily WHERE user_id = p_id_user;
|
||||
DELETE FROM bot.purifications WHERE id_user = p_id_user;
|
||||
|
||||
v_money := bot.get_money(p_id_user);
|
||||
|
|
|
|||
|
|
@ -0,0 +1,51 @@
|
|||
-- Переход на дневные счётчики: переносим старые построчные сообщения
|
||||
-- в analytics.messages_daily и удаляем журнал bot.messages.
|
||||
--
|
||||
-- Для старых строк chat_id неизвестен (в bot.messages его не было), поэтому
|
||||
-- пишем 0. Чтение группирует по (day, user_id) и суммирует count, так что
|
||||
-- старые и новые записи одного дня не дублируются в интерфейсе.
|
||||
DO $$
|
||||
DECLARE
|
||||
min_day date;
|
||||
max_day date;
|
||||
d date;
|
||||
BEGIN
|
||||
SELECT min((dt AT TIME ZONE 'UTC' AT TIME ZONE 'Europe/Moscow')::date),
|
||||
max((dt AT TIME ZONE 'UTC' AT TIME ZONE 'Europe/Moscow')::date)
|
||||
INTO min_day, max_day
|
||||
FROM bot.messages
|
||||
WHERE dt IS NOT NULL;
|
||||
|
||||
IF min_day IS NULL THEN
|
||||
RETURN;
|
||||
END IF;
|
||||
|
||||
-- Партиции на все исторические дни, иначе вставка ниже не найдёт партицию.
|
||||
FOR d IN
|
||||
SELECT min_day + i
|
||||
FROM generate_series(0, max_day - min_day) AS i
|
||||
LOOP
|
||||
EXECUTE format(
|
||||
'CREATE TABLE IF NOT EXISTS analytics.messages_daily_%s '
|
||||
'PARTITION OF analytics.messages_daily '
|
||||
'FOR VALUES FROM (%L) TO (%L)',
|
||||
to_char(d, 'YYYYMMDD'), d, d + 1
|
||||
);
|
||||
END LOOP;
|
||||
END $$;
|
||||
|
||||
INSERT INTO analytics.messages_daily (day, user_id, username, chat_id, count)
|
||||
SELECT (m.dt AT TIME ZONE 'UTC' AT TIME ZONE 'Europe/Moscow')::date AS day,
|
||||
m.id_user,
|
||||
coalesce(tg.username, '') AS username,
|
||||
0 AS chat_id,
|
||||
count(*) AS count
|
||||
FROM bot.messages AS m
|
||||
LEFT JOIN bot.tg_users AS tg ON tg.id_user = m.id_user
|
||||
WHERE m.dt IS NOT NULL
|
||||
GROUP BY 1, 2, 3
|
||||
ON CONFLICT (day, user_id, chat_id) DO UPDATE
|
||||
SET count = analytics.messages_daily.count + EXCLUDED.count,
|
||||
username = EXCLUDED.username;
|
||||
|
||||
DROP TABLE IF EXISTS bot.messages;
|
||||
|
|
@ -7,4 +7,4 @@ from common import __version__
|
|||
|
||||
def test_version_is_set():
|
||||
assert __version__
|
||||
assert __version__ == "0.7.0"
|
||||
assert __version__ == "0.7.1"
|
||||
|
|
|
|||
Loading…
Reference in New Issue