feat(stg): DDL-бутстрап, топик hits и приём сырья обеими нодами
Зачем: стенду нужен воспроизводимый холодный старт, при котором схема хранилища и топик появляются сами, а сырьё из Kafka доезжает в STG обеими нодами кластера — без ручных шагов между `make clean` и рабочим приёмом. Что: - `sql/ddl/` — три файла, применяются по порядку имён: базы `stg` и `ods`, Kafka-чтец `hits_raw_kafka` формата RawBLOB, реплицируемая `hits_raw_rep` с окном TTL в трое суток, распределённая `hits_raw_dist` и матвью `hits_raw_mv`, переносящая сырьё вместе с метаданными доставки. - `compose.yaml` — службы `kafka-init` (топик `hits` на две партиции, с ремонтом уже созданного однопартиционного) и `clickhouse-init` (применяет `/ddl/*.sql`); `hostname:` у обеих нод, чтобы `hostName()` отдавал имя узла, а не идентификатор контейнера; `airflow-init` зависит от `clickhouse-init` — без зависимого успешный одноразовый сервис считается упавшим для `--wait`. - Доки: конвенции и раздел «Что проверено» в справочнике хранилища, указатели и границы обещаний в ADR 0005, снятые пункты в разделе 11 спеки. Проверка: `make lint`, `make typecheck`, `make config-test`, `make smoke` (25 проверок), `make smoke-guards` — зелёные. Приёмочный прогон с чистого тома подтвердил все пять критериев #37: холодный старт и идемпотентный повтор, две партиции у `hits`, метаданные доставки у доехавшего сообщения, обе партиции на обеих потребляющих нодах в одном прогоне, некорректный JSON лежит сырым и приём не встаёт. Известная граница: RawBLOB молча теряет запись с пустым значением и запись-надгробие; принято как свойство, замер и довод — в справочнике хранилища. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
@@ -94,6 +94,7 @@ services:
|
||||
# Инициатор DDL и будущее подключение Airflow.
|
||||
clickhouse-01:
|
||||
<<: *clickhouse-common
|
||||
hostname: clickhouse-01
|
||||
ports:
|
||||
- "127.0.0.1:${CLICKHOUSE_01_HTTP_PORT:-28123}:8123"
|
||||
- "127.0.0.1:${CLICKHOUSE_01_TCP_PORT:-29000}:9000"
|
||||
@@ -106,6 +107,7 @@ services:
|
||||
# Будущее подключение Superset.
|
||||
clickhouse-02:
|
||||
<<: *clickhouse-common
|
||||
hostname: clickhouse-02
|
||||
ports:
|
||||
- "127.0.0.1:${CLICKHOUSE_02_HTTP_PORT:-28124}:8123"
|
||||
- "127.0.0.1:${CLICKHOUSE_02_TCP_PORT:-29001}:9000"
|
||||
@@ -150,6 +152,79 @@ services:
|
||||
retries: 20
|
||||
start_period: 30s
|
||||
|
||||
kafka-init:
|
||||
image: ${KAFKA_IMAGE:-apache/kafka:4.3.1}
|
||||
restart: "no"
|
||||
depends_on:
|
||||
kafka:
|
||||
condition: service_healthy
|
||||
entrypoint: ["/bin/bash", "-ec"]
|
||||
command:
|
||||
- |
|
||||
topic_partition_count() {
|
||||
/opt/kafka/bin/kafka-topics.sh \
|
||||
--bootstrap-server kafka:9092 \
|
||||
--describe \
|
||||
--topic hits | \
|
||||
sed -n '1s/.*PartitionCount: \([0-9][0-9]*\).*/\1/p'
|
||||
}
|
||||
|
||||
/opt/kafka/bin/kafka-topics.sh \
|
||||
--bootstrap-server kafka:9092 \
|
||||
--create \
|
||||
--if-not-exists \
|
||||
--topic hits \
|
||||
--partitions 2 \
|
||||
--replication-factor 1
|
||||
|
||||
partition_count="$$(topic_partition_count)"
|
||||
# Брокер разрешает автосоздание, хотя потребитель ClickHouse его не запрашивает.
|
||||
# На сохранённом томе другой клиент мог уже создать hits с одним разделом.
|
||||
if [[ "$$partition_count" == "1" ]]; then
|
||||
/opt/kafka/bin/kafka-topics.sh \
|
||||
--bootstrap-server kafka:9092 \
|
||||
--alter \
|
||||
--topic hits \
|
||||
--partitions 2
|
||||
partition_count="$$(topic_partition_count)"
|
||||
fi
|
||||
if [[ "$$partition_count" != "2" ]]; then
|
||||
printf 'ОШИБКА: у топика hits разделов: %s, ожидалось: 2.\n' \
|
||||
"$${partition_count:-неизвестно}" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# DDL применяется с ноды 1 по порядку имён файлов после готовности всего кластера.
|
||||
clickhouse-init:
|
||||
image: ${CLICKHOUSE_IMAGE:-clickhouse/clickhouse-server:26.3.17.56}
|
||||
restart: "no"
|
||||
depends_on:
|
||||
kafka-init:
|
||||
condition: service_completed_successfully
|
||||
clickhouse-01:
|
||||
condition: service_healthy
|
||||
clickhouse-02:
|
||||
condition: service_healthy
|
||||
environment:
|
||||
LC_ALL: C
|
||||
entrypoint: ["/bin/bash", "-ec"]
|
||||
command:
|
||||
- |
|
||||
set -- /ddl/*.sql
|
||||
if [[ ! -e "$$1" ]]; then
|
||||
printf 'ОШИБКА: в /ddl нет файлов SQL.\n' >&2
|
||||
exit 1
|
||||
fi
|
||||
for ddl_file do
|
||||
printf 'Применяем %s.\n' "$${ddl_file##*/}"
|
||||
clickhouse-client \
|
||||
--host clickhouse-01 \
|
||||
--multiquery \
|
||||
--queries-file "$$ddl_file"
|
||||
done
|
||||
volumes:
|
||||
- ./sql/ddl:/ddl:ro
|
||||
|
||||
postgres-metadata:
|
||||
image: ${POSTGRES_IMAGE:-postgres:16-alpine}
|
||||
restart: unless-stopped
|
||||
@@ -187,6 +262,10 @@ services:
|
||||
condition: service_healthy
|
||||
clickhouse-01:
|
||||
condition: service_healthy
|
||||
# Не убирать: без зависимого успешный clickhouse-init считается
|
||||
# упавшим для --wait.
|
||||
clickhouse-init:
|
||||
condition: service_completed_successfully
|
||||
entrypoint: ["/bin/bash"]
|
||||
command: ["/opt/airflow/init.sh"]
|
||||
|
||||
|
||||
Reference in New Issue
Block a user