docs(63): правки по двум холодным ревью реализации
- Зачем:
- линия дефектов нашла три неверных утверждения и мёртвый замер, линия
уместности — три пересказа уже сказанного.
- Что:
- «тип колонки не решает, какое число ляжет» сужено до правды: разбор
отдаёт готовое число, а пояс приёмника решал бы судьбу строки.
- замер до правки типов помечен как неповторяемый на нынешнем стенде.
- правило о поясе сервера привязано к местам, где линза что-то решает:
матвью приёма пояс не называет, и это не нарушение.
- убраны: пересказ механики в ADR 0005, четыре строки учебного
комментария, утверждение о порядке файлов и «секунды от начала эпохи»
у миллисекундной метки.
- Проверка:
- make lint, make typecheck, make test (408 тестов)
- make clean && make up && make check-clickhouse — 9 из 9
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
@@ -232,11 +232,8 @@ ClickHouse 26.3.17.56. Все четыре ответили так, как жд
|
|||||||
всех трёх NULL. Источник у топика один и шлёт одну запись, так что широта не
|
всех трёх NULL. Источник у топика один и шлёт одну запись, так что широта не
|
||||||
нужна вовсе, а платится за неё отключённой проверкой.
|
нужна вовсе, а платится за неё отключённой проверкой.
|
||||||
|
|
||||||
Третий аргумент — имя пояса, `'UTC'` — пришёл с конвенцией #63. Маска сверяет
|
Третий аргумент — имя пояса, `'UTC'` — пришёл с конвенцией #63; правило и его
|
||||||
суффикс `Z` как букву и выбрасывает, зоны из строки не берёт вовсе, поэтому без
|
довод — [конвенция часовых поясов](../architecture/storage.md).
|
||||||
имени функция трактует показания часов по поясу сессии, а тот по умолчанию
|
|
||||||
серверный. Правило целиком и его довод — [конвенция часовых
|
|
||||||
поясов](../architecture/storage.md).
|
|
||||||
|
|
||||||
Цена выбора измерена на настоящих данных: по всем 101 252 строкам сырья
|
Цена выбора измерена на настоящих данных: по всем 101 252 строкам сырья
|
||||||
модельного дня (день залит дважды) точный формат разобрал метку у каждой, и
|
модельного дня (день залит дважды) точный формат разобрал метку у каждой, и
|
||||||
|
|||||||
@@ -153,11 +153,12 @@ Greenplum, чтобы словарь был общим у двух хранил
|
|||||||
какие сутки оно попадёт.
|
какие сутки оно попадёт.
|
||||||
|
|
||||||
**Линза называется явно — в типе колонки либо в вызове функции.** Третий
|
**Линза называется явно — в типе колонки либо в вызове функции.** Третий
|
||||||
источник, умолчание сервера, в коде не виден и меняется снаружи, поэтому в DDL
|
источник, умолчание сервера, в коде не виден и меняется снаружи, поэтому там,
|
||||||
и запросах его не остаётся. Прописать этот пояс своей рукой — `<timezone>` в
|
где линза что-то решает — в объявлении хранимой колонки и в выражении,
|
||||||
конфигурации ноды или `TZ` контейнеру — было бы той же болезнью с другим
|
считающем дату, — его не остаётся. Прописать этот пояс своей рукой —
|
||||||
умолчанием, и вдобавок отняло бы проверку: когда линза названа в типах и в
|
`<timezone>` в конфигурации ноды или `TZ` контейнеру — было бы той же болезнью
|
||||||
разборе, пояс сервера на данные не влияет нигде, и в этом можно убедиться,
|
с другим умолчанием, и вдобавок отняло бы проверку: когда линза названа в типах
|
||||||
|
и в разборе, пояс сервера на данные не влияет нигде, и в этом можно убедиться,
|
||||||
поменяв его. Правило стоит на источнике пояса, а не на функции:
|
поменяв его. Правило стоит на источнике пояса, а не на функции:
|
||||||
`toDate` по колонке, чей тип пояс несёт, законен и имени не требует — так и
|
`toDate` по колонке, чей тип пояс несёт, законен и имени не требует — так и
|
||||||
работают ключи партиций `toDate(_load_ts)` у сырья и у таблицы ошибок. Имя
|
работают ключи партиций `toDate(_load_ts)` у сырья и у таблицы ошибок. Имя
|
||||||
@@ -182,8 +183,9 @@ UTC+4), и пересчёт идёт один раз при наполнении
|
|||||||
метку или свести её к дате. На записи — превратить строку в число: суффикс `Z`
|
метку или свести её к дате. На записи — превратить строку в число: суффикс `Z`
|
||||||
на проводе зоны не даёт, маска разбора съедает его буквой, и
|
на проводе зоны не даёт, маска разбора съедает его буквой, и
|
||||||
`parseDateTimeOrNull` без третьего аргумента трактует показания часов по поясу
|
`parseDateTimeOrNull` без третьего аргумента трактует показания часов по поясу
|
||||||
сессии, а тот по умолчанию серверный. Тип колонки тут не помогает: он про то,
|
сессии, а тот по умолчанию серверный. Тип колонки тут не помогает: разбор
|
||||||
как число читают, а не про то, какое ляжет. Механика и выбор функции — [ADR
|
отдаёт готовое число, и колонка кладёт его как есть — пояс приёмника решал бы
|
||||||
|
судьбу строки, а не числа. Механика и выбор функции — [ADR
|
||||||
0005](../adr/0005-event-ingestion.md).
|
0005](../adr/0005-event-ingestion.md).
|
||||||
|
|
||||||
**День берётся из `EventDate`.** Дата в поясе счётчика уже посчитана
|
**День берётся из `EventDate`.** Дата в поясе счётчика уже посчитана
|
||||||
@@ -463,7 +465,9 @@ ODS. Второе: матвью приёма создаётся последне
|
|||||||
- `toDate` берёт пояс у типа своего аргумента. Из одного момента:
|
- `toDate` берёт пояс у типа своего аргумента. Из одного момента:
|
||||||
по `DateTime('UTC')` — `2026-05-31`, по `DateTime('Europe/Samara')` —
|
по `DateTime('UTC')` — `2026-05-31`, по `DateTime('Europe/Samara')` —
|
||||||
`2026-06-01`. Отсюда форма правила: имя пояса нужно там, где его не несёт тип.
|
`2026-06-01`. Отсюда форма правила: имя пояса нужно там, где его не несёт тип.
|
||||||
- У колонки без объявленного пояса глаз и `GROUP BY` расходятся. Событие
|
- У колонки без объявленного пояса глаз и `GROUP BY` расходятся. Замер снят до
|
||||||
|
правки типов и на нынешнем стенде не повторяется — колонка уже с поясом.
|
||||||
|
Событие
|
||||||
`WatchID = 113504893317`, `EventDate` = `2026-06-05`: без настроек колонка
|
`WatchID = 113504893317`, `EventDate` = `2026-06-05`: без настроек колонка
|
||||||
показана `2026-06-04 20:58:56`, под `session_timezone = 'Europe/Samara'` —
|
показана `2026-06-04 20:58:56`, под `session_timezone = 'Europe/Samara'` —
|
||||||
`2026-06-05 00:58:56`, а `toDate(UTCEventTime)` в обоих случаях
|
`2026-06-05 00:58:56`, а `toDate(UTCEventTime)` в обоих случаях
|
||||||
|
|||||||
@@ -21,9 +21,10 @@ COUNTER_ID = 42150607
|
|||||||
# модельные сутки, как в выгрузке Метрики — `EventDate` дата в поясе счётчика,
|
# модельные сутки, как в выгрузке Метрики — `EventDate` дата в поясе счётчика,
|
||||||
# `UTCEventTime` абсолютная метка. Отсюда следствие, о котором сторона
|
# `UTCEventTime` абсолютная метка. Отсюда следствие, о котором сторона
|
||||||
# хранилища должна знать заранее: `toDate(UTCEventTime)` ≠ `EventDate` у ночных
|
# хранилища должна знать заранее: `toDate(UTCEventTime)` ≠ `EventDate` у ночных
|
||||||
# событий (спека генератора, раздел 9). Хранилищу нужно имя из базы поясов: его
|
# событий (спека генератора, раздел 9). Хранилищу тот же пояс нужен именем из
|
||||||
# просят `toDate` и типы колонок DDS (docs/architecture/storage.md, «Часовые
|
# базы поясов — так пишутся `toDate` и типы колонок DDS
|
||||||
# пояса»).
|
# (docs/architecture/storage.md, «Часовые пояса»). Пути отсюда в SQL нет, имя
|
||||||
|
# переносят руками — но берут его здесь.
|
||||||
COUNTER_TIMEZONE = "Europe/Samara"
|
COUNTER_TIMEZONE = "Europe/Samara"
|
||||||
COUNTER_TIMEZONE_MINUTES = 240
|
COUNTER_TIMEZONE_MINUTES = 240
|
||||||
|
|
||||||
|
|||||||
@@ -41,7 +41,7 @@ SETTINGS
|
|||||||
-- виртуальные колонки его не несут, а после записи в Distributed он уже
|
-- виртуальные колонки его не несут, а после записи в Distributed он уже
|
||||||
-- невосстановим.
|
-- невосстановим.
|
||||||
--
|
--
|
||||||
-- kafka_timestamp — Nullable(DateTime64(3)), и заполняется из виртуальной
|
-- kafka_timestamp — Nullable(DateTime64(3, 'UTC')), и заполняется из виртуальной
|
||||||
-- колонки _timestamp_ms, а не из _timestamp. Измерено на стенде 5 августа
|
-- колонки _timestamp_ms, а не из _timestamp. Измерено на стенде 5 августа
|
||||||
-- 2026 года: _timestamp — Nullable(DateTime), то есть секунды; _timestamp_ms —
|
-- 2026 года: _timestamp — Nullable(DateTime), то есть секунды; _timestamp_ms —
|
||||||
-- Nullable(DateTime64(3)). Взяты миллисекунды: у брокера метка миллисекундная,
|
-- Nullable(DateTime64(3)). Взяты миллисекунды: у брокера метка миллисекундная,
|
||||||
@@ -49,12 +49,10 @@ SETTINGS
|
|||||||
-- округлять ему нечего. Обнуляемость обязательна: метку брокер заполняет не
|
-- округлять ему нечего. Обнуляемость обязательна: метку брокер заполняет не
|
||||||
-- всегда, а необнуляемый тип дал бы либо падение приёма, либо тихий 1970 год.
|
-- всегда, а необнуляемый тип дал бы либо падение приёма, либо тихий 1970 год.
|
||||||
--
|
--
|
||||||
-- Пояс у обеих меток написан в типе — DateTime64(3, 'UTC'); в DDL стенда он
|
-- Пояс у обеих меток написан в типе. Хранимого числа он не меняет, а решает,
|
||||||
-- встречается здесь впервые. Само число от пояса не зависит, это секунды от
|
-- в какие сутки метка попадёт, — то есть чем окажется toDate(_load_ts) в ключе
|
||||||
-- начала эпохи. Пояс — линза: по нему решают, какие часы покажут метку и в
|
-- партиции ниже. Не напиши его — пояс возьмётся у сервера, а это умолчание в
|
||||||
-- какие сутки она попадёт, то есть чем окажется toDate(_load_ts) в ключе
|
-- коде не видно. Правило целиком — docs/architecture/storage.md, «Часовые
|
||||||
-- партиции ниже. Не назови линзу — её выберет пояс сервера, умолчание, которого
|
|
||||||
-- в коде не видно. Правило целиком — docs/architecture/storage.md, «Часовые
|
|
||||||
-- пояса».
|
-- пояса».
|
||||||
--
|
--
|
||||||
-- Нарезка и срок жизни — по _load_ts, то есть по реальному времени загрузки:
|
-- Нарезка и срок жизни — по _load_ts, то есть по реальному времени загрузки:
|
||||||
|
|||||||
@@ -55,7 +55,8 @@
|
|||||||
-- Третьим аргументом назван пояс — 'UTC'. Суффикс Z маска сверяет как букву и
|
-- Третьим аргументом назван пояс — 'UTC'. Суффикс Z маска сверяет как букву и
|
||||||
-- выбрасывает, зоны из строки не берёт вовсе, поэтому без имени функция читала
|
-- выбрасывает, зоны из строки не берёт вовсе, поэтому без имени функция читала
|
||||||
-- бы показания часов по поясу сессии, а тот по умолчанию серверный. Тип
|
-- бы показания часов по поясу сессии, а тот по умолчанию серверный. Тип
|
||||||
-- колонки этого не чинит: он про то, как число покажут, а не какое ляжет.
|
-- колонки этого не чинит: разбор отдаёт готовое число, и колонка кладёт его
|
||||||
|
-- как есть — пояс приёмника решал бы судьбу строки, а не числа.
|
||||||
-- Правило и замер — docs/architecture/storage.md, «Часовые пояса».
|
-- Правило и замер — docs/architecture/storage.md, «Часовые пояса».
|
||||||
--
|
--
|
||||||
-- EventDate в такой подпорке не нуждается: дата уезжает как «2026-06-01», и
|
-- EventDate в такой подпорке не нуждается: дата уезжает как «2026-06-01», и
|
||||||
|
|||||||
Reference in New Issue
Block a user