Зачем: горячее ревью поймало ошибку в трёх местах сразу. Было записано, что после правки каталога ноды расходятся до тридцати секунд — число взято из ширины окна LIFETIME(MIN 60 MAX 90). Ширина тут ни при чём: каждая нода ждёт правки от нуля до верхней границы окна, фазы у них независимы, поэтому расходиться они могут почти на все полторы минуты. Что: исправлены ADR 0012, опорная точка мастер-спеки и комментарий DDL. Заодно уточнено, что LIFETIME(0) неатомарность не предотвращал, а сжимал до разброса исполнения SYSTEM RELOAD DICTIONARY ON CLUSTER, и что витрина выручки пока спроектирована, а не построена. Проверка: рассуждением, замером не подтверждалось — прежнее число тоже было выведено, а не измерено. Замер в ADR (116 секунд на окне MIN 60 MAX 120) относится к другому утверждению: что словарь обновляется сам. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
69 lines
4.5 KiB
SQL
69 lines
4.5 KiB
SQL
-- Каталог товаров: подложка на файловом движке и словарь поверх неё.
|
||
--
|
||
-- Словарь читает не файл, а таблицу хранилища — намеренное усложнение
|
||
-- ([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): словарь перезагружается сам в случайный
|
||
-- момент внутри окна. Случайность разводит обращения разных серверов к
|
||
-- источнику, и цена у неё заявленная — ноды обновляются вразнобой. Ждать
|
||
-- правки каталога каждой из них приходится от нуля до верхней границы окна,
|
||
-- фазы у них независимы, поэтому расходиться они могут почти на все
|
||
-- полторы минуты: одна перезагрузилась сразу после правки, вторая ещё нет.
|
||
--
|
||
-- Ключ строковый, поэтому 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);
|