Сервис ClickHouse, схема событий и агрегаты
- docker-compose (prod и dev) — контейнер clickhouse 25.3 с volume, ulimits и healthcheck - порт только на loopback хоста, номер задаётся CLICKHOUSE_HTTP_PORT (test 8124, prod 8123) - clickhouse/init/001_events.sql — таблица analytics.events с дневными партициями плюс агрегаты events_daily и money_daily - бот ходит в ClickHouse по имени сервиса и стартует независимо от его готовности - env-примеры контуров, цель make ch и проверка ClickHouse в check_contour.sh - версия 0.3.1
This commit is contained in:
parent
84f75e7c7d
commit
f85f876a7f
6
Makefile
6
Makefile
|
|
@ -9,7 +9,7 @@ COMPOSE := docker compose -p $(E) --env-file $(FILE) -f docker-compose.prod.yml
|
|||
.DEFAULT_GOAL := help
|
||||
.RECIPEPREFIX := >
|
||||
|
||||
.PHONY: help up pull down restart logs ps images check psql
|
||||
.PHONY: help up pull down restart logs ps images check psql ch
|
||||
|
||||
help:
|
||||
> @echo "Контур задаётся через E=test|prod (по умолчанию test):"
|
||||
|
|
@ -22,6 +22,7 @@ help:
|
|||
> @echo " make images E=test образы контура"
|
||||
> @echo " make check E=test проверка контура (скрипт)"
|
||||
> @echo " make psql E=test psql в БД контура"
|
||||
> @echo " make ch E=test clickhouse-client контура"
|
||||
|
||||
up:
|
||||
> $(COMPOSE) up -d
|
||||
|
|
@ -49,3 +50,6 @@ check:
|
|||
|
||||
psql:
|
||||
> $(COMPOSE) exec postgres sh -lc 'psql -U "$$POSTGRES_USER" -d "$$POSTGRES_DB"'
|
||||
|
||||
ch:
|
||||
> $(COMPOSE) exec clickhouse sh -lc 'clickhouse-client --user "$$CLICKHOUSE_USER" --password "$$CLICKHOUSE_PASSWORD" --database analytics'
|
||||
|
|
|
|||
|
|
@ -73,7 +73,7 @@ pytest
|
|||
|
||||
## Версионирование
|
||||
|
||||
- Единый источник версии — `common/version.py` (`__version__`), текущая — `0.3.0`.
|
||||
- Единый источник версии — `common/version.py` (`__version__`), текущая — `0.3.1`.
|
||||
- Схема — SemVer-подобная (`MAJOR.MINOR.PATCH`); git-тег = `v` + версия (`v0.1`).
|
||||
- Docker-образы тегируются версией (Блок 2), события ClickHouse получают `bot_version` (Блок 3).
|
||||
- Выпуск: изменить `common/version.py` → коммит → аннотированный тег `vX.Y` → `git push --tags`.
|
||||
|
|
|
|||
|
|
@ -0,0 +1,72 @@
|
|||
-- Схема аналитики ClickHouse.
|
||||
--
|
||||
-- Этот каталог монтируется в контейнер как /docker-entrypoint-initdb.d,
|
||||
-- поэтому скрипт применяется ТОЛЬКО при первой инициализации пустого data-каталога.
|
||||
-- Flyway у нас только для Postgres; изменения схемы здесь — новым файлом
|
||||
-- 00N_*.sql (и применением вручную на существующем контуре) плюс раздел в DEPLOY.md.
|
||||
|
||||
CREATE DATABASE IF NOT EXISTS analytics;
|
||||
|
||||
-- Единая широкая таблица событий (см. docs/events.md).
|
||||
-- Партиционирование по дню: сырые партиции удаляются после ежедневного бэкапа
|
||||
-- на домашний сервер (scripts/clickhouse_backup.sh), поэтому удаление должно
|
||||
-- быть дешёвым (DROP PARTITION, без mutation).
|
||||
CREATE TABLE IF NOT EXISTS analytics.events
|
||||
(
|
||||
event_time DateTime('UTC'),
|
||||
event_type LowCardinality(String),
|
||||
user_id Int64,
|
||||
username String,
|
||||
chat_id Int64,
|
||||
payload String,
|
||||
bot_version String,
|
||||
prod UInt8
|
||||
)
|
||||
ENGINE = MergeTree
|
||||
PARTITION BY toYYYYMMDD(event_time)
|
||||
ORDER BY (event_time, event_type, user_id);
|
||||
|
||||
-- Агрегат: события по дням и типам. Остаётся на VPS, когда сырые удалены.
|
||||
CREATE TABLE IF NOT EXISTS analytics.events_daily
|
||||
(
|
||||
day Date,
|
||||
event_type LowCardinality(String),
|
||||
user_id Int64,
|
||||
chat_id Int64,
|
||||
events UInt64
|
||||
)
|
||||
ENGINE = SummingMergeTree
|
||||
ORDER BY (day, event_type, user_id, chat_id);
|
||||
|
||||
CREATE MATERIALIZED VIEW IF NOT EXISTS analytics.events_daily_mv TO analytics.events_daily AS
|
||||
SELECT
|
||||
toDate(event_time) AS day,
|
||||
event_type,
|
||||
user_id,
|
||||
chat_id,
|
||||
count() AS events
|
||||
FROM analytics.events
|
||||
GROUP BY day, event_type, user_id, chat_id;
|
||||
|
||||
-- Агрегат: денежные потоки по дням (сумма amount из payload событий экономики).
|
||||
CREATE TABLE IF NOT EXISTS analytics.money_daily
|
||||
(
|
||||
day Date,
|
||||
event_type LowCardinality(String),
|
||||
user_id Int64,
|
||||
amount_sum Int64,
|
||||
events UInt64
|
||||
)
|
||||
ENGINE = SummingMergeTree
|
||||
ORDER BY (day, event_type, user_id);
|
||||
|
||||
CREATE MATERIALIZED VIEW IF NOT EXISTS analytics.money_daily_mv TO analytics.money_daily AS
|
||||
SELECT
|
||||
toDate(event_time) AS day,
|
||||
event_type,
|
||||
user_id,
|
||||
sum(JSONExtractInt(payload, 'amount')) AS amount_sum,
|
||||
count() AS events
|
||||
FROM analytics.events
|
||||
WHERE event_type IN ('money_accrual', 'money_spend')
|
||||
GROUP BY day, event_type, user_id;
|
||||
|
|
@ -7,4 +7,4 @@
|
|||
|
||||
from __future__ import annotations
|
||||
|
||||
__version__ = "0.3.0"
|
||||
__version__ = "0.3.1"
|
||||
|
|
|
|||
|
|
@ -46,17 +46,55 @@ services:
|
|||
environment:
|
||||
DB_HOST: postgres
|
||||
DB_PORT: "5432"
|
||||
CLICKHOUSE_HOST: clickhouse
|
||||
CLICKHOUSE_PORT: "8123"
|
||||
depends_on:
|
||||
postgres:
|
||||
condition: service_healthy
|
||||
migrate:
|
||||
condition: service_completed_successfully
|
||||
# Без ожидания здоровья: аналитика некритична, бот работает и при недоступном ClickHouse.
|
||||
clickhouse:
|
||||
condition: service_started
|
||||
logging:
|
||||
driver: json-file
|
||||
options:
|
||||
max-size: "10m"
|
||||
max-file: "3"
|
||||
|
||||
# Аналитика событий (docs/events.md). Наружу не публикуется:
|
||||
# только loopback хоста для бэкапа/туннеля; бот ходит внутрь сети compose.
|
||||
clickhouse:
|
||||
image: clickhouse/clickhouse-server:25.3
|
||||
restart: unless-stopped
|
||||
environment:
|
||||
CLICKHOUSE_DB: ${CLICKHOUSE_DB:-analytics}
|
||||
CLICKHOUSE_USER: ${CLICKHOUSE_USER:-de_bot}
|
||||
CLICKHOUSE_PASSWORD: ${CLICKHOUSE_PASSWORD:?задайте CLICKHOUSE_PASSWORD}
|
||||
# Нужно для создания пользователей/грантов (в финале — read-only пользователь для Superset).
|
||||
CLICKHOUSE_DEFAULT_ACCESS_MANAGEMENT: "1"
|
||||
volumes:
|
||||
- clickhousedata:/var/lib/clickhouse
|
||||
# DDL применяется один раз при первой инициализации (пустой data-каталог).
|
||||
- ./clickhouse/init:/docker-entrypoint-initdb.d:ro
|
||||
ports:
|
||||
# Порт контура задаётся в env (prod 8123, test 8124) — иначе конфликт на одной VPS.
|
||||
- "127.0.0.1:${CLICKHOUSE_HTTP_PORT:-8123}:8123"
|
||||
ulimits:
|
||||
nofile:
|
||||
soft: 262144
|
||||
hard: 262144
|
||||
healthcheck:
|
||||
test:
|
||||
- CMD-SHELL
|
||||
- >-
|
||||
clickhouse-client --user "$${CLICKHOUSE_USER}" --password "$${CLICKHOUSE_PASSWORD}"
|
||||
--query "SELECT 1" > /dev/null || exit 1
|
||||
interval: 15s
|
||||
timeout: 10s
|
||||
retries: 10
|
||||
start_period: 20s
|
||||
|
||||
# --- Reverse proxy и TLS (Блок 2.4) ---
|
||||
# Включается, когда появится админка/Mini App (Блоки 4–5).
|
||||
# Раскомментировать и положить deploy/Caddyfile:
|
||||
|
|
@ -74,5 +112,6 @@ services:
|
|||
|
||||
volumes:
|
||||
pgdata:
|
||||
clickhousedata:
|
||||
# caddy_data:
|
||||
# caddy_config:
|
||||
|
|
|
|||
|
|
@ -51,16 +51,51 @@ services:
|
|||
# Внутри сети compose БД доступна по имени сервиса.
|
||||
DB_HOST: postgres
|
||||
DB_PORT: "5432"
|
||||
CLICKHOUSE_HOST: clickhouse
|
||||
CLICKHOUSE_PORT: "8123"
|
||||
depends_on:
|
||||
postgres:
|
||||
condition: service_healthy
|
||||
migrate:
|
||||
condition: service_completed_successfully
|
||||
# Аналитика некритична: бот стартует, даже если ClickHouse ещё не готов.
|
||||
clickhouse:
|
||||
condition: service_started
|
||||
logging:
|
||||
driver: json-file
|
||||
options:
|
||||
max-size: "10m"
|
||||
max-file: "3"
|
||||
|
||||
# Аналитика событий (Блок 3). DDL — в clickhouse/init/.
|
||||
clickhouse:
|
||||
image: clickhouse/clickhouse-server:25.3
|
||||
restart: unless-stopped
|
||||
environment:
|
||||
CLICKHOUSE_DB: ${CLICKHOUSE_DB:-analytics}
|
||||
CLICKHOUSE_USER: ${CLICKHOUSE_USER:-de_bot}
|
||||
CLICKHOUSE_PASSWORD: ${CLICKHOUSE_PASSWORD:?задайте CLICKHOUSE_PASSWORD в .env}
|
||||
CLICKHOUSE_DEFAULT_ACCESS_MANAGEMENT: "1"
|
||||
volumes:
|
||||
- clickhousedata:/var/lib/clickhouse
|
||||
- ./clickhouse/init:/docker-entrypoint-initdb.d:ro
|
||||
ports:
|
||||
- "127.0.0.1:8123:8123"
|
||||
ulimits:
|
||||
nofile:
|
||||
soft: 262144
|
||||
hard: 262144
|
||||
healthcheck:
|
||||
test:
|
||||
- CMD-SHELL
|
||||
- >-
|
||||
clickhouse-client --user "$${CLICKHOUSE_USER}" --password "$${CLICKHOUSE_PASSWORD}"
|
||||
--query "SELECT 1" > /dev/null || exit 1
|
||||
interval: 15s
|
||||
timeout: 10s
|
||||
retries: 10
|
||||
start_period: 20s
|
||||
|
||||
volumes:
|
||||
pgdata:
|
||||
clickhousedata:
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@ ENV_FILE=env/prod.env
|
|||
|
||||
APP_ENV=prod
|
||||
|
||||
# Образ в реестре (latest = последняя сборка из master; можно закрепить <sha> или версию, напр. 0.2.5)
|
||||
# Образ в реестре (latest = последняя сборка из master; можно закрепить <sha> или версию)
|
||||
REGISTRY=git.mpakosrv.ru
|
||||
IMAGE_TAG=latest
|
||||
|
||||
|
|
@ -30,3 +30,12 @@ ALERT_CHAT_ID=0
|
|||
# Telegram-ID админов (доступ в обход проверки членства), через запятую; можно пусто
|
||||
ADMIN_IDS=
|
||||
TIMEZONE=Europe/Moscow
|
||||
|
||||
# ClickHouse (аналитика событий, см. docs/events.md).
|
||||
# Наружу не публикуется: только loopback хоста для бэкапа/туннеля;
|
||||
# бот ходит внутрь сети compose по имени сервиса clickhouse:8123.
|
||||
CLICKHOUSE_DB=analytics
|
||||
CLICKHOUSE_USER=de_bot
|
||||
CLICKHOUSE_PASSWORD=
|
||||
# Порт на хосте (для бэкапа). У test и prod он должен различаться.
|
||||
CLICKHOUSE_HTTP_PORT=8123
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@ ENV_FILE=env/test.env
|
|||
|
||||
APP_ENV=dev
|
||||
|
||||
# Образ в реестре (latest = последняя сборка из master; можно закрепить <sha> или версию, напр. 0.2.5)
|
||||
# Образ в реестре (latest = последняя сборка из master; можно закрепить <sha> или версию)
|
||||
REGISTRY=git.mpakosrv.ru
|
||||
IMAGE_TAG=latest
|
||||
|
||||
|
|
@ -28,3 +28,12 @@ ALERT_CHAT_ID=0
|
|||
# Telegram-ID админов (доступ в обход проверки членства), через запятую; можно пусто
|
||||
ADMIN_IDS=
|
||||
TIMEZONE=Europe/Moscow
|
||||
|
||||
# ClickHouse (аналитика событий, см. docs/events.md).
|
||||
# Наружу не публикуется: только loopback хоста для бэкапа/туннеля;
|
||||
# бот ходит внутрь сети compose по имени сервиса clickhouse:8123.
|
||||
CLICKHOUSE_DB=analytics
|
||||
CLICKHOUSE_USER=de_bot
|
||||
CLICKHOUSE_PASSWORD=
|
||||
# Порт на хосте: у test он отличается от prod (8123), иначе конфликт на одной VPS.
|
||||
CLICKHOUSE_HTTP_PORT=8124
|
||||
|
|
|
|||
|
|
@ -25,7 +25,7 @@ echo "== проверки =="
|
|||
|
||||
running="$($COMPOSE ps --services --status running || true)"
|
||||
fail=0
|
||||
for svc in postgres bot; do
|
||||
for svc in postgres clickhouse bot; do
|
||||
if echo "$running" | grep -qx "$svc"; then
|
||||
echo "OK ${svc}: running"
|
||||
else
|
||||
|
|
@ -49,4 +49,18 @@ else
|
|||
fail=1
|
||||
fi
|
||||
|
||||
# ClickHouse отвечает и схема аналитики применена (initdb применяется один раз,
|
||||
# при первой инициализации пустого data-каталога)
|
||||
if $COMPOSE exec -T clickhouse sh -lc 'clickhouse-client --user "$CLICKHOUSE_USER" --password "$CLICKHOUSE_PASSWORD" --query "SELECT 1"' >/dev/null 2>&1; then
|
||||
echo "OK clickhouse: принимает запросы"
|
||||
if $COMPOSE exec -T clickhouse sh -lc 'clickhouse-client --user "$CLICKHOUSE_USER" --password "$CLICKHOUSE_PASSWORD" --query "SELECT count() FROM analytics.events"' >/dev/null 2>&1; then
|
||||
echo "OK clickhouse: таблица analytics.events доступна"
|
||||
else
|
||||
echo "WARN clickhouse: нет доступа к analytics.events (см. clickhouse/init/001_events.sql)" >&2
|
||||
fi
|
||||
else
|
||||
echo "FAIL clickhouse: не отвечает" >&2
|
||||
fail=1
|
||||
fi
|
||||
|
||||
exit $fail
|
||||
|
|
|
|||
|
|
@ -7,4 +7,4 @@ from common import __version__
|
|||
|
||||
def test_version_is_set():
|
||||
assert __version__
|
||||
assert __version__ == "0.3.0"
|
||||
assert __version__ == "0.3.1"
|
||||
|
|
|
|||
Loading…
Reference in New Issue