-- ============================================================================ -- Слой STG (Staging) — сырые JSON + интеграция с Kafka -- ============================================================================ -- Назначение: -- - Хранение сырых данных "как есть" из Kafka -- - Метаданные доставки (topic, partition, offset, timestamp) -- - Источник для отладки и восстановления -- -- Поток данных: -- Kafka → kafka_*_raw (ENGINE=Kafka) → MV → *_raw (MergeTree) -- ============================================================================ -- ---------------------------------------------------------------------------- -- Таблицы для сырых данных (MergeTree) -- ---------------------------------------------------------------------------- -- Хранят JSON "как есть" + метаданные Kafka -- Партиционируем по месяцу ingest_ts для эффективной очистки старых данных -- ---------------------------------------------------------------------------- CREATE TABLE IF NOT EXISTS stg.browser_raw ( ingest_ts DateTime64(3) DEFAULT now64(3), -- Время вставки в ClickHouse kafka_topic LowCardinality(String) DEFAULT '', -- Топик Kafka kafka_partition Int32 DEFAULT -1, -- Партиция kafka_offset Int64 DEFAULT -1, -- Смещение (гарантирует уникальность) kafka_ts DateTime64(3) DEFAULT ingest_ts, -- Время из Kafka (если есть) raw String -- JSON как строка ) ENGINE = MergeTree PARTITION BY toYYYYMM(ingest_ts) ORDER BY (kafka_topic, kafka_partition, kafka_offset, ingest_ts); CREATE TABLE IF NOT EXISTS stg.location_raw ( ingest_ts DateTime64(3) DEFAULT now64(3), kafka_topic LowCardinality(String) DEFAULT '', kafka_partition Int32 DEFAULT -1, kafka_offset Int64 DEFAULT -1, kafka_ts DateTime64(3) DEFAULT ingest_ts, raw String ) ENGINE = MergeTree PARTITION BY toYYYYMM(ingest_ts) ORDER BY (kafka_topic, kafka_partition, kafka_offset, ingest_ts); CREATE TABLE IF NOT EXISTS stg.device_raw ( ingest_ts DateTime64(3) DEFAULT now64(3), kafka_topic LowCardinality(String) DEFAULT '', kafka_partition Int32 DEFAULT -1, kafka_offset Int64 DEFAULT -1, kafka_ts DateTime64(3) DEFAULT ingest_ts, raw String ) ENGINE = MergeTree PARTITION BY toYYYYMM(ingest_ts) ORDER BY (kafka_topic, kafka_partition, kafka_offset, ingest_ts); CREATE TABLE IF NOT EXISTS stg.geo_raw ( ingest_ts DateTime64(3) DEFAULT now64(3), kafka_topic LowCardinality(String) DEFAULT '', kafka_partition Int32 DEFAULT -1, kafka_offset Int64 DEFAULT -1, kafka_ts DateTime64(3) DEFAULT ingest_ts, raw String ) ENGINE = MergeTree PARTITION BY toYYYYMM(ingest_ts) ORDER BY (kafka_topic, kafka_partition, kafka_offset, ingest_ts); -- ---------------------------------------------------------------------------- -- Таблицы-источники Kafka (ENGINE = Kafka) -- ---------------------------------------------------------------------------- -- Читают данные из топиков Kafka в реальном времени -- Не хранят данные постоянно, а "потребляют" их при SELECT/MV -- ---------------------------------------------------------------------------- CREATE TABLE IF NOT EXISTS stg.kafka_browser_raw (raw String) ENGINE = Kafka SETTINGS kafka_broker_list = 'kafka:29092', -- Адрес брокера (внутри Docker-сети) kafka_topic_list = 'browser_events', -- Имя топика kafka_group_name = 'ch_stg_browser', -- Группа консьюмеров (для контроля offset'ов) kafka_format = 'JSONAsString', -- Читаем весь JSON как строку kafka_num_consumers = 1, -- Количество консьюмеров (для параллелизма) kafka_handle_error_mode = 'stream'; -- Ошибки не прерывают чтение CREATE TABLE IF NOT EXISTS stg.kafka_location_raw (raw String) ENGINE = Kafka SETTINGS kafka_broker_list = 'kafka:29092', kafka_topic_list = 'location_events', kafka_group_name = 'ch_stg_location', kafka_format = 'JSONAsString', kafka_num_consumers = 1, kafka_handle_error_mode = 'stream'; CREATE TABLE IF NOT EXISTS stg.kafka_device_raw (raw String) ENGINE = Kafka SETTINGS kafka_broker_list = 'kafka:29092', kafka_topic_list = 'device_events', kafka_group_name = 'ch_stg_device', kafka_format = 'JSONAsString', kafka_num_consumers = 1, kafka_handle_error_mode = 'stream'; CREATE TABLE IF NOT EXISTS stg.kafka_geo_raw (raw String) ENGINE = Kafka SETTINGS kafka_broker_list = 'kafka:29092', kafka_topic_list = 'geo_events', kafka_group_name = 'ch_stg_geo', kafka_format = 'JSONAsString', kafka_num_consumers = 1, kafka_handle_error_mode = 'stream'; -- ---------------------------------------------------------------------------- -- Materialized Views: Kafka → STG -- ---------------------------------------------------------------------------- -- Автоматически перекладывают данные из Kafka-таблиц в MergeTree -- Работают в реальном времени: сообщение из Kafka → сразу в *_raw -- ---------------------------------------------------------------------------- CREATE MATERIALIZED VIEW IF NOT EXISTS stg.mv_kafka_browser_to_stg TO stg.browser_raw AS SELECT now64(3) AS ingest_ts, _topic AS kafka_topic, _partition AS kafka_partition, _offset AS kafka_offset, _timestamp_ms AS kafka_ts, raw FROM stg.kafka_browser_raw; CREATE MATERIALIZED VIEW IF NOT EXISTS stg.mv_kafka_location_to_stg TO stg.location_raw AS SELECT now64(3) AS ingest_ts, _topic AS kafka_topic, _partition AS kafka_partition, _offset AS kafka_offset, _timestamp_ms AS kafka_ts, raw FROM stg.kafka_location_raw; CREATE MATERIALIZED VIEW IF NOT EXISTS stg.mv_kafka_device_to_stg TO stg.device_raw AS SELECT now64(3) AS ingest_ts, _topic AS kafka_topic, _partition AS kafka_partition, _offset AS kafka_offset, _timestamp_ms AS kafka_ts, raw FROM stg.kafka_device_raw; CREATE MATERIALIZED VIEW IF NOT EXISTS stg.mv_kafka_geo_to_stg TO stg.geo_raw AS SELECT now64(3) AS ingest_ts, _topic AS kafka_topic, _partition AS kafka_partition, _offset AS kafka_offset, _timestamp_ms AS kafka_ts, raw FROM stg.kafka_geo_raw;