diff --git a/docs/architecture/storage.md b/docs/architecture/storage.md index 05d676a..f932cbd 100644 --- a/docs/architecture/storage.md +++ b/docs/architecture/storage.md @@ -424,48 +424,27 @@ kafka_offset)`: смотрят такую таблицу от класса, а Справочные данные живут в базе `dic` — вне цепочки STG → ODS → DDS → DM. В хранилище их никто не производит: содержимое приходит из файла репозитория, а -читают его несколько слоёв сразу. Поэтому зона своя, читать её вправе любой -слой, писать — никто. Почему так, а не пропиской в слое модели, — -[ADR 0012](../adr/0012-dictionary-home.md). +читают его несколько слоёв сразу. Отсюда правило принадлежности: читать зону +вправе любой слой, писать — никто. Почему зона, а не прописка в слое модели, и +почему словарь читает подложку, а не файл, — [ADR +0012](../adr/0012-dictionary-home.md). -### Словарь товаров +Первый и пока единственный справочник — каталог товаров: подложка +`dic.products_file` над CSV репозитория и словарь `dic.products` поверх неё. +Реплик у зоны нет: обе ноды читают свой смонтированный файл и держат свою копию +словаря в памяти. Словарь обновляется сам, окном `LIFETIME`, и потому после +правки каталога ноды какое-то время отвечают по-разному. Разогнать его раньше +срока — `SYSTEM RELOAD DICTIONARY ON CLUSTER`; это административная операция, +роль `etl` права на неё не получает. -Под словарём лежит подложка `dic.products_file` на движке `File`. Она ничего не -хранит и перечитывает `data/catalog/products.csv` на каждом запросе. Compose -монтирует каталог только для чтения в `user_files` обеих нод, DDL создаёт оба -объекта `ON CLUSTER`: имя и форма одни, но каждая нода читает свой файл и держит -свою копию словаря в памяти. +Деньги каталога стоит держать в голове отдельно от остальных: в CSV лежат целые +копейки, словарь отдаёт `Decimal(18, 2)`, а `productPrice` события — уже целые +рубли. Разрыв намеренный, на нём стоит урок про `Float64` ([описание +выгрузки](../formats/clickstream-event.md)). -Путь у движка `File` считается **от `user_files`**, а не от корня данных. -Форма `./user_files/catalog/products.csv`, которой требовал прежний файловый -источник словаря, даёт `FILE_DOESNT_EXIST` — легко принять за пропавший монтаж. - -Сам `dic.products` берёт подложку источником `CLICKHOUSE`, причём формой -`query`, а не `table`: словарь нормализует данные на входе, а не зеркалит -подложку. Пользователя надо называть явно — без него словарь идёт как `default` -с пустым паролем и падает с `AUTHENTICATION_FAILED`. Ходит он беспарольным -`dict`, у которого одно право: чтение `dic`. Хост локальный, поэтому запрос -идёт без сети. - -Строковый ключ `sku` требует `COMPLEX_KEY_HASHED`; числовой `FLAT` здесь -неприменим. - -**Цена: три единицы, и их не надо путать.** В файле каталога лежат целые -копейки — так их пишет генератор, и часть цен несёт копейки намеренно. Словарь -приводит их к `Decimal(18, 2)`, как у денег бэкенда: единица становится видна в -самом числе, `129000` против `1290.00`. А в контракте события `productPrice` — -целые **рубли**, округление формата. Разрыв между ценой каталога и ценой в -событии заложен специально: на нём держится урок про `Float64` и расхождение -представлений денег. - -`LIFETIME(MIN 60 MAX 90)` включает фоновое обновление: правка каталога доезжает -до словаря сама, без команды. Момент внутри окна случаен — так разводят -обращения разных серверов к источнику, чтобы они не шли разом. Цена у этого -заявленная: ноды обновляются вразнобой, и до тридцати секунд одна отвечает по -новому каталогу, а вторая по старому. Разогнать словари вручную можно штатной -командой — `SYSTEM RELOAD DICTIONARY ON CLUSTER clickstream_cluster -dic.products`; это административная операция, её выполняют под `default`, роль -`etl` права перезагрузки словарей не получает. +Устройство обоих объектов — почему такой движок, такая форма пути, такой +пользователь и такое окно — расписано в +[`sql/ddl/05-dic-catalog.sql`](../../sql/ddl/05-dic-catalog.sql). ## Раскладка SQL @@ -592,21 +571,12 @@ DDL-словарь с файловым источником внутри `user_f изменилась на обеих нодах; после отката и повторной команды вернулась обратно. **Проверка зоны справочников 20 августа 2026 года (#105).** Подложка на движке -`File` работает `ON CLUSTER` и перечитывает файл на каждом запросе: строка, -дописанная снаружи, меняет счёт без команд. Словарь поверх неё обновляется сам -внутри окна `LIFETIME` — замер поймал изменение через 116 секунд, без -`SYSTEM RELOAD DICTIONARY`. Относительный путь у движка считается от -`user_files`, а не от корня данных. Источник `CLICKHOUSE` без явного `user` -идёт как `default` с пустым паролем и падает с `AUTHENTICATION_FAILED`; -беспарольный пользователь из файла настройки принимается молча. Приведение -`toDecimal64(price, 2) / 100` даёт `Decimal(18, 2)` и точно на ценах с -копейками: `188990 → 1889.90`. Собранный с нуля стенд поднял оба объекта, `bi` -читает словарь `dictGet`-ом со второй ноды, `analyst` — подложку соединением. - -Отдельно про право на движок: отказ называет `TABLE ENGINE ON File`, но права -с таким именем не хватает. Замер тремя пользователями показал, что достаточно -`GRANT FILE ON *.*` — привилегии на источник, как у Kafka, — а `TABLE ENGINE ON -File` не нужен вовсе. Текст ошибки здесь уводит в сторону. +`File` перечитывает файл на каждом запросе, словарь поверх неё обновляется сам +внутри окна `LIFETIME`, а собранный с нуля стенд поднимает оба объекта и отдаёт +словарь читателям. Замеры целиком — в +[ADR 0012](../adr/0012-dictionary-home.md), раздел «Что проверено»; там же +разобрано, почему отказ в праве на движок называет не то право, которого не +хватает. **Проверка версий заказов 18 августа 2026 года (#94).** MCP Context7 подтвердил, что `JSONType` возвращает имя типа значения JSON, — на нём стоит проверка типов