Зачем: стенду нужен воспроизводимый холодный старт, при котором схема хранилища и топик появляются сами, а сырьё из 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>
36 lines
2.5 KiB
SQL
36 lines
2.5 KiB
SQL
-- STG: матвью приёма — из чтеца в сырьё.
|
||
--
|
||
-- Номер 40, а не 11, и пропуск в нумерации намеренный. Файлы 20-ods-tables.sql
|
||
-- и 30-ods-views.sql приносит #43, и матвью приёма обязана создаваться после
|
||
-- матвью разбора: Kafka-движок начинает читать топик ровно тогда, когда к нему
|
||
-- привязывают первую матвью. Создай её раньше разбора — и всё, что доедет в
|
||
-- зазоре, ляжет в сырьё и не попадёт в ODS никуда, ни в событие, ни в ошибки.
|
||
-- На пустом топике зазор безвреден, поэтому первый прогон о нём не скажет:
|
||
-- проснётся он, когда тома ClickHouse снесены, а данные Kafka целы, то есть на
|
||
-- обычной отладке. Нумерацию здесь не «приводить в порядок».
|
||
--
|
||
-- Пишем в stg.hits_raw_dist, а не в локальную таблицу: раскладку по шардам
|
||
-- обязан определять ключ шардирования, а не то, какая нода случайно читала
|
||
-- топик. Вставка при этом фоновая — окно потери принято осознанно, довод
|
||
-- целиком в docs/architecture/storage.md, раздел «Приём».
|
||
--
|
||
-- Служебные колонки заполняются выражением здесь, а не DEFAULT в таблице.
|
||
-- Для consumer_host это обязательно: проверено на стенде 5 августа 2026 года —
|
||
-- DEFAULT hostName() вычисляется на шарде-получателе и назвал бы не ту ноду,
|
||
-- которая читала топик. hostName() в SELECT снимается на вставляющей ноде,
|
||
-- то есть отвечает ровно на нужный вопрос.
|
||
--
|
||
-- Порядок колонок в SELECT совпадает с порядком в целевой таблице.
|
||
CREATE MATERIALIZED VIEW IF NOT EXISTS stg.hits_raw_mv ON CLUSTER clickstream_cluster
|
||
TO stg.hits_raw_dist
|
||
AS
|
||
SELECT
|
||
raw,
|
||
_topic AS kafka_topic,
|
||
_partition AS kafka_partition,
|
||
_offset AS kafka_offset,
|
||
_timestamp_ms AS kafka_timestamp,
|
||
hostName() AS consumer_host,
|
||
now64(3) AS _load_ts
|
||
FROM stg.hits_raw_kafka;
|