feat(clickhouse): справочники вынесены в зону dic, словарь читает подложку
Зачем: место словаря в dds было унаследовано от первого объекта, а не решено. Справочник читают несколько слоёв, а производит его в хранилище ни один — прописка внутри одного из потребителей приписывала DDS владение, которого у него нет. Что: заведена база dic вне цепочки STG → ODS → DDS → DM, dds.products переехал в dic.products. Под словарём появилась подложка dic.products_file на движке File — намеренное усложнение ради урока: в бою источник словаря приезжает процессом, а не лежит файлом у сервера. Источник объявлен формой query и приводит цену из целых копеек каталога в Decimal(18, 2), как у денег бэкенда. Обновление — окном LIFETIME(MIN 60 MAX 90) вместо ручной перезагрузки, ценой заявленной неатомарности между нодами. У словаря свой беспарольный пользователь dict с единственным правом на чтение dic. Суффикс _file добавлен в конвенцию имён ADR 0006. Решение, отвергнутые варианты и условия пересмотра — ADR 0012. Проверка: make clean && make up на собранном заново стенде — зелено, make smoke 20 проверок и 0 ошибок, make lint чисто. Словарь LOADED со 180 строками, цена Decimal(18, 2) и точна на копейках: 188990 → 1889.90. Пользователь bi читает словарь dictGet-ом со второй ноды, analyst — подложку соединением, база dds пуста. Минимальное право на движок замерено тремя пользователями: достаточно GRANT FILE ON *.*, хотя отказ называет TABLE ENGINE ON File. Closes #105 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,66 @@
|
||||
-- Каталог товаров: подложка на файловом движке и словарь поверх неё.
|
||||
--
|
||||
-- Словарь читает не файл, а таблицу хранилища — намеренное усложнение
|
||||
-- ([ADR 0012](../../docs/adr/0012-dictionary-home.md)). В бою справочник
|
||||
-- приезжает процессом, и предложение SOURCE с запросом и учётной записью —
|
||||
-- та форма, которую менти встретит; файловый источник работает, но редок.
|
||||
--
|
||||
-- Зона dic лежит вне цепочки STG → ODS → DDS → DM: справочник в хранилище
|
||||
-- никто не производит, а читают его несколько слоёв.
|
||||
|
||||
-- Подложка ничего не хранит: движок File перечитывает CSV на каждом запросе,
|
||||
-- поэтому правка каталога доезжает до словаря сама. Compose монтирует один и
|
||||
-- тот же файл в user_files обеих нод только для чтения.
|
||||
--
|
||||
-- Путь считается ОТ user_files, а не от корня данных: форма
|
||||
-- './user_files/catalog/products.csv' даёт FILE_DOESNT_EXIST. У файлового
|
||||
-- источника словаря база пути была другой — отсюда разница с прежним DDL.
|
||||
-- Типы здесь повторяют файл, а не модель: цена лежит целыми копейками, как её
|
||||
-- пишет генератор. Приведение к деньгам делает словарь.
|
||||
CREATE TABLE IF NOT EXISTS dic.products_file ON CLUSTER clickstream_cluster
|
||||
(
|
||||
sku String,
|
||||
name String,
|
||||
category String,
|
||||
brand String,
|
||||
price Int64,
|
||||
demand String
|
||||
)
|
||||
ENGINE = File(CSVWithNames, './catalog/products.csv');
|
||||
|
||||
-- Пользователь dict объявлен файлом настройки и умеет одно — читать dic.
|
||||
-- Назвать его обязательно: без user словарь идёт как default с пустым паролем
|
||||
-- и падает с AUTHENTICATION_FAILED. Хост локальный, поэтому запрос к подложке
|
||||
-- идёт без сети.
|
||||
--
|
||||
-- Окно обновления вместо LIFETIME(0): словарь перезагружается сам в случайный
|
||||
-- момент внутри окна. Случайность разводит обращения разных серверов к
|
||||
-- источнику, и цена у неё заявленная — ноды обновляются вразнобой, до 30
|
||||
-- секунд одна видит правку каталога, а вторая ещё нет.
|
||||
--
|
||||
-- Ключ строковый, поэтому COMPLEX_KEY_HASHED: числовой FLAT здесь неприменим.
|
||||
--
|
||||
-- Цена приводится к деньгам прямо в источнике — оттого форма query, а не
|
||||
-- table: словарь нормализует на входе, а не зеркалит подложку. В файле лежат
|
||||
-- целые копейки, наружу словарь отдаёт Decimal(18, 2), как заказы бэкенда.
|
||||
-- Единица в числе становится видна: 129000 против 1290.00.
|
||||
--
|
||||
-- Стык с событием на этом и стоит: в контракте события productPrice — целые
|
||||
-- РУБЛИ, округление формата. Разрыв между ценой каталога и ценой в событии
|
||||
-- намеренный, на нём держится урок про Float64.
|
||||
CREATE DICTIONARY IF NOT EXISTS dic.products ON CLUSTER clickstream_cluster
|
||||
(
|
||||
sku String,
|
||||
name String,
|
||||
category String,
|
||||
brand String,
|
||||
price Decimal(18, 2),
|
||||
demand String
|
||||
)
|
||||
PRIMARY KEY sku
|
||||
SOURCE(CLICKHOUSE(
|
||||
host 'localhost' port 9000 user 'dict'
|
||||
query 'SELECT sku, name, category, brand, toDecimal64(price, 2) / 100 AS price, demand FROM dic.products_file'
|
||||
))
|
||||
LAYOUT(COMPLEX_KEY_HASHED())
|
||||
LIFETIME(MIN 60 MAX 90);
|
||||
Reference in New Issue
Block a user