Сервис 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
|
.DEFAULT_GOAL := help
|
||||||
.RECIPEPREFIX := >
|
.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:
|
help:
|
||||||
> @echo "Контур задаётся через E=test|prod (по умолчанию test):"
|
> @echo "Контур задаётся через E=test|prod (по умолчанию test):"
|
||||||
|
|
@ -22,6 +22,7 @@ help:
|
||||||
> @echo " make images E=test образы контура"
|
> @echo " make images E=test образы контура"
|
||||||
> @echo " make check E=test проверка контура (скрипт)"
|
> @echo " make check E=test проверка контура (скрипт)"
|
||||||
> @echo " make psql E=test psql в БД контура"
|
> @echo " make psql E=test psql в БД контура"
|
||||||
|
> @echo " make ch E=test clickhouse-client контура"
|
||||||
|
|
||||||
up:
|
up:
|
||||||
> $(COMPOSE) up -d
|
> $(COMPOSE) up -d
|
||||||
|
|
@ -49,3 +50,6 @@ check:
|
||||||
|
|
||||||
psql:
|
psql:
|
||||||
> $(COMPOSE) exec postgres sh -lc 'psql -U "$$POSTGRES_USER" -d "$$POSTGRES_DB"'
|
> $(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`).
|
- Схема — SemVer-подобная (`MAJOR.MINOR.PATCH`); git-тег = `v` + версия (`v0.1`).
|
||||||
- Docker-образы тегируются версией (Блок 2), события ClickHouse получают `bot_version` (Блок 3).
|
- Docker-образы тегируются версией (Блок 2), события ClickHouse получают `bot_version` (Блок 3).
|
||||||
- Выпуск: изменить `common/version.py` → коммит → аннотированный тег `vX.Y` → `git push --tags`.
|
- Выпуск: изменить `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
|
from __future__ import annotations
|
||||||
|
|
||||||
__version__ = "0.3.0"
|
__version__ = "0.3.1"
|
||||||
|
|
|
||||||
|
|
@ -46,17 +46,55 @@ services:
|
||||||
environment:
|
environment:
|
||||||
DB_HOST: postgres
|
DB_HOST: postgres
|
||||||
DB_PORT: "5432"
|
DB_PORT: "5432"
|
||||||
|
CLICKHOUSE_HOST: clickhouse
|
||||||
|
CLICKHOUSE_PORT: "8123"
|
||||||
depends_on:
|
depends_on:
|
||||||
postgres:
|
postgres:
|
||||||
condition: service_healthy
|
condition: service_healthy
|
||||||
migrate:
|
migrate:
|
||||||
condition: service_completed_successfully
|
condition: service_completed_successfully
|
||||||
|
# Без ожидания здоровья: аналитика некритична, бот работает и при недоступном ClickHouse.
|
||||||
|
clickhouse:
|
||||||
|
condition: service_started
|
||||||
logging:
|
logging:
|
||||||
driver: json-file
|
driver: json-file
|
||||||
options:
|
options:
|
||||||
max-size: "10m"
|
max-size: "10m"
|
||||||
max-file: "3"
|
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) ---
|
# --- Reverse proxy и TLS (Блок 2.4) ---
|
||||||
# Включается, когда появится админка/Mini App (Блоки 4–5).
|
# Включается, когда появится админка/Mini App (Блоки 4–5).
|
||||||
# Раскомментировать и положить deploy/Caddyfile:
|
# Раскомментировать и положить deploy/Caddyfile:
|
||||||
|
|
@ -74,5 +112,6 @@ services:
|
||||||
|
|
||||||
volumes:
|
volumes:
|
||||||
pgdata:
|
pgdata:
|
||||||
|
clickhousedata:
|
||||||
# caddy_data:
|
# caddy_data:
|
||||||
# caddy_config:
|
# caddy_config:
|
||||||
|
|
|
||||||
|
|
@ -51,16 +51,51 @@ services:
|
||||||
# Внутри сети compose БД доступна по имени сервиса.
|
# Внутри сети compose БД доступна по имени сервиса.
|
||||||
DB_HOST: postgres
|
DB_HOST: postgres
|
||||||
DB_PORT: "5432"
|
DB_PORT: "5432"
|
||||||
|
CLICKHOUSE_HOST: clickhouse
|
||||||
|
CLICKHOUSE_PORT: "8123"
|
||||||
depends_on:
|
depends_on:
|
||||||
postgres:
|
postgres:
|
||||||
condition: service_healthy
|
condition: service_healthy
|
||||||
migrate:
|
migrate:
|
||||||
condition: service_completed_successfully
|
condition: service_completed_successfully
|
||||||
|
# Аналитика некритична: бот стартует, даже если ClickHouse ещё не готов.
|
||||||
|
clickhouse:
|
||||||
|
condition: service_started
|
||||||
logging:
|
logging:
|
||||||
driver: json-file
|
driver: json-file
|
||||||
options:
|
options:
|
||||||
max-size: "10m"
|
max-size: "10m"
|
||||||
max-file: "3"
|
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:
|
volumes:
|
||||||
pgdata:
|
pgdata:
|
||||||
|
clickhousedata:
|
||||||
|
|
|
||||||
|
|
@ -7,7 +7,7 @@ ENV_FILE=env/prod.env
|
||||||
|
|
||||||
APP_ENV=prod
|
APP_ENV=prod
|
||||||
|
|
||||||
# Образ в реестре (latest = последняя сборка из master; можно закрепить <sha> или версию, напр. 0.2.5)
|
# Образ в реестре (latest = последняя сборка из master; можно закрепить <sha> или версию)
|
||||||
REGISTRY=git.mpakosrv.ru
|
REGISTRY=git.mpakosrv.ru
|
||||||
IMAGE_TAG=latest
|
IMAGE_TAG=latest
|
||||||
|
|
||||||
|
|
@ -30,3 +30,12 @@ ALERT_CHAT_ID=0
|
||||||
# Telegram-ID админов (доступ в обход проверки членства), через запятую; можно пусто
|
# Telegram-ID админов (доступ в обход проверки членства), через запятую; можно пусто
|
||||||
ADMIN_IDS=
|
ADMIN_IDS=
|
||||||
TIMEZONE=Europe/Moscow
|
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
|
APP_ENV=dev
|
||||||
|
|
||||||
# Образ в реестре (latest = последняя сборка из master; можно закрепить <sha> или версию, напр. 0.2.5)
|
# Образ в реестре (latest = последняя сборка из master; можно закрепить <sha> или версию)
|
||||||
REGISTRY=git.mpakosrv.ru
|
REGISTRY=git.mpakosrv.ru
|
||||||
IMAGE_TAG=latest
|
IMAGE_TAG=latest
|
||||||
|
|
||||||
|
|
@ -28,3 +28,12 @@ ALERT_CHAT_ID=0
|
||||||
# Telegram-ID админов (доступ в обход проверки членства), через запятую; можно пусто
|
# Telegram-ID админов (доступ в обход проверки членства), через запятую; можно пусто
|
||||||
ADMIN_IDS=
|
ADMIN_IDS=
|
||||||
TIMEZONE=Europe/Moscow
|
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)"
|
running="$($COMPOSE ps --services --status running || true)"
|
||||||
fail=0
|
fail=0
|
||||||
for svc in postgres bot; do
|
for svc in postgres clickhouse bot; do
|
||||||
if echo "$running" | grep -qx "$svc"; then
|
if echo "$running" | grep -qx "$svc"; then
|
||||||
echo "OK ${svc}: running"
|
echo "OK ${svc}: running"
|
||||||
else
|
else
|
||||||
|
|
@ -49,4 +49,18 @@ else
|
||||||
fail=1
|
fail=1
|
||||||
fi
|
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
|
exit $fail
|
||||||
|
|
|
||||||
|
|
@ -7,4 +7,4 @@ from common import __version__
|
||||||
|
|
||||||
def test_version_is_set():
|
def test_version_is_set():
|
||||||
assert __version__
|
assert __version__
|
||||||
assert __version__ == "0.3.0"
|
assert __version__ == "0.3.1"
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue