feat(ddl): пояс назван явно — в типах колонок и в разборе строки
- Зачем: - конвенция #63 записана, а код её не достиг: колонки времени стояли без пояса, и сходилось всё лишь потому, что пояс сервера — UTC. - Что: - UTCEventTime объявлен DateTime('UTC'), служебные метки _load_ts и kafka_timestamp — DateTime64(3, 'UTC') в STG и ODS. - parseDateTimeOrNull получил третьим аргументом 'UTC': маска сверяет суффикс Z как букву, зоны из строки не берёт вовсе. - контракт схемы и описание выгрузки несут тип с поясом; имя пояса Europe/Samara встало рядом со смещением в world.py, сходимость сверяет тест. - учебный комментарий о линзе — у первой колонки с явным поясом. - Проверка: - make lint, make typecheck, make test (408 тестов) - make clean && make up && make check-clickhouse — 9 из 9 - замер тикета повторён: под session_timezone='Europe/Samara' колонка показана 2026-05-31 23:37:00, как и без настроек Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
@@ -104,11 +104,11 @@ keeper, Kafka, каркас сервисов. Этап 2 идёт: в `sql/ddl/`
|
||||
С `kafka_timestamp` сложнее, и форма его решена на стенде. Меток времени движок
|
||||
даёт две: `_timestamp` — `Nullable(DateTime)`, то есть секунды, и
|
||||
`_timestamp_ms` — `Nullable(DateTime64(3))`, миллисекунды. Колонка объявлена
|
||||
`Nullable(DateTime64(3))` и заполняется из `_timestamp_ms`: у брокера метка
|
||||
миллисекундная, соседняя `_load_ts` тоже `DateTime64(3)`, а слой сырья хранит
|
||||
приехавшее, и округлять ему нечего. Обнуляемость нужна отдельно от разрядности:
|
||||
брокер метку заполняет не всегда, а необнуляемый тип значил бы либо падение
|
||||
приёма на первом сообщении, либо тихие нули за 1970 год.
|
||||
`Nullable(DateTime64(3, 'UTC'))` и заполняется из `_timestamp_ms`: у брокера
|
||||
метка миллисекундная, соседняя `_load_ts` тоже миллисекундная, а слой сырья
|
||||
хранит приехавшее, и округлять ему нечего. Обнуляемость нужна отдельно от
|
||||
разрядности: брокер метку заполняет не всегда, а необнуляемый тип значил бы
|
||||
либо падение приёма на первом сообщении, либо тихие нули за 1970 год.
|
||||
|
||||
Заполняются все они выражением в `SELECT` матвью приёма, а не `DEFAULT` в
|
||||
таблице. Для `consumer_host` это обязательно: `DEFAULT hostName()` вычисляется
|
||||
@@ -124,9 +124,9 @@ kafka_offset)`: разбор полётов идёт от «какое сооб
|
||||
нет. Замену версий сюда ставить нельзя — она отменила бы свойство слоя, ради
|
||||
которого он заведён: повтор доставки в сырье обязан быть виден.
|
||||
|
||||
Метка времени загрузки зовётся `_load_ts`, тип `DateTime64(3)`. Ставится она
|
||||
один раз, в матвью приёма, и дальше переносится из STG в ODS как есть: колонка
|
||||
отвечает на вопрос «когда строка приехала в хранилище», а не «когда её
|
||||
Метка времени загрузки зовётся `_load_ts`, тип `DateTime64(3, 'UTC')`. Ставится
|
||||
она один раз, в матвью приёма, и дальше переносится из STG в ODS как есть:
|
||||
колонка отвечает на вопрос «когда строка приехала в хранилище», а не «когда её
|
||||
разобрали». В ODS она же служит колонкой версии `ReplacingMergeTree`, и работа у
|
||||
этой версии ровно одна — схлопнуть повтор доставки. Содержимое у повтора то же
|
||||
самое, отличается только метка, поэтому какая из двух строк переживёт мерж,
|
||||
@@ -160,10 +160,9 @@ Greenplum, чтобы словарь был общим у двух хранил
|
||||
разборе, пояс сервера на данные не влияет нигде, и в этом можно убедиться,
|
||||
поменяв его. Правило стоит на источнике пояса, а не на функции:
|
||||
`toDate` по колонке, чей тип пояс несёт, законен и имени не требует — так и
|
||||
работают ключи партиций `toDate(_load_ts)` у сырья и у таблицы ошибок, когда
|
||||
`_load_ts` типизирован. Имя пишется тогда,
|
||||
когда нужна другая линза, чем у колонки: `toDate(UTCEventTime, 'Europe/Samara')`
|
||||
— это «день по часам счётчика».
|
||||
работают ключи партиций `toDate(_load_ts)` у сырья и у таблицы ошибок. Имя
|
||||
пишется тогда, когда нужна другая линза, чем у колонки:
|
||||
`toDate(UTCEventTime, 'Europe/Samara')` — это «день по часам счётчика».
|
||||
|
||||
**Какая линза, решает слой — по тому, кого он обслуживает.** ODS хранит снимок
|
||||
выгрузки и говорит на языке выгрузки: у Метрики `UTCEventTime` абсолютна,
|
||||
@@ -452,7 +451,7 @@ ODS. Второе: матвью приёма создаётся последне
|
||||
|
||||
**Проверено на стенде.** Опыты прогнаны на живом кластере: пять при исполнении
|
||||
#37 (четыре 5 августа 2026 года, пятый 6 августа), пять при исполнении #43
|
||||
(7 августа) и три при обсуждении #63 (8 августа). Все подтвердили то, что здесь
|
||||
(7 августа) и четыре при #63 (8 августа). Все подтвердили то, что здесь
|
||||
написано.
|
||||
|
||||
- Разбор строки берёт пояс у сессии, а не из строки. Под
|
||||
@@ -470,9 +469,14 @@ ODS. Второе: матвью приёма создаётся последне
|
||||
`2026-06-05 00:58:56`, а `toDate(UTCEventTime)` в обоих случаях
|
||||
`2026-06-04`. То есть вывод колонки идёт по поясу сессии, а функция — по
|
||||
поясу типа, и тип на сессию не смотрит. Родной клиент и HTTP ведут себя
|
||||
одинаково. После объявления `DateTime('UTC')` расхождение уходит: проверено
|
||||
кастом на том же событии — колонка показывает `20:58:56` и при чужом поясе
|
||||
сессии.
|
||||
одинаково.
|
||||
- С объявленным поясом расхождение уходит. Стенд поднят с нуля уже по
|
||||
конвенции; событие `WatchID = 384218330540`, `EventDate` = `2026-06-01`:
|
||||
колонка `DateTime('UTC')` показана `2026-05-31 23:37:00` и без настроек, и
|
||||
под `session_timezone = 'Europe/Samara'`, по родному клиенту и по HTTP.
|
||||
`toDate(UTCEventTime)` даёт `2026-05-31`, `toDate(UTCEventTime,
|
||||
'Europe/Samara')` — `2026-06-01`, `EventDate` — `2026-06-01`: расхождение
|
||||
`toDate(UTCEventTime)` с `EventDate` осталось, это мир, а не пояс колонки.
|
||||
|
||||
- Матвью с источником-`Distributed` срабатывает на вставку именно в эту
|
||||
распределённую таблицу, до раскладки по шардам. Обе матвью разбора стоят над
|
||||
|
||||
Reference in New Issue
Block a user