Compare commits
1
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
13abc58d98 |
@@ -105,10 +105,10 @@ Kafka по той же причине спрашивают снаружи. Её
|
|||||||
|
|
||||||
`make check-clickhouse` запускает отдельную глубокую проверку ClickHouse:
|
`make check-clickhouse` запускает отдельную глубокую проверку ClickHouse:
|
||||||
описание кластера, макросы, связь с keeper, `ReplicatedMergeTree`,
|
описание кластера, макросы, связь с keeper, `ReplicatedMergeTree`,
|
||||||
`Distributed`, очередь распределённых DDL и очистку временных таблиц. Последняя
|
`Distributed`, очередь распределённых DDL и очистку временных таблиц. Девятая
|
||||||
из девяти проверок — единственная на настоящих данных: она подневно сверяет
|
из десяти проверок спрашивает словарь товаров на обеих нодах. Последняя —
|
||||||
события стартового мира с описью и при расхождении говорит, где искать —
|
единственная на настоящих событиях: она подневно сверяет стартовый мир с описью
|
||||||
в событиях или в браке.
|
и при расхождении говорит, где искать — в событиях или в браке.
|
||||||
|
|
||||||
`make config-test` проверяет Compose, синтаксис файлов DAG и пробельные ошибки
|
`make config-test` проверяет Compose, синтаксис файлов DAG и пробельные ошибки
|
||||||
в diff без запуска стенда.
|
в diff без запуска стенда.
|
||||||
|
|||||||
@@ -11,6 +11,9 @@ x-clickhouse-common: &clickhouse-common
|
|||||||
environment:
|
environment:
|
||||||
# Пользователей объявляет access.xml; настройка учётки образом не нужна.
|
# Пользователей объявляет access.xml; настройка учётки образом не нужна.
|
||||||
CLICKHOUSE_SKIP_USER_SETUP: "1"
|
CLICKHOUSE_SKIP_USER_SETUP: "1"
|
||||||
|
# Каталог товаров смонтирован в user_files только для чтения. Entrypoint
|
||||||
|
# иначе пытается сделать ему chown и первый запуск ноды завершается ошибкой.
|
||||||
|
CLICKHOUSE_DO_NOT_CHOWN: "1"
|
||||||
CLICKHOUSE_CLUSTER_SECRET: ${CLICKHOUSE_CLUSTER_SECRET:?Скопируйте .env.example в .env}
|
CLICKHOUSE_CLUSTER_SECRET: ${CLICKHOUSE_CLUSTER_SECRET:?Скопируйте .env.example в .env}
|
||||||
CLICKHOUSE_DEFAULT_PASSWORD: ${CLICKHOUSE_DEFAULT_PASSWORD:?Скопируйте .env.example в .env}
|
CLICKHOUSE_DEFAULT_PASSWORD: ${CLICKHOUSE_DEFAULT_PASSWORD:?Скопируйте .env.example в .env}
|
||||||
CLICKHOUSE_ETL_PASSWORD: ${CLICKHOUSE_ETL_PASSWORD:?Скопируйте .env.example в .env}
|
CLICKHOUSE_ETL_PASSWORD: ${CLICKHOUSE_ETL_PASSWORD:?Скопируйте .env.example в .env}
|
||||||
@@ -149,6 +152,7 @@ services:
|
|||||||
- ./infra/clickhouse/config.d/prometheus.xml:/etc/clickhouse-server/config.d/prometheus.xml:ro
|
- ./infra/clickhouse/config.d/prometheus.xml:/etc/clickhouse-server/config.d/prometheus.xml:ro
|
||||||
- ./infra/clickhouse/config.d/macros-01.xml:/etc/clickhouse-server/config.d/macros.xml:ro
|
- ./infra/clickhouse/config.d/macros-01.xml:/etc/clickhouse-server/config.d/macros.xml:ro
|
||||||
- ./infra/clickhouse/users.d/access.xml:/etc/clickhouse-server/users.d/access.xml:ro
|
- ./infra/clickhouse/users.d/access.xml:/etc/clickhouse-server/users.d/access.xml:ro
|
||||||
|
- ./data/catalog:/var/lib/clickhouse/user_files/catalog:ro
|
||||||
- clickhouse_01_data:/var/lib/clickhouse
|
- clickhouse_01_data:/var/lib/clickhouse
|
||||||
|
|
||||||
# Точка подключения Superset.
|
# Точка подключения Superset.
|
||||||
@@ -163,6 +167,7 @@ services:
|
|||||||
- ./infra/clickhouse/config.d/prometheus.xml:/etc/clickhouse-server/config.d/prometheus.xml:ro
|
- ./infra/clickhouse/config.d/prometheus.xml:/etc/clickhouse-server/config.d/prometheus.xml:ro
|
||||||
- ./infra/clickhouse/config.d/macros-02.xml:/etc/clickhouse-server/config.d/macros.xml:ro
|
- ./infra/clickhouse/config.d/macros-02.xml:/etc/clickhouse-server/config.d/macros.xml:ro
|
||||||
- ./infra/clickhouse/users.d/access.xml:/etc/clickhouse-server/users.d/access.xml:ro
|
- ./infra/clickhouse/users.d/access.xml:/etc/clickhouse-server/users.d/access.xml:ro
|
||||||
|
- ./data/catalog:/var/lib/clickhouse/user_files/catalog:ro
|
||||||
- clickhouse_02_data:/var/lib/clickhouse
|
- clickhouse_02_data:/var/lib/clickhouse
|
||||||
|
|
||||||
kafka:
|
kafka:
|
||||||
|
|||||||
@@ -12,6 +12,8 @@ keeper, Kafka, каркас сервисов. Этап 2 идёт: в `sql/ddl/`
|
|||||||
Этап 3 добавил вход второго источника и довёл его до ODS: топик `orders`, свой
|
Этап 3 добавил вход второго источника и довёл его до ODS: топик `orders`, свой
|
||||||
чтец, своё сырьё, версии заказов с таблицей ошибок и поверхность текущего
|
чтец, своё сырьё, версии заказов с таблицей ошибок и поверхность текущего
|
||||||
состояния. Наполняет всю цепочку даг `orders_ingest` двумя шагами, а не матвью.
|
состояния. Наполняет всю цепочку даг `orders_ingest` двумя шагами, а не матвью.
|
||||||
|
Тот же этап принёс первый объект DDS — словарь товаров из общего с генератором
|
||||||
|
CSV-файла.
|
||||||
Дальше по тексту устройство описано так, как оно проектируется; построенное от
|
Дальше по тексту устройство описано так, как оно проектируется; построенное от
|
||||||
заложенного отличает карта таблиц в конце.
|
заложенного отличает карта таблиц в конце.
|
||||||
|
|
||||||
@@ -414,6 +416,23 @@ kafka_offset)`: смотрят такую таблицу от класса, а
|
|||||||
разрастается до имени отдельного поля. Точная граница приёма — в
|
разрастается до имени отдельного поля. Точная граница приёма — в
|
||||||
[спецификации заказов](orders/ingestion.md).
|
[спецификации заказов](orders/ingestion.md).
|
||||||
|
|
||||||
|
## Словарь товаров
|
||||||
|
|
||||||
|
`dds.products` читает `data/catalog/products.csv` напрямую. Compose монтирует
|
||||||
|
каталог только для чтения в `user_files` обеих нод, а DDL создаёт словарь
|
||||||
|
`ON CLUSTER`: имя и форма одни, но каждая нода держит свою копию в памяти.
|
||||||
|
|
||||||
|
Источник `FILE` с форматом `CSVWithNames` читает заголовок файла. Строковый ключ
|
||||||
|
`sku` требует `COMPLEX_KEY_HASHED`; числовой `FLAT` здесь неприменим. Цена
|
||||||
|
остаётся целым числом копеек типа `Int64`, как в контракте события.
|
||||||
|
|
||||||
|
`LIFETIME(0)` отключает фоновое обновление. Каталог меняется только явной
|
||||||
|
правкой репозитория, а независимый опрос двух нод позволил бы им временно
|
||||||
|
отвечать разными версиями. Изменение применяют к обеим нодам штатной командой:
|
||||||
|
`SYSTEM RELOAD DICTIONARY ON CLUSTER clickstream_cluster dds.products`.
|
||||||
|
Это административная операция: её выполняют под `default`; роль `etl` права
|
||||||
|
перезагрузки словарей не получает.
|
||||||
|
|
||||||
## Раскладка SQL
|
## Раскладка SQL
|
||||||
|
|
||||||
Исполняемые дагами запросы лежат по правилу
|
Исполняемые дагами запросы лежат по правилу
|
||||||
@@ -439,6 +458,7 @@ Airflow читает и собирает эти файлы штатным шаб
|
|||||||
| `00-databases.sql` | базы слоёв |
|
| `00-databases.sql` | базы слоёв |
|
||||||
| `10-stg-tables.sql` | чтецы топиков `hits` и `orders`, локальные и распределённые таблицы сырья обоих источников |
|
| `10-stg-tables.sql` | чтецы топиков `hits` и `orders`, локальные и распределённые таблицы сырья обоих источников |
|
||||||
| `20-ods-tables.sql` | типизированное событие, версии заказа и обе таблицы ошибок |
|
| `20-ods-tables.sql` | типизированное событие, версии заказа и обе таблицы ошибок |
|
||||||
|
| `25-dds-dictionaries.sql` | словарь товаров из общего CSV-каталога |
|
||||||
| `30-ods-views.sql` | актуальные события, текущие заказы и матвью разбора в ODS |
|
| `30-ods-views.sql` | актуальные события, текущие заказы и матвью разбора в ODS |
|
||||||
| `40-stg-views.sql` | матвью приёма: чтец в сырьё |
|
| `40-stg-views.sql` | матвью приёма: чтец в сырьё |
|
||||||
|
|
||||||
@@ -501,13 +521,14 @@ ODS. Второе: матвью приёма создаётся последне
|
|||||||
| ODS | `ods.order_snapshot_rep` / `_dist` | типизированные версии заказа |
|
| ODS | `ods.order_snapshot_rep` / `_dist` | типизированные версии заказа |
|
||||||
| ODS | `ods.order_v` | текущая версия заказа на языке источника |
|
| ODS | `ods.order_v` | текущая версия заказа на языке источника |
|
||||||
| ODS | `ods.order_snapshot_errors_rep` / `_dist` | строки слепка, не прошедшие строгий приём |
|
| ODS | `ods.order_snapshot_errors_rep` / `_dist` | строки слепка, не прошедшие строгий приём |
|
||||||
|
| DDS | `dds.products` | словарь товаров из общего с генератором CSV-каталога |
|
||||||
|
|
||||||
Матвью разбора у заказов нет: срез сырья раскладывают по этим двум целям два
|
Матвью разбора у заказов нет: срез сырья раскладывают по этим двум целям два
|
||||||
`INSERT SELECT` шага `parse_batch` из файлов `sql/ods/order_snapshot_load.sql`
|
`INSERT SELECT` шага `parse_batch` из файлов `sql/ods/order_snapshot_load.sql`
|
||||||
и `sql/ods/order_snapshot_errors_load.sql`.
|
и `sql/ods/order_snapshot_errors_load.sql`.
|
||||||
|
|
||||||
Слои DDS и DM появляются на следующих этапах; их состав задан разделом 7
|
Таблицы DDS и слой DM появляются на следующих этапах; их состав задан разделом
|
||||||
мастер-спеки и переносится сюда по мере постройки.
|
7 мастер-спеки и переносится сюда по мере постройки.
|
||||||
|
|
||||||
## Что проверено
|
## Что проверено
|
||||||
|
|
||||||
@@ -525,6 +546,13 @@ MCP Context7 подтвердила обычное представление с
|
|||||||
`ods.event_v` остался равен `ods.event_dist FINAL`. Имена и типы всех колонок
|
`ods.event_v` остался равен `ods.event_dist FINAL`. Имена и типы всех колонок
|
||||||
представления совпали с распределённой таблицей.
|
представления совпали с распределённой таблицей.
|
||||||
|
|
||||||
|
**Проверка словаря товаров 19 августа 2026 года (#95).** Context7 подтвердил
|
||||||
|
DDL-словарь с файловым источником внутри `user_files`, хешированную раскладку
|
||||||
|
для строкового ключа и отключение фонового обновления через `LIFETIME(0)`.
|
||||||
|
На закреплённом ClickHouse 26.3 цена известного `sku` после правки CSV и
|
||||||
|
`SYSTEM RELOAD DICTIONARY ON CLUSTER`
|
||||||
|
изменилась на обеих нодах; после отката и повторной команды вернулась обратно.
|
||||||
|
|
||||||
**Проверка версий заказов 18 августа 2026 года (#94).** MCP Context7 подтвердил,
|
**Проверка версий заказов 18 августа 2026 года (#94).** MCP Context7 подтвердил,
|
||||||
что `JSONType` возвращает имя типа значения JSON, — на нём стоит проверка типов
|
что `JSONType` возвращает имя типа значения JSON, — на нём стоит проверка типов
|
||||||
в предикате приёма заказов. Остальное снято на закреплённом ClickHouse 26.3.
|
в предикате приёма заказов. Остальное снято на закреплённом ClickHouse 26.3.
|
||||||
|
|||||||
@@ -43,7 +43,7 @@
|
|||||||
| `make config-test` | корень | Compose разбирается, файлы DAG синтаксически целы, в diff нет пробельных ошибок. О работоспособности не говорит ничего | не нужен | 1 с |
|
| `make config-test` | корень | Compose разбирается, файлы DAG синтаксически целы, в diff нет пробельных ошибок. О работоспособности не говорит ничего | не нужен | 1 с |
|
||||||
| `make lint` | корень | Код стенда — `dags/` и `infra/superset/` — отформатирован и проходит ruff | не нужен | 0,4 с |
|
| `make lint` | корень | Код стенда — `dags/` и `infra/superset/` — отформатирован и проходит ruff | не нужен | 0,4 с |
|
||||||
| `make smoke` | корень | Стенд **собран**: службы живы, порты отвечают, подключения настроены друг на друга. Вширь и по касательной к каждой службе. Единственная цель, которая здесь правда смоук | нужен | 9 с |
|
| `make smoke` | корень | Стенд **собран**: службы живы, порты отвечают, подключения настроены друг на друга. Вширь и по касательной к каждой службе. Единственная цель, которая здесь правда смоук | нужен | 9 с |
|
||||||
| `make check-clickhouse` | корень | Всё, что спрашивают **у ClickHouse** и он отвечает сам: макросы, шарды, реплики, путь в keeper, ключ шардирования, очередь распределённых DDL, счёт событий стартового мира против описи | нужен | 8 с |
|
| `make check-clickhouse` | корень | Всё, что спрашивают **у ClickHouse** и он отвечает сам: макросы, шарды, реплики, путь в keeper, ключ шардирования, очередь распределённых DDL, ответ словаря товаров, счёт событий стартового мира против описи | нужен | 24 с |
|
||||||
| `make check-services` | корень | **Службы работают**: DAG запускается и доходит, топик создаётся и удаляется, Superset логинится и ходит в базу | нужен | 59 с |
|
| `make check-services` | корень | **Службы работают**: DAG запускается и доходит, топик создаётся и удаляется, Superset логинится и ходит в базу | нужен | 59 с |
|
||||||
| `make lint` | `generator/` | Код генератора отформатирован и проходит ruff | не нужен | 0,4 с |
|
| `make lint` | `generator/` | Код генератора отформатирован и проходит ruff | не нужен | 0,4 с |
|
||||||
| `make typecheck` | `generator/` | Типы генератора сходятся (ty) | не нужен | 0,5 с |
|
| `make typecheck` | `generator/` | Типы генератора сходятся (ty) | не нужен | 0,5 с |
|
||||||
@@ -221,6 +221,10 @@ Kafka → STG → ODS, и живёт он в `make check-clickhouse`.
|
|||||||
|
|
||||||
## Что проверено
|
## Что проверено
|
||||||
|
|
||||||
|
**Перезамер 19 августа 2026 года при исполнении #95.** После добавления десятой
|
||||||
|
проверки словаря `make check-clickhouse` на чисто поднятом стенде прошёл за
|
||||||
|
24 секунды. Это цена всей цели, а не одной новой строки.
|
||||||
|
|
||||||
**Замер 9 августа 2026 года при появлении корневого `lint` (#65).** Три прогона
|
**Замер 9 августа 2026 года при появлении корневого `lint` (#65).** Три прогона
|
||||||
подряд — 0,39, 0,36 и 0,39 секунды на трёх файлах стенда; в таблице 0,4 с. На
|
подряд — 0,39, 0,36 и 0,39 секунды на трёх файлах стенда; в таблице 0,4 с. На
|
||||||
машине с непрогретым кешем первый прогон дороже: `uvx` сначала скачивает ruff.
|
машине с непрогретым кешем первый прогон дороже: `uvx` сначала скачивает ruff.
|
||||||
|
|||||||
@@ -9,7 +9,7 @@
|
|||||||
<query>GRANT SELECT, INSERT, ALTER, CREATE TABLE, CREATE VIEW, DROP TABLE, DROP VIEW ON default.*</query>
|
<query>GRANT SELECT, INSERT, ALTER, CREATE TABLE, CREATE VIEW, DROP TABLE, DROP VIEW ON default.*</query>
|
||||||
<query>GRANT SELECT, INSERT, ALTER, CREATE TABLE, CREATE VIEW, DROP TABLE, DROP VIEW, CREATE DATABASE ON stg.*</query>
|
<query>GRANT SELECT, INSERT, ALTER, CREATE TABLE, CREATE VIEW, DROP TABLE, DROP VIEW, CREATE DATABASE ON stg.*</query>
|
||||||
<query>GRANT SELECT, INSERT, ALTER, CREATE TABLE, CREATE VIEW, DROP TABLE, DROP VIEW, CREATE DATABASE ON ods.*</query>
|
<query>GRANT SELECT, INSERT, ALTER, CREATE TABLE, CREATE VIEW, DROP TABLE, DROP VIEW, CREATE DATABASE ON ods.*</query>
|
||||||
<query>GRANT SELECT, INSERT, ALTER, CREATE TABLE, CREATE VIEW, DROP TABLE, DROP VIEW, CREATE DATABASE ON dds.*</query>
|
<query>GRANT SELECT, INSERT, ALTER, CREATE TABLE, CREATE VIEW, CREATE DICTIONARY, dictGet, DROP TABLE, DROP VIEW, CREATE DATABASE ON dds.*</query>
|
||||||
<query>GRANT SELECT, INSERT, ALTER, CREATE TABLE, CREATE VIEW, DROP TABLE, DROP VIEW, CREATE DATABASE ON dm.*</query>
|
<query>GRANT SELECT, INSERT, ALTER, CREATE TABLE, CREATE VIEW, DROP TABLE, DROP VIEW, CREATE DATABASE ON dm.*</query>
|
||||||
<query>GRANT SELECT ON system.*</query>
|
<query>GRANT SELECT ON system.*</query>
|
||||||
<query>GRANT CLUSTER ON *.*</query>
|
<query>GRANT CLUSTER ON *.*</query>
|
||||||
@@ -21,7 +21,7 @@
|
|||||||
<!-- Оба читателя: clickhouse-connect читает system.settings при подключении. -->
|
<!-- Оба читателя: clickhouse-connect читает system.settings при подключении. -->
|
||||||
<bi_reader>
|
<bi_reader>
|
||||||
<grants>
|
<grants>
|
||||||
<query>GRANT SELECT ON dds.*</query>
|
<query>GRANT SELECT, dictGet ON dds.*</query>
|
||||||
<query>GRANT SELECT ON dm.*</query>
|
<query>GRANT SELECT ON dm.*</query>
|
||||||
<query>GRANT SELECT ON system.settings</query>
|
<query>GRANT SELECT ON system.settings</query>
|
||||||
</grants>
|
</grants>
|
||||||
@@ -30,7 +30,7 @@
|
|||||||
<grants>
|
<grants>
|
||||||
<query>GRANT SELECT ON stg.*</query>
|
<query>GRANT SELECT ON stg.*</query>
|
||||||
<query>GRANT SELECT ON ods.*</query>
|
<query>GRANT SELECT ON ods.*</query>
|
||||||
<query>GRANT SELECT ON dds.*</query>
|
<query>GRANT SELECT, dictGet ON dds.*</query>
|
||||||
<query>GRANT SELECT ON dm.*</query>
|
<query>GRANT SELECT ON dm.*</query>
|
||||||
<query>GRANT SELECT ON system.settings</query>
|
<query>GRANT SELECT ON system.settings</query>
|
||||||
</grants>
|
</grants>
|
||||||
|
|||||||
+40
-12
@@ -2,6 +2,7 @@
|
|||||||
set -Eeuo pipefail
|
set -Eeuo pipefail
|
||||||
|
|
||||||
readonly ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
|
readonly ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
|
||||||
|
readonly CATALOG="${ROOT_DIR}/data/catalog/products.csv"
|
||||||
readonly INVENTORY="${ROOT_DIR}/data/world-inventory.json"
|
readonly INVENTORY="${ROOT_DIR}/data/world-inventory.json"
|
||||||
readonly CLUSTER="clickstream_cluster"
|
readonly CLUSTER="clickstream_cluster"
|
||||||
readonly LOCAL_TABLE="smoke_replicated_local"
|
readonly LOCAL_TABLE="smoke_replicated_local"
|
||||||
@@ -26,6 +27,17 @@ query() {
|
|||||||
' _ "$sql" </dev/null
|
' _ "$sql" </dev/null
|
||||||
}
|
}
|
||||||
|
|
||||||
|
query_as_etl() {
|
||||||
|
local service="$1"
|
||||||
|
local sql="$2"
|
||||||
|
compose exec -T "$service" sh -c '
|
||||||
|
clickhouse-client \
|
||||||
|
--user etl \
|
||||||
|
--password "$CLICKHOUSE_ETL_PASSWORD" \
|
||||||
|
--query "$1"
|
||||||
|
' _ "$sql" </dev/null
|
||||||
|
}
|
||||||
|
|
||||||
query_with_timeout() {
|
query_with_timeout() {
|
||||||
local timeout_seconds="$1"
|
local timeout_seconds="$1"
|
||||||
local service="$2"
|
local service="$2"
|
||||||
@@ -162,7 +174,7 @@ ensure_stand_running
|
|||||||
trap on_exit EXIT
|
trap on_exit EXIT
|
||||||
trap on_signal INT TERM
|
trap on_signal INT TERM
|
||||||
|
|
||||||
printf 'Проверка 1/9: описание кластера одинаково на обеих нодах...\n'
|
printf 'Проверка 1/10: описание кластера одинаково на обеих нодах...\n'
|
||||||
cluster_sql="SELECT cluster, shard_num, replica_num, host_name, port FROM system.clusters WHERE cluster = '${CLUSTER}' ORDER BY shard_num, replica_num FORMAT TSV"
|
cluster_sql="SELECT cluster, shard_num, replica_num, host_name, port FROM system.clusters WHERE cluster = '${CLUSTER}' ORDER BY shard_num, replica_num FORMAT TSV"
|
||||||
cluster_01="$(query clickhouse-01 "$cluster_sql")"
|
cluster_01="$(query clickhouse-01 "$cluster_sql")"
|
||||||
cluster_02="$(query clickhouse-02 "$cluster_sql")"
|
cluster_02="$(query clickhouse-02 "$cluster_sql")"
|
||||||
@@ -171,7 +183,7 @@ assert_equal "$expected_cluster" "$cluster_01" "неверная тополог
|
|||||||
assert_equal "$expected_cluster" "$cluster_02" "неверная топология на второй ноде"
|
assert_equal "$expected_cluster" "$cluster_02" "неверная топология на второй ноде"
|
||||||
printf 'ЗЕЛЁНО: обе ноды видят ожидаемые два шарда: clickhouse-01 и clickhouse-02.\n'
|
printf 'ЗЕЛЁНО: обе ноды видят ожидаемые два шарда: clickhouse-01 и clickhouse-02.\n'
|
||||||
|
|
||||||
printf 'Проверка 2/9: у нод разные макросы shard и replica...\n'
|
printf 'Проверка 2/10: у нод разные макросы shard и replica...\n'
|
||||||
macros_sql="SELECT macro, substitution FROM system.macros WHERE macro IN ('shard', 'replica') ORDER BY macro FORMAT TSV"
|
macros_sql="SELECT macro, substitution FROM system.macros WHERE macro IN ('shard', 'replica') ORDER BY macro FORMAT TSV"
|
||||||
macros_01="$(query clickhouse-01 "$macros_sql")"
|
macros_01="$(query clickhouse-01 "$macros_sql")"
|
||||||
macros_02="$(query clickhouse-02 "$macros_sql")"
|
macros_02="$(query clickhouse-02 "$macros_sql")"
|
||||||
@@ -180,14 +192,14 @@ assert_equal $'replica\tclickhouse-02\nshard\t02' "$macros_02" "неверные
|
|||||||
[[ "$macros_01" != "$macros_02" ]] || fail "макросы нод не должны совпадать"
|
[[ "$macros_01" != "$macros_02" ]] || fail "макросы нод не должны совпадать"
|
||||||
printf 'ЗЕЛЁНО: clickhouse-01=(shard 01, replica clickhouse-01), clickhouse-02=(shard 02, replica clickhouse-02).\n'
|
printf 'ЗЕЛЁНО: clickhouse-01=(shard 01, replica clickhouse-01), clickhouse-02=(shard 02, replica clickhouse-02).\n'
|
||||||
|
|
||||||
printf 'Проверка 3/9: keeper отвечает обеим нодам...\n'
|
printf 'Проверка 3/10: keeper отвечает обеим нодам...\n'
|
||||||
query clickhouse-01 "SELECT name FROM system.zookeeper WHERE path = '/' ORDER BY name FORMAT Null"
|
query clickhouse-01 "SELECT name FROM system.zookeeper WHERE path = '/' ORDER BY name FORMAT Null"
|
||||||
query clickhouse-02 "SELECT name FROM system.zookeeper WHERE path = '/' ORDER BY name FORMAT Null"
|
query clickhouse-02 "SELECT name FROM system.zookeeper WHERE path = '/' ORDER BY name FORMAT Null"
|
||||||
printf 'ЗЕЛЁНО: system.zookeeper доступна с обеих нод.\n'
|
printf 'ЗЕЛЁНО: system.zookeeper доступна с обеих нод.\n'
|
||||||
|
|
||||||
cleanup_tables || fail "не удалось очистить объекты предыдущего запуска"
|
cleanup_tables || fail "не удалось очистить объекты предыдущего запуска"
|
||||||
|
|
||||||
printf 'Проверка 4/9: ReplicatedMergeTree создаётся через ON CLUSTER...\n'
|
printf 'Проверка 4/10: ReplicatedMergeTree создаётся через ON CLUSTER...\n'
|
||||||
query clickhouse-01 "
|
query clickhouse-01 "
|
||||||
CREATE TABLE default.${LOCAL_TABLE} ON CLUSTER ${CLUSTER}
|
CREATE TABLE default.${LOCAL_TABLE} ON CLUSTER ${CLUSTER}
|
||||||
(
|
(
|
||||||
@@ -205,13 +217,13 @@ assert_equal $'smoke_replicated_local\tReplicatedMergeTree' "$(query clickhouse-
|
|||||||
assert_equal $'smoke_replicated_local\tReplicatedMergeTree' "$(query clickhouse-02 "$tables_sql")" "локальная таблица не создана на второй ноде"
|
assert_equal $'smoke_replicated_local\tReplicatedMergeTree' "$(query clickhouse-02 "$tables_sql")" "локальная таблица не создана на второй ноде"
|
||||||
printf 'ЗЕЛЁНО: ReplicatedMergeTree видна в system.tables на обеих нодах.\n'
|
printf 'ЗЕЛЁНО: ReplicatedMergeTree видна в system.tables на обеих нодах.\n'
|
||||||
|
|
||||||
printf 'Проверка 5/9: путь в keeper собран из макроса shard...\n'
|
printf 'Проверка 5/10: путь в keeper собран из макроса shard...\n'
|
||||||
path_sql="SELECT zookeeper_path, replica_name FROM system.replicas WHERE database = 'default' AND table = '${LOCAL_TABLE}' FORMAT TSV"
|
path_sql="SELECT zookeeper_path, replica_name FROM system.replicas WHERE database = 'default' AND table = '${LOCAL_TABLE}' FORMAT TSV"
|
||||||
assert_equal "/clickhouse/tables/01/${LOCAL_TABLE}"$'\t'"clickhouse-01" "$(query clickhouse-01 "$path_sql")" "неверные путь или имя реплики на первой ноде"
|
assert_equal "/clickhouse/tables/01/${LOCAL_TABLE}"$'\t'"clickhouse-01" "$(query clickhouse-01 "$path_sql")" "неверные путь или имя реплики на первой ноде"
|
||||||
assert_equal "/clickhouse/tables/02/${LOCAL_TABLE}"$'\t'"clickhouse-02" "$(query clickhouse-02 "$path_sql")" "неверные путь или имя реплики на второй ноде"
|
assert_equal "/clickhouse/tables/02/${LOCAL_TABLE}"$'\t'"clickhouse-02" "$(query clickhouse-02 "$path_sql")" "неверные путь или имя реплики на второй ноде"
|
||||||
printf 'ЗЕЛЁНО: пути собраны из shard (/01/ и /02/), имя реплики собрано из макроса replica.\n'
|
printf 'ЗЕЛЁНО: пути собраны из shard (/01/ и /02/), имя реплики собрано из макроса replica.\n'
|
||||||
|
|
||||||
printf 'Проверка 6/9: Distributed создаётся ON CLUSTER и передаёт данные между нодами...\n'
|
printf 'Проверка 6/10: Distributed создаётся ON CLUSTER и передаёт данные между нодами...\n'
|
||||||
query clickhouse-01 "
|
query clickhouse-01 "
|
||||||
CREATE TABLE default.${DISTRIBUTED_TABLE} ON CLUSTER ${CLUSTER}
|
CREATE TABLE default.${DISTRIBUTED_TABLE} ON CLUSTER ${CLUSTER}
|
||||||
AS default.${LOCAL_TABLE}
|
AS default.${LOCAL_TABLE}
|
||||||
@@ -233,12 +245,12 @@ sharding_sql="SELECT countIf(_shard_num != cityHash64(ClientID) % 2 + 1), uniqEx
|
|||||||
assert_equal $'0\t2' "$(query clickhouse-02 "$sharding_sql")" "Distributed использует неверный ключ шардирования"
|
assert_equal $'0\t2' "$(query clickhouse-02 "$sharding_sql")" "Distributed использует неверный ключ шардирования"
|
||||||
printf 'ЗЕЛЁНО: локальная строка первой ноды читается со второй; ключ cityHash64(ClientID) разложил строки по двум шардам.\n'
|
printf 'ЗЕЛЁНО: локальная строка первой ноды читается со второй; ключ cityHash64(ClientID) разложил строки по двум шардам.\n'
|
||||||
|
|
||||||
printf 'Проверка 7/9: в очереди распределённых DDL нет незавершённых заданий...\n'
|
printf 'Проверка 7/10: в очереди распределённых DDL нет незавершённых заданий...\n'
|
||||||
assert_ddl_queue_completed clickhouse-01 'после CREATE'
|
assert_ddl_queue_completed clickhouse-01 'после CREATE'
|
||||||
assert_ddl_queue_completed clickhouse-02 'после CREATE'
|
assert_ddl_queue_completed clickhouse-02 'после CREATE'
|
||||||
printf 'ЗЕЛЁНО: очередь содержит задания CREATE, незавершённых среди них нет.\n'
|
printf 'ЗЕЛЁНО: очередь содержит задания CREATE, незавершённых среди них нет.\n'
|
||||||
|
|
||||||
printf 'Проверка 8/9: временные таблицы удаляются через ON CLUSTER...\n'
|
printf 'Проверка 8/10: временные таблицы удаляются через ON CLUSTER...\n'
|
||||||
query clickhouse-01 "DROP TABLE default.${DISTRIBUTED_TABLE} ON CLUSTER ${CLUSTER} SYNC" >/dev/null
|
query clickhouse-01 "DROP TABLE default.${DISTRIBUTED_TABLE} ON CLUSTER ${CLUSTER} SYNC" >/dev/null
|
||||||
query clickhouse-01 "DROP TABLE default.${LOCAL_TABLE} ON CLUSTER ${CLUSTER} SYNC" >/dev/null
|
query clickhouse-01 "DROP TABLE default.${LOCAL_TABLE} ON CLUSTER ${CLUSTER} SYNC" >/dev/null
|
||||||
remaining_sql="SELECT count() FROM system.tables WHERE database = 'default' AND name IN ('${LOCAL_TABLE}', '${DISTRIBUTED_TABLE}') FORMAT TSVRaw"
|
remaining_sql="SELECT count() FROM system.tables WHERE database = 'default' AND name IN ('${LOCAL_TABLE}', '${DISTRIBUTED_TABLE}') FORMAT TSVRaw"
|
||||||
@@ -249,9 +261,25 @@ assert_ddl_queue_completed clickhouse-02 'после DROP'
|
|||||||
trap - EXIT INT TERM
|
trap - EXIT INT TERM
|
||||||
printf 'ЗЕЛЁНО: временные таблицы удалены; проверены завершённые задания CREATE и DROP.\n'
|
printf 'ЗЕЛЁНО: временные таблицы удалены; проверены завершённые задания CREATE и DROP.\n'
|
||||||
|
|
||||||
# После снятия ловушек: своих объектов эта проверка не заводит и прибирать за
|
# После снятия ловушек: своих объектов эти проверки не заводят и прибирать за
|
||||||
# собой ей нечего — она только смотрит на то, что стенд произвёл сам.
|
# собой им нечего — они только смотрят на то, что стенд произвёл сам.
|
||||||
printf 'Проверка 9/9: стартовый мир в ods.event сходится с описью...\n'
|
printf 'Проверка 9/10: словарь товаров отвечает на обеих нодах...\n'
|
||||||
|
product_sql="SELECT
|
||||||
|
dictGet('dds.products', 'name', tuple('HOME-0001')),
|
||||||
|
dictGet('dds.products', 'category', tuple('HOME-0001')),
|
||||||
|
dictGet('dds.products', 'price', tuple('HOME-0001'))
|
||||||
|
FORMAT TSV"
|
||||||
|
expected_product="$(
|
||||||
|
awk -F, \
|
||||||
|
'$1 == "HOME-0001" { print $2 "\t" $3 "\t" $5; exit }' \
|
||||||
|
"$CATALOG"
|
||||||
|
)"
|
||||||
|
[[ -n "$expected_product" ]] || fail 'в каталоге нет эталонного sku HOME-0001'
|
||||||
|
assert_equal "$expected_product" "$(query_as_etl clickhouse-01 "$product_sql")" "словарь товаров не ответил на первой ноде"
|
||||||
|
assert_equal "$expected_product" "$(query_as_etl clickhouse-02 "$product_sql")" "словарь товаров не ответил на второй ноде"
|
||||||
|
printf 'ЗЕЛЁНО: известный sku вернул имя, категорию и цену на обеих нодах.\n'
|
||||||
|
|
||||||
|
printf 'Проверка 10/10: стартовый мир в ods.event сходится с описью...\n'
|
||||||
check_starting_world
|
check_starting_world
|
||||||
|
|
||||||
printf 'ИТОГ: все 9 проверок кластера ClickHouse прошли.\n'
|
printf 'ИТОГ: все 10 проверок кластера ClickHouse прошли.\n'
|
||||||
|
|||||||
@@ -13,3 +13,6 @@ CREATE DATABASE IF NOT EXISTS stg ON CLUSTER clickstream_cluster;
|
|||||||
-- База ODS заводится здесь же, хотя её объекты приносит #43: базы дёшевы, а
|
-- База ODS заводится здесь же, хотя её объекты приносит #43: базы дёшевы, а
|
||||||
-- порядок файлов от этого не зависит.
|
-- порядок файлов от этого не зависит.
|
||||||
CREATE DATABASE IF NOT EXISTS ods ON CLUSTER clickstream_cluster;
|
CREATE DATABASE IF NOT EXISTS ods ON CLUSTER clickstream_cluster;
|
||||||
|
|
||||||
|
-- DDS начинается со словаря товаров; таблицы слоя появятся на следующем этапе.
|
||||||
|
CREATE DATABASE IF NOT EXISTS dds ON CLUSTER clickstream_cluster;
|
||||||
|
|||||||
@@ -0,0 +1,16 @@
|
|||||||
|
-- Общий с генератором каталог товаров. ClickHouse разрешает файловому
|
||||||
|
-- источнику читать только из user_files; Compose монтирует сюда один и тот же
|
||||||
|
-- файл на обе ноды. Цена хранится в копейках, как и в контракте события.
|
||||||
|
CREATE DICTIONARY IF NOT EXISTS dds.products ON CLUSTER clickstream_cluster
|
||||||
|
(
|
||||||
|
sku String,
|
||||||
|
name String,
|
||||||
|
category String,
|
||||||
|
brand String,
|
||||||
|
price Int64,
|
||||||
|
demand String
|
||||||
|
)
|
||||||
|
PRIMARY KEY sku
|
||||||
|
SOURCE(FILE(PATH './user_files/catalog/products.csv' FORMAT 'CSVWithNames'))
|
||||||
|
LAYOUT(COMPLEX_KEY_HASHED())
|
||||||
|
LIFETIME(0);
|
||||||
Reference in New Issue
Block a user