feat(dm): добавлены учебные задания витрин и даг самопроверки
- Зачем:
- менти строит витрины DM сам, от DDL по правилу зерна до задачи в
конвейере; стенду нужны ТЗ с образцом и обратная связь по каждой
витрине (#159, резолюция #135).
- Что:
- docs/labs/: README и пять ТЗ — events_enriched_v, top_pages_daily,
session_overview, dq_errors_daily, utm_effectiveness; каждое ТЗ
называет урок, состав колонок, форму по зерну и самопроверку.
- dags/homework_check.py: ручной даг, задача на витрину, красная без
витрины или при расхождении; проверки — шесть файлов sql/dq/ с
инвариантами против DDS и ODS, без золотых констант; в dq_summary
не пишет.
- docs: dm.md называет homework_check и снимает «Остается нарезке»,
§7 и §10 мастер-спеки согласованы с лабами, dds/session.md указывает
на ТЗ, AGENTS.md пополнен правилом имен docs/labs/.
- Проверка:
- make lint; make smoke 21/21, make check-clickhouse 10/10,
make check-services 7/7.
- на свежем стенде все пять задач homework_check красные и называют
ТЗ; черновик каждой витрины по тексту ТЗ зеленит свою задачу,
после сноса — снова красная; строк от самопроверки в dq_summary нет.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
1 parent
35d43125b1
commit
f33d75113d
17 files changed
+1367
-25
No files matched your search
@@ -35,10 +35,9 @@
|
||||
ломают.
|
||||
|
||||
`session_id` — не ключ сортировки и не ключ шардирования, а колонка-ручка:
|
||||
витрины сессий читают `dds.session_v`, их зерно совпадает с зерном модели, и в
|
||||
соединениях id не участвует — события его не несут, мост к заказам живёт на
|
||||
событиях. Читатели ручки — лаба сессий (сопоставить свою нарезку со стендовой и
|
||||
назвать расхождение одним числом) и разбор DQ-расхождений.
|
||||
в соединениях он не участвует — события его не несут, мост к заказам живёт
|
||||
на событиях. Читатели ручки — лаба сессий (сопоставить свою нарезку со
|
||||
стендовой и назвать расхождение одним числом) и разбор DQ-расхождений.
|
||||
|
||||
## Состав строки
|
||||
|
||||
@@ -110,8 +109,9 @@
|
||||
|
||||
## Читатели
|
||||
|
||||
- Витрина обзора сессий `session_overview_v`: длительности, глубина,
|
||||
вход/выход; читает `dds.session_v`, зерно совпадает (#135).
|
||||
- Витрина обзора сессий `session_overview_v`: дневные итоги визитов в
|
||||
срезе устройств; читает `dds.session_v` и поднимает зерно до дня — состав и довод
|
||||
в ее [ТЗ](../../labs/session-overview.md) (#135).
|
||||
- DQ-сверка с эталоном (см. выше).
|
||||
- Лаба сессий: менти собирает нарезку сам до знакомства с кодом стенда,
|
||||
потом сравнивает — с эталоном и со стендовой.
|
||||
|
||||
+10
-14
@@ -39,10 +39,11 @@
|
||||
образцом. Разминка — представление `events_enriched_v` (зерно события),
|
||||
затем агрегаты `top_pages_daily`, `session_overview` и `dq_errors_daily`,
|
||||
последней — самая содержательная `utm_effectiveness` с уроком `declared_*`.
|
||||
Построенная витрина встаёт в конвейер руками менти: финальный пункт
|
||||
каждого ТЗ — добавить задачу своей витрины в `dm_transform`. До этого
|
||||
Построенная витрина встаёт в конвейер руками менти: финальный пункт ТЗ
|
||||
каждого агрегата — добавить задачу своей витрины в `dm_transform`. До этого
|
||||
конвейер о ней не знает и остаётся зелёным, после — витрина пересобирается
|
||||
наравне со стендовыми.
|
||||
наравне со стендовыми. У разминочного представления такого пункта нет:
|
||||
пересобирать нечего. Тексты заданий — в [`docs/labs/`](../labs/README.md).
|
||||
|
||||
Дашборд этапа 8 стоит только на витринах стенда: приёмка этапа не зависит
|
||||
от выполненных домашек. Расширение дашборда визуализациями по своим
|
||||
@@ -212,22 +213,17 @@ business_key`.
|
||||
|
||||
## Даг самопроверки
|
||||
|
||||
Домашки контролирует отдельный даг без расписания: менти запускает его
|
||||
рукой. Задача витрины красная, пока витрины нет или счёт не сходится, и
|
||||
зелёная, когда построено верно; сообщение об ошибке говорит, что не так и
|
||||
куда смотреть. Проверки — инварианты против DDS-источников, золотые
|
||||
константы не прибиваются. В `dq_summary` даг не пишет: он инструмент лабы,
|
||||
а не история качества конвейера.
|
||||
Домашки контролирует `homework_check` — отдельный даг без расписания: менти
|
||||
запускает его рукой. По задаче на каждое из пяти [заданий](../labs/README.md).
|
||||
Задача красная, пока витрины нет или счёт не сходится, и зелёная, когда
|
||||
построено верно; сообщение об ошибке говорит, что не так и куда смотреть. Проверки — инварианты против источников витрины,
|
||||
золотые константы не прибиваются. В `dq_summary` даг не пишет: он инструмент
|
||||
лабы, а не история качества конвейера.
|
||||
|
||||
Приём проверен на соседнем учебном стенде Airflow + Greenplum:
|
||||
валидационный даг с группами проверок по слоям давал менти обратную связь
|
||||
по каждому шагу домашки, не трогая рабочий конвейер.
|
||||
|
||||
## Остается нарезке
|
||||
|
||||
- Имя и состав дага самопроверки домашних заданий.
|
||||
- Тексты учебных заданий и точный список проверок учебного контура.
|
||||
|
||||
## Отклонённые варианты
|
||||
|
||||
- **Весь слой представлениями.** Дёшево и всегда свежо, но главный
|
||||
|
||||
@@ -0,0 +1,104 @@
|
||||
# Учебные задания: витрины слоя DM
|
||||
|
||||
Пять домашек. Стенд привозит слой DM наполовину: витрины трех сюжетов
|
||||
дашборда и контроль качества построены, остальные витрины строит менти.
|
||||
Каждое задание — самостоятельное ТЗ: что за витрина, какое у нее зерно,
|
||||
из чего она собирается и как проверить, что собралась верно.
|
||||
|
||||
Общее устройство слоя — [дока DM](../architecture/dm.md). Она отвечает на
|
||||
вопросы «почему так», задание отвечает на вопрос «что сделать».
|
||||
|
||||
## Порядок
|
||||
|
||||
Задания идут по нарастанию: каждое следующее опирается на то, что уже
|
||||
усвоено.
|
||||
|
||||
| № | Витрина | Чему учит |
|
||||
|---|---|---|
|
||||
| 1 | [`events_enriched_v`](events-enriched.md) | слой — это контракт, а не копия данных |
|
||||
| 2 | [`top_pages_daily`](top-pages-daily.md) | зерно сменилось — витрина материализуется и встает в конвейер |
|
||||
| 3 | [`session_overview`](session-overview.md) | зерно решает, на какие вопросы витрина уже не ответит |
|
||||
| 4 | [`dq_errors_daily`](dq-errors-daily.md) | у брака свой день и свой срок жизни |
|
||||
| 5 | [`utm_effectiveness`](utm-effectiveness.md) | `declared_` в имени — предупреждение, а не украшение |
|
||||
|
||||
## Общие правила
|
||||
|
||||
Они одни на все пять заданий, поэтому записаны здесь, а не повторяются в
|
||||
каждом ТЗ.
|
||||
|
||||
**Читать — через `_v`.** Публичный договор сущности DDS — представление с
|
||||
суффиксом `_v`: `dds.event_v`, `dds.session_v`. Витрина называет только
|
||||
его; физические `_rep` и `_dist` служат загрузке и обслуживанию. Соседнюю
|
||||
домашку витрина источником тоже не берет: витрина слоя DM читает договор
|
||||
DDS, и задания остаются независимыми. Исключение одно и оно оговорено в
|
||||
задании 4: у таблиц брака публичного договора нет.
|
||||
|
||||
**Писать — только в `_dist`.** При записи в локальную таблицу раскладку
|
||||
определяет то, какая нода случайно выполняла код, а не свойство данных.
|
||||
Правило и его следствия — [дока хранилища](../architecture/storage.md),
|
||||
«Раскладка по шардам».
|
||||
|
||||
**Форма — по зерну.** Витрина, меняющая зерно источника, материализуется:
|
||||
`dm.<имя>_rep` / `_dist`, донор `dm.<имя>_stage_rep` / `_stage_dist` и
|
||||
публичное `dm.<имя>_v` с явным списком колонок. Витрина, сохраняющая зерно,
|
||||
остается обычным представлением. Довод — [дока DM](../architecture/dm.md),
|
||||
«Форма: по зерну, не по цене запроса».
|
||||
|
||||
**Ключ шардирования — твое решение.** Ни одно ТЗ ключа не называет.
|
||||
Правило выбора и требование к донору — [дока
|
||||
хранилища](../architecture/storage.md), «Раскладка по шардам»: донор
|
||||
объявляется через `AS <цель>_rep` и шардируется тем же ключом, что цель,
|
||||
иначе замена партиции потеряет строку на одном шарде и задвоит на другом.
|
||||
Названный ключ надо уметь защитить: почему он не дает перекоса и почему
|
||||
более сложный тут ничего не добавляет. Образец такой защиты —
|
||||
`dm.revenue_daily_dist` в [`sql/ddl/55-dm-tables.sql`](../../sql/ddl/55-dm-tables.sql).
|
||||
|
||||
**Где лежит код.** DDL домашек — свой файл `sql/ddl/70-labs.sql`: он
|
||||
применяется после стендовых по порядку имени, и твои объекты не мешаются
|
||||
со стендовыми. У агрегатов рядом со стендовыми ложатся еще три запроса —
|
||||
`sql/dm/<витрина>_scope.sql` спрашивает у хранилища, какие дни пересобирает
|
||||
запуск, `_rebuild.sql` собирает их в донор, `_replace.sql` заменяет
|
||||
партицию, — и задача в
|
||||
[`dags/dm_transform.py`](../../dags/dm_transform.py). У разминочного
|
||||
представления нет ничего из этого: весь его код — одно `CREATE VIEW`.
|
||||
|
||||
**Образец на все случаи** — витрина `revenue_daily`: у нее есть все пять
|
||||
объектов, донор, сборка, замена дневных партиций и задача в конвейере.
|
||||
Списывать с нее устройство можно и нужно; списывать состав колонок — нет,
|
||||
он у каждой витрины свой.
|
||||
|
||||
## Самопроверка
|
||||
|
||||
Даг `homework_check` запускается рукой, расписания у него нет. Задача
|
||||
витрины красная, пока витрины нет или счет не сходится, и зеленая, когда
|
||||
собрано верно. В сводку качества конвейера `dm.dq_summary` он не пишет:
|
||||
это инструмент лабы, а не история качества стенда.
|
||||
|
||||
Проверка у всех заданий устроена одинаково и золотых констант не содержит:
|
||||
рядом с витриной независимым запросом собирается тот же ответ — прямо из
|
||||
источника витрины, минуя ее саму. У четырех заданий источник эталона тот
|
||||
же, что у витрины, `dds.session_v` или `dds.event_v`; у задания 4 это
|
||||
таблицы брака ODS, потому что в DDS у брака представительства нет. Обе
|
||||
стороны соединяются по деловому ключу через `FULL OUTER JOIN`, и считается
|
||||
число строк, где стороны разошлись или одной стороны нет. Ноль — зеленый.
|
||||
Полное соединение здесь обязательно: при обычном пропавший день или
|
||||
пропавший ключ просто не попал бы в результат и остался бы незамечен.
|
||||
|
||||
Раздел «Как проверить себя» каждого ТЗ называет деловой ключ и сравниваемые
|
||||
величины. Тот же прием в стендовом исполнении — [даг
|
||||
`dq_check`](../../dags/dq_check.py) и запросы в [`sql/dq/`](../../sql/dq).
|
||||
|
||||
**Предупреждение.** Запросы самой проверки лежат в `sql/dq/` открыто, и
|
||||
имена называют витрину и роль сверки. Проверка на инвариантах неизбежно
|
||||
близка к ответу: чтобы сверить витрину, надо ее пересчитать. Прятать эти
|
||||
файлы никто не станет — но открыв их до того, как задание сделано, ты
|
||||
обменяешь урок на готовый SQL.
|
||||
Смотреть туда стоит после своей сборки: сравнить два решения полезно,
|
||||
списать одно — нет.
|
||||
|
||||
## Уборка
|
||||
|
||||
Черновики и опыты за собой сносить: правило проверок стенда —
|
||||
[дока тестирования](../architecture/testing.md). Снесенная витрина обязана
|
||||
снова покраснить свою задачу в `homework_check` — если не покраснила,
|
||||
проверка ничего и не проверяла.
|
||||
@@ -0,0 +1,118 @@
|
||||
# Задание 4: `dm.dq_errors_daily`
|
||||
|
||||
**Чему учит:** у брака свой день и свой срок жизни. Витрина качества живет
|
||||
по времени загрузки, а не по модельному дню, и переживает собственный
|
||||
источник — поэтому «пересобрать все» здесь означает «стереть историю».
|
||||
|
||||
## Роль витрины
|
||||
|
||||
Сводка приемки: сколько строк за день не прошли разбор и по какой причине.
|
||||
Читают ее, когда качество потока просело: сначала смотрят, вырос ли класс
|
||||
брака, потом идут за сырым текстом в саму таблицу ошибок.
|
||||
|
||||
Витрина отвечает на вопрос «сколько и чего», а не «что именно сломалось».
|
||||
Разбирательство с конкретной строкой начинается в `ods.event_errors_dist` и
|
||||
`ods.order_errors_dist`, где рядом с классом лежат сырой текст и координаты
|
||||
доставки.
|
||||
|
||||
## Источник: единственное исключение из правила `_v`
|
||||
|
||||
Общее правило домашек — читать через публичный договор `_v`. У таблиц брака
|
||||
такого договора нет, и заводить его ради этой витрины не надо: брак — не
|
||||
сущность бизнес-модели, а диагностика слоя приема. Читать нужно
|
||||
распределенные таблицы напрямую:
|
||||
|
||||
- `ods.event_errors_dist` — брак разбора событий;
|
||||
- `ods.order_errors_dist` — брак разбора слепка заказов.
|
||||
|
||||
Именно `_dist`, а не `_rep`: локальная таблица вернет строки одного шарда, и
|
||||
витрина тихо покажет половину брака. Ошибки при этом не будет — ошибка
|
||||
появится позже, когда по витрине примут решение.
|
||||
|
||||
## Зерно и форма
|
||||
|
||||
Зерно — **день загрузки × источник × класс брака**. Зерно источника —
|
||||
строка брака, значит зерно сменилось и витрина материализуется:
|
||||
`dm.dq_errors_daily_rep` / `_dist`, донор `_stage_rep` / `_stage_dist`,
|
||||
публичное `dm.dq_errors_daily_v`. Ключ партиции — день загрузки.
|
||||
|
||||
День здесь — `toDate(_load_ts)`, то есть настоящее время, когда строка
|
||||
приехала в хранилище, а не модельный день мира. Модельного дня у брака нет
|
||||
и быть не может: он живет в содержимом, а содержимое как раз и не
|
||||
разобралось.
|
||||
|
||||
## Состав колонок
|
||||
|
||||
| Колонка | Тип | Что это |
|
||||
|---|---|---|
|
||||
| `load_date` | `Date` | день загрузки, в который приехал брак |
|
||||
| `source` | `LowCardinality(String)` | `event` или `order` |
|
||||
| `error_class` | `LowCardinality(String)` | класс брака из таблицы ошибок |
|
||||
| `error_rows` | `UInt64` | число забракованных строк |
|
||||
| `_load_id` | `String` | запуск, собравший день |
|
||||
| `_load_ts` | `DateTime64(3, 'UTC')` | когда собран |
|
||||
|
||||
Две похожие колонки рядом различаются намеренно: `load_date` — про день,
|
||||
когда приехал брак, `_load_ts` — про запуск, который собрал витрину. Первая
|
||||
деловая, вторая служебная, и совпадать они не обязаны.
|
||||
|
||||
Классы у двух источников свои и не пересекаются по смыслу, поэтому колонка
|
||||
`source` в зерне обязательна: без нее одинаковое имя класса у событий и у
|
||||
заказов слилось бы в одну строку.
|
||||
|
||||
## Ритм пересборки: витрина переживает источник
|
||||
|
||||
Таблицы брака живут месяц (`TTL`), сырье — трое суток. Витрина живет без
|
||||
срока: сводка «сколько брака было в марте» нужна и после того, как сами
|
||||
строки марта удалены.
|
||||
|
||||
Отсюда правило прогона: пересобираются **только те дни загрузки, которые
|
||||
еще есть в источнике**. Спроси у источника список живых дней и замени
|
||||
партиции ровно этих дней. Соблазн «собрать все заново в донор и подменить
|
||||
таблицу целиком» здесь стоит дороже обычного: истекшие по `TTL` дни исчезли
|
||||
бы из витрины навсегда, а вместе с ними — единственная оставшаяся память о
|
||||
них.
|
||||
|
||||
Текущий день загрузки не замерзает до конца суток: поток событий идет, и
|
||||
брак в него еще добавится. Это нормально, день просто пересоберется на
|
||||
следующем прогоне.
|
||||
|
||||
## Ключ шардирования
|
||||
|
||||
Твое решение по [правилу донора](../architecture/storage.md), «Раскладка по
|
||||
шардам». Витрина крошечная: классов брака у событий и заказов вместе меньше
|
||||
десятка. Учти это в защите ключа — иногда правильный ответ звучит как
|
||||
«перекос здесь ничего не стоит, и вот почему».
|
||||
|
||||
## Как проверить себя
|
||||
|
||||
Деловой ключ — пара `source` и `error_class` внутри `load_date`. Область
|
||||
проверки — дни загрузки, которые еще живы хотя бы в одной таблице ошибок:
|
||||
день за границей `TTL` сверять не с чем, и его отсутствие в источнике не
|
||||
ошибка.
|
||||
|
||||
Собери прямой счет по обеим таблицам ошибок, соедини с
|
||||
`dm.dq_errors_daily_v` полным внешним соединением по ключу и посчитай
|
||||
строки, где ключа нет с одной из сторон или где расходится `error_rows`.
|
||||
Ноль — зеленый.
|
||||
|
||||
Отдельно: сумма `error_rows` по живым дням равна общему числу строк в
|
||||
`ods.event_errors_dist` плюс `ods.order_errors_dist`. Если эта сумма вышла
|
||||
примерно вдвое меньше — витрина почти наверняка собрана с `_rep` вместо
|
||||
`_dist` и видит один шард из двух.
|
||||
|
||||
**Пустая витрина — верный ответ.** На чистом стенде брака нет, обе стороны
|
||||
пусты, расхождений ноль, задача зеленая. Проверка, которая требует хотя бы
|
||||
одной строки, на здоровом стенде краснела бы всегда и приучала бы себя
|
||||
игнорировать.
|
||||
|
||||
## Приемка
|
||||
|
||||
- [ ] пять объектов витрины созданы, донор объявлен через `AS` и шардируется
|
||||
ключом цели;
|
||||
- [ ] источники — `_dist`-таблицы обоих видов брака;
|
||||
- [ ] прогон заменяет партиции только живых дней источника и не трогает
|
||||
истекшие;
|
||||
- [ ] задача `dq_errors_daily` в `homework_check` зеленая — в том числе на
|
||||
чистом стенде, где брака нет;
|
||||
- [ ] задача витрины добавлена в `dm_transform`.
|
||||
@@ -0,0 +1,114 @@
|
||||
# Задание 1: `dm.events_enriched_v`
|
||||
|
||||
**Чему учит:** слой — это контракт, а не копия данных. Витрина, которая не
|
||||
меняет зерно источника, остается представлением, и копии событий ради
|
||||
других имен колонок не заводит ни один боевой склад.
|
||||
|
||||
Это разминка. Строк она не хранит, в конвейер не встает, ключа шардирования
|
||||
у нее нет. Задача одна: собрать плоское событие для BI и не поддаться
|
||||
соблазну разложить его на диск.
|
||||
|
||||
## Роль витрины
|
||||
|
||||
Договор события в DDS широкий — 44 колонки, почти весь формат выгрузки:
|
||||
часть из них технические координаты источника, часть — параллельные массивы
|
||||
товаров. Аналитику в Superset нужен плоский набор деловых полей и пара
|
||||
производных, которые иначе каждый пишет заново в каждом запросе.
|
||||
|
||||
Витрина отвечает на вопрос «что вообще происходило»: с нее начинают, когда
|
||||
надо посмотреть на события глазами человека, а не разбирать широкую строку
|
||||
источника.
|
||||
|
||||
Источником для витрин следующих заданий она не служит. Витрина слоя DM
|
||||
читает публичный договор DDS — так устроены и стендовые витрины, и потому
|
||||
задания независимы: соседнюю домашку можно делать первой, а можно не делать
|
||||
вовсе.
|
||||
|
||||
## Зерно и форма
|
||||
|
||||
Зерно — **событие**: одна строка витрины равна одной строке `dds.event_v`.
|
||||
Зерно источника сохранено, значит форма — обычное представление
|
||||
`CREATE VIEW dm.events_enriched_v`, без физической таблицы, без донора и
|
||||
без задачи в `dm_transform`. Пересобирать нечего.
|
||||
|
||||
Источник — `dds.event_v`, и только он.
|
||||
|
||||
## Состав колонок
|
||||
|
||||
Явный список обязателен: договор не должен расширяться сам, когда в
|
||||
источник добавят колонку.
|
||||
|
||||
**Время и посетитель:** `event_date`, `event_time`, `event_hour`,
|
||||
`client_id`, `event_id`.
|
||||
|
||||
**Действие:** `event_type` (`pageview`, `add_to_cart`, `purchase`), `url`,
|
||||
`page_path`, `referer`, `referer_host`, `title`.
|
||||
|
||||
**Атрибуция:** `utm_source`, `utm_medium`, `utm_campaign`, `utm_content`,
|
||||
`utm_term`.
|
||||
|
||||
**Паспорт визита:** `device_category`, `browser`, `operating_system`,
|
||||
`region_country`, `region_city`.
|
||||
|
||||
**Торговый блок, массивами как в источнике:** `product_id`, `product_name`,
|
||||
`product_category`, `product_price`, `product_quantity`,
|
||||
`product_event_type`, `purchase_id`, `purchase_revenue`, `purchase_coupon`.
|
||||
|
||||
Три колонки в источнике не лежат и считаются здесь:
|
||||
|
||||
| Колонка | Как считается | Зачем |
|
||||
|---|---|---|
|
||||
| `event_hour` | `toHour(event_time)` | суточный профиль спрашивают в каждом отчете |
|
||||
| `page_path` | путь `url` без домена и параметров | группировка по странице без разбора строки в каждом запросе |
|
||||
| `referer_host` | домен `referer` | «откуда пришли» одним значением, а не полным адресом |
|
||||
|
||||
Служебных `_load_id` и `_load_ts` у витрины нет, и это не забывчивость. Их
|
||||
нет и у `dds.event_v`: событие в DDS не грузят, а читают из ODS, поэтому
|
||||
координат собственной загрузки у него не бывает. Служебная колонка называет
|
||||
запуск, который положил строку на диск, — здесь на диск никто ничего не
|
||||
кладет.
|
||||
|
||||
## Ловушка задания
|
||||
|
||||
Массивы `product_*` тянет развернуть через `ARRAY JOIN` — так плоская
|
||||
таблица становится по-настоящему плоской. Разворачивать нельзя, и вреда
|
||||
здесь два.
|
||||
|
||||
Очевидный: у события с тремя позициями получилось бы три строки, зерно
|
||||
витрины перестало бы совпадать с зерном источника, а вместе с ним развалилось
|
||||
бы и правило формы — такую витрину уже пришлось бы материализовать.
|
||||
|
||||
Неочевидный и куда более разрушительный: `ARRAY JOIN` — соединение
|
||||
внутреннее, и строку с пустым массивом он выбрасывает. Торговый блок пуст у
|
||||
всех событий, кроме корзин и покупок, то есть у подавляющего большинства.
|
||||
Так «плоская таблица событий» превращается в таблицу товарных позиций и
|
||||
теряет девятнадцать строк из двадцати — а по имени и по составу колонок она
|
||||
по-прежнему выглядит таблицей событий. Ошибка не падает и ничего не
|
||||
подсвечивает.
|
||||
|
||||
Массивы едут в договор как есть; разворачивает их тот, кому нужна позиция
|
||||
заказа, — и делает это уже в своей витрине, с другим зерном и другим именем.
|
||||
|
||||
## Как проверить себя
|
||||
|
||||
Деловой ключ — `event_id` внутри дня. Собери прямой ответ из `dds.event_v`
|
||||
за тот же день, соедини с витриной полным внешним соединением и посчитай
|
||||
строки, где ключа нет с одной из сторон или где различаются значения
|
||||
деловых колонок. Ноль — зеленый.
|
||||
|
||||
Отдельно проверяются три производные колонки: `event_hour`, `page_path` и
|
||||
`referer_host` должны совпасть с тем же выражением, посчитанным по
|
||||
`dds.event_v` на лету.
|
||||
|
||||
Если строк в витрине оказалось в разы меньше, чем в источнике, а остались в
|
||||
ней одни корзины и покупки — это развернутые массивы: `ARRAY JOIN` выбросил
|
||||
все события с пустым торговым блоком.
|
||||
|
||||
## Приемка
|
||||
|
||||
- [ ] `dm.events_enriched_v` создано, физических таблиц под ним нет;
|
||||
- [ ] список колонок в представлении явный;
|
||||
- [ ] число строк и набор `event_id` совпадают с `dds.event_v` за каждый
|
||||
прожитый день;
|
||||
- [ ] задача `events_enriched` в `homework_check` зеленая;
|
||||
- [ ] задачи в `dm_transform` **нет** — и не должно быть.
|
||||
@@ -0,0 +1,121 @@
|
||||
# Задание 3: `dm.session_overview`
|
||||
|
||||
**Чему учит:** агрегат хранит слагаемые, а не готовые средние и доли.
|
||||
Среднее по строкам витрины не складывается никогда, а уникальных нельзя
|
||||
складывать даже тогда, когда на сегодняшних данных сумма сходится.
|
||||
|
||||
## Роль витрины
|
||||
|
||||
Дневной портрет визитов в разрезе устройств: сколько было сессий, какие они
|
||||
были длинные, сколько страниц смотрели и как часто доходили до покупки.
|
||||
Вопрос витрины — «как люди ходят по магазину с телефона и с настольного
|
||||
компьютера, и чем эти визиты отличаются».
|
||||
|
||||
Сессия на стенде — расчетная сущность, ее уже нарезал слой DDS. Собирать
|
||||
визиты заново из событий не нужно и нельзя: это была бы вторая нарезка,
|
||||
которая тихо разойдется с первой.
|
||||
|
||||
Витрина с таким именем была и на прошлом стенде, но зерно у нее сменилось,
|
||||
и сменилось не по вкусу. Там визитов как сущности не было, и витрина
|
||||
нарезала их сама, строкой на визит. Здесь визит — сущность слоя DDS, и
|
||||
витрина, повторяющая его зерно, оказалась бы обычным представлением по
|
||||
[правилу формы](../architecture/dm.md): копией сессий с другими именами
|
||||
колонок. Чтобы задание осталось про дневной агрегат, витрина поднялась на
|
||||
зерно выше — до дня и среза.
|
||||
|
||||
## Зерно и форма
|
||||
|
||||
Зерно — **день сессии × категория устройства**. Источник —
|
||||
`dds.session_v`, у него зерно визита, значит зерно сменилось и витрина
|
||||
материализуется: `dm.session_overview_rep` / `_dist`, донор `_stage_rep` /
|
||||
`_stage_dist`, публичное `dm.session_overview_v`. Ключ партиции — день.
|
||||
|
||||
Категория устройства взята из сессии как есть: у куки она постоянна, и слой
|
||||
DDS уже перевел код источника в слово. Словарь расшифровки шире, чем мир
|
||||
стенда: слов в нем четыре, а встречаются три — `desktop`, `mobile` и
|
||||
`tablet`. Строку под `tv` заводить незачем, она возникнет сама, если такие
|
||||
визиты появятся.
|
||||
|
||||
## Состав колонок
|
||||
|
||||
| Колонка | Тип | Что это |
|
||||
|---|---|---|
|
||||
| `report_date` | `Date` | день сессии |
|
||||
| `device_category` | `LowCardinality(String)` | категория устройства |
|
||||
| `sessions` | `UInt64` | число визитов |
|
||||
| `visitors` | `UInt64` | число разных `client_id` |
|
||||
| `bounce_sessions` | `UInt64` | визиты ровно из одного события |
|
||||
| `purchase_sessions` | `UInt64` | визиты, в которых была хотя бы одна покупка |
|
||||
| `duration_seconds_total` | `UInt64` | суммарная длительность визитов |
|
||||
| `events_total` | `UInt64` | событий во всех визитах |
|
||||
| `pageviews` | `UInt64` | просмотров страниц |
|
||||
| `cart_adds` | `UInt64` | добавлений в корзину |
|
||||
| `purchases` | `UInt64` | покупок |
|
||||
| `_load_id` | `String` | запуск, собравший день |
|
||||
| `_load_ts` | `DateTime64(3, 'UTC')` | когда собран |
|
||||
|
||||
Средней длительности и доли отказов в витрине нет намеренно. Хранить
|
||||
`avg_duration_seconds` было бы удобнее ровно один раз — пока смотрят одну
|
||||
строку. Тот, кто захочет среднее по дню целиком, сложит средние по трем
|
||||
устройствам и получит неправду: средние не складываются, они взвешиваются.
|
||||
Пара «`duration_seconds_total` и `sessions`» дает верный ответ на любом
|
||||
срезе, а деление стоит потребителю один вызов. То же и с долей отказов:
|
||||
храним `bounce_sessions` и `sessions`, долю считает тот, кто спрашивает.
|
||||
|
||||
С `visitors` история тоньше, и в ней главная ловушка задания. Складывать
|
||||
уникальных по строкам нельзя: один и тот же посетитель, попавший в две
|
||||
строки, будет посчитан дважды. Проверь на стенде — и увидишь, что сумма
|
||||
`visitors` по устройствам за день **сходится** с `dm.daily_traffic_v`.
|
||||
|
||||
Это не отменяет правила, а показывает, откуда берется совпадение: в мире
|
||||
стенда устройство приписано куке на всю жизнь ([словарь
|
||||
понятий](../../CONTEXT.md), «Паспорт куки»), поэтому одна кука лежит ровно
|
||||
в одной строке дня, и пересекаться строкам не в чем.
|
||||
|
||||
Совпадение это — свойство мира, а не свойство витрины. Появятся визиты
|
||||
одной куки с двух устройств — сумма поедет, и ни одна проверка об этом не
|
||||
скажет: отчет просто начнет тихо завышать аудиторию. Отсюда правило,
|
||||
которое стоит унести с задания: складывать уникальных нельзя даже там, где
|
||||
сегодня получается. Число посетителей дня спрашивают у
|
||||
`dm.daily_traffic_v`, где оно посчитано на своем зерне.
|
||||
|
||||
## Ритм пересборки
|
||||
|
||||
Источник — сессии: день замерзает, как только прожит. Объем прогона тот же,
|
||||
что в задании 2, — хвост от первого несобранного прожитого дня; механика та
|
||||
же: донор и замена дневных партиций.
|
||||
|
||||
## Ключ шардирования
|
||||
|
||||
Твое решение по [правилу донора](../architecture/storage.md), «Раскладка по
|
||||
шардам». Здесь у измерения всего три встречающихся значения, и распределены
|
||||
они неровно: планшеты — тонкая полоса, а телефоны и настольные машины держат
|
||||
основную массу. Прежде чем брать хеш от одного `device_category`, посчитай,
|
||||
сколько строк ляжет на каждый шард, и назови, чем тебя устраивает результат.
|
||||
|
||||
## Как проверить себя
|
||||
|
||||
Деловой ключ — `device_category` внутри дня. Собери прямой агрегат из
|
||||
`dds.session_v` за день, соедини с `dm.session_overview_v` полным внешним
|
||||
соединением и посчитай строки, где ключа нет с одной из сторон или где
|
||||
расходится любой счетчик. Ноль — зеленый.
|
||||
|
||||
Две проверки покрупнее ловят грубую ошибку сразу:
|
||||
|
||||
- сумма `sessions` за день равна числу строк `dds.session_v` этого дня:
|
||||
ни один визит не потерян и ни один не задвоен;
|
||||
- сумма `events_total` за день равна сумме `events_total` по сессиям того
|
||||
же дня.
|
||||
|
||||
Если суммы событий сходятся, а сумма сессий больше числа визитов — почти
|
||||
наверняка витрина собрана из событий, а не из сессий.
|
||||
|
||||
## Приемка
|
||||
|
||||
- [ ] пять объектов витрины созданы, донор объявлен через `AS` и шардируется
|
||||
ключом цели;
|
||||
- [ ] `dm.session_overview_v` перечисляет колонки явно;
|
||||
- [ ] источник витрины — `dds.session_v`, своей нарезки визитов в ней нет;
|
||||
- [ ] средних и долей в физической таблице нет;
|
||||
- [ ] задача `session_overview` в `homework_check` зеленая;
|
||||
- [ ] задача витрины добавлена в `dm_transform`.
|
||||
@@ -0,0 +1,114 @@
|
||||
# Задание 2: `dm.top_pages_daily`
|
||||
|
||||
**Чему учит:** зерно сменилось — значит витрина материализуется, живет
|
||||
дневными партициями и встает в конвейер. Первая настоящая витрина: пять
|
||||
объектов, донор, замена партиции, задача в `dm_transform`.
|
||||
|
||||
## Роль витрины
|
||||
|
||||
Популярность страниц по дням: сколько раз страницу открыли, сколько разных
|
||||
посетителей ее видели и что на ней делали кроме просмотра.
|
||||
|
||||
Торговое событие садится на ту страницу, где случилось: корзина — на
|
||||
карточку товара, покупка — на страницу подтверждения заказа. Поэтому у
|
||||
карточки товара рядом стоят просмотры и корзины, и их отношение —
|
||||
конверсия карточки — главный вопрос к этой витрине: какие товары кладут в
|
||||
корзину, едва открыв, а какие смотрят и уходят.
|
||||
|
||||
Воронки «просмотр → корзина → покупка» здесь нет и быть не может: корзина и
|
||||
покупка живут на разных страницах, и связать их шаги в одну цепочку на
|
||||
зерне страницы не из чего. Путь одного человека собирают из событий или из
|
||||
сессий, а витрина страниц отвечает про страницу.
|
||||
|
||||
## Зерно и форма
|
||||
|
||||
Зерно — **день события × страница**. Источник — события, у него зерно
|
||||
события, значит зерно сменилось и витрина материализуется:
|
||||
`dm.top_pages_daily_rep` / `_dist`, донор `_stage_rep` / `_stage_dist`,
|
||||
публичное `dm.top_pages_daily_v`.
|
||||
|
||||
Страница — это `page_path`, путь `url` без домена и параметров запроса;
|
||||
считается он здесь же, из колонки `url` источника. Полный адрес в зерно не
|
||||
берется: с параметрами запроса одна и та же страница рассыпалась бы на
|
||||
сотни строк, и витрина перестала бы отвечать на свой вопрос.
|
||||
|
||||
Ключ партиции — день: он же единица пересборки.
|
||||
|
||||
Источник — `dds.event_v`, публичный договор события. Витрина задания 1
|
||||
источником не служит: витрина слоя DM читает договор DDS, как и стендовые
|
||||
витрины, и не зависит от того, выполнена ли соседняя домашка.
|
||||
|
||||
## Состав колонок
|
||||
|
||||
| Колонка | Тип | Что это |
|
||||
|---|---|---|
|
||||
| `report_date` | `Date` | день события |
|
||||
| `page_path` | `LowCardinality(String)` | путь страницы |
|
||||
| `pageviews` | `UInt64` | число событий `pageview` на странице |
|
||||
| `visitors` | `UInt64` | число разных `client_id` на странице |
|
||||
| `cart_adds` | `UInt64` | число событий `add_to_cart` на странице |
|
||||
| `purchases` | `UInt64` | число событий `purchase` на странице |
|
||||
| `_load_id` | `String` | запуск, собравший день |
|
||||
| `_load_ts` | `DateTime64(3, 'UTC')` | когда собран |
|
||||
|
||||
Счетчики условные: строку дают все три типа событий, а различает их
|
||||
`countIf`. Отфильтровать источник по `event_type = 'pageview'`, как
|
||||
подсказывает имя витрины, — значит получить витрину из одних просмотров:
|
||||
страницы останутся на месте, а `cart_adds` и `purchases` у всех строк
|
||||
обнулятся. Ошибка тихая: витрина выглядит собранной, и главный ее вопрос —
|
||||
конверсия карточки — молча отвечает «ноль везде».
|
||||
|
||||
## Ритм пересборки
|
||||
|
||||
Источник — события: день замерзает сразу, как только прожит. Значит
|
||||
пересобирать нужно только то, что еще не собрано: хвост дней, которых в
|
||||
витрине нет, — от первого несобранного прожитого дня до последнего
|
||||
прожитого. Готовые дни трогать незачем, а вот дыру, оставленную частичным
|
||||
отказом, объем прогона обязан подобрать: ищи не последний собранный день, а
|
||||
первый несобранный.
|
||||
|
||||
Механика — сборка отрезка в донор одной вставкой и замена дневных партиций
|
||||
по одной, как у `revenue_daily`.
|
||||
|
||||
## Ключ шардирования
|
||||
|
||||
Твое решение по [правилу донора](../architecture/storage.md), «Раскладка по
|
||||
шардам». Подумай, чем эта витрина отличается от `revenue_daily`: там кроме
|
||||
дня было одно измерение из шести близких по объему значений, здесь —
|
||||
страницы, которых много и которые по популярности очень неровные.
|
||||
|
||||
## Как проверить себя
|
||||
|
||||
Деловой ключ — `page_path` внутри дня. Собери прямой агрегат из
|
||||
`dds.event_v` за день, соедини с `dm.top_pages_daily_v` полным внешним
|
||||
соединением по ключу и посчитай строки, где ключа нет с одной из сторон или
|
||||
где расходится любой из четырех счетчиков. Ноль — зеленый.
|
||||
|
||||
Две проверки попроще, ими удобно ловить грубую ошибку до полного
|
||||
соединения:
|
||||
|
||||
- сумма `pageviews` за день равна числу событий `pageview` этого дня в
|
||||
`dds.event_v`;
|
||||
- сумма `purchases` за день равна числу событий `purchase` этого дня.
|
||||
|
||||
Проверки ловят разное. Фильтр `WHERE event_type = 'pageview'` первая из них
|
||||
пропускает: отбор строк по типу не меняет счет событий того же типа.
|
||||
Расхождение покажет только вторая — вместе с `cart_adds` обнулятся и
|
||||
`purchases`. Проверку выбирают по тому, умеет ли она покраснеть на твоей
|
||||
ошибке, а не по тому, как быстро она пишется: [проверка, которая не умеет
|
||||
краснеть, бесполезна](../architecture/testing.md).
|
||||
|
||||
`visitors` складывать по строкам нельзя: один посетитель ходит по разным
|
||||
страницам и в сумме посчитается несколько раз. Число посетителей дня
|
||||
спрашивают у `dm.daily_traffic_v`, а не у этой витрины.
|
||||
|
||||
## Приемка
|
||||
|
||||
- [ ] пять объектов витрины созданы, донор объявлен через `AS` и шардируется
|
||||
ключом цели;
|
||||
- [ ] `dm.top_pages_daily_v` перечисляет колонки явно, включая `_load_id` и
|
||||
`_load_ts`;
|
||||
- [ ] объем прогона выводится из состояния витрины, а не задается руками;
|
||||
- [ ] задача `top_pages_daily` в `homework_check` зеленая;
|
||||
- [ ] задача витрины добавлена в `dm_transform`, повторный прогон конвейера
|
||||
результат не меняет.
|
||||
@@ -0,0 +1,150 @@
|
||||
# Задание 5: `dm.utm_effectiveness`
|
||||
|
||||
**Чему учит:** приставка `declared_` в имени колонки — предупреждение, а не
|
||||
украшение. Она говорит: это заявка клиента, а не деньги отчетности. Урок
|
||||
здесь дает конфликт: суммы не сойдутся, и раскапывать почему предстоит
|
||||
самому.
|
||||
|
||||
Правило стенда: **поведение и атрибуцию считаем по трекеру, деньги — по
|
||||
бэкенду**. Эта витрина — единственное разрешенное исключение: атрибуции по
|
||||
UTM у бэкенда нет, метки живут только в кликстриме. Раз считать иначе
|
||||
нечем, клиентская сумма берется, но называется своим именем.
|
||||
|
||||
## Роль витрины
|
||||
|
||||
Эффективность рекламных каналов по дням: сколько трафика привел канал,
|
||||
сколько корзин и покупок в нем случилось и на какую сумму эти покупки, по
|
||||
словам клиента.
|
||||
|
||||
## Атрибуция: по событию, а не по визиту
|
||||
|
||||
Метки едут на каждом событии, и витрина группирует события по их
|
||||
собственным меткам. Модели «первое касание» или «последнее касание» здесь
|
||||
нет: их построение — отдельная тема, а это витрина трафика по меткам.
|
||||
|
||||
`dds.session_v` при этом несет UTM первого события визита, и ответ «какой
|
||||
канал привел» из сессии и из этой витрины будет разным. Это не
|
||||
ошибка ни одной из сторон — это два разных вопроса, и в отчете их надо
|
||||
различать.
|
||||
|
||||
## Прямой трафик из витрины не выбрасывается
|
||||
|
||||
Соблазн отфильтровать строки без меток велик: витрина же про рекламу.
|
||||
Отфильтровать нельзя по двум причинам. Первая деловая: прямой трафик —
|
||||
обычно самый крупный «канал» магазина, и отчет, где его нет, врет о доле
|
||||
рекламы. Вторая техническая: как только часть событий выпала, итог витрины
|
||||
перестал сходиться с итогом источника, и проверить сборку стало нечем.
|
||||
|
||||
Пустые метки остаются пустыми строками, как в источнике. Своего слова для
|
||||
«прямого захода» витрина не изобретает: чем меньше выдуманных значений, тем
|
||||
меньше расшифровывать читателю.
|
||||
|
||||
## Зерно и форма
|
||||
|
||||
Зерно — **день события × `utm_source` × `utm_medium` × `utm_campaign`**.
|
||||
Зерно источника — событие, значит витрина материализуется:
|
||||
`dm.utm_effectiveness_rep` / `_dist`, донор `_stage_rep` / `_stage_dist`,
|
||||
публичное `dm.utm_effectiveness_v`. Ключ партиции — день.
|
||||
|
||||
Источник — `dds.event_v`, публичный договор события: витрина слоя DM читает
|
||||
договор DDS, а не соседнюю витрину.
|
||||
|
||||
## Состав колонок
|
||||
|
||||
| Колонка | Тип | Что это |
|
||||
|---|---|---|
|
||||
| `report_date` | `Date` | день события |
|
||||
| `utm_source` | `LowCardinality(String)` | источник кампании |
|
||||
| `utm_medium` | `LowCardinality(String)` | канал кампании |
|
||||
| `utm_campaign` | `String` | имя кампании |
|
||||
| `visitors` | `UInt64` | число разных `client_id` |
|
||||
| `pageviews` | `UInt64` | просмотров страниц |
|
||||
| `add_to_carts` | `UInt64` | добавлений в корзину |
|
||||
| `purchases` | `UInt64` | покупок |
|
||||
| `declared_revenue` | `Decimal(18, 2)` | выручка покупок глазами клиента |
|
||||
| `_load_id` | `String` | запуск, собравший день |
|
||||
| `_load_ts` | `DateTime64(3, 'UTC')` | когда собран |
|
||||
|
||||
`declared_revenue` складывает `purchase_revenue[1]` событий `purchase`.
|
||||
Индекс не описка: блок `purchase*` едет параллельными массивами, и у события
|
||||
покупки в них ровно один элемент — контракт источника обещает один номер
|
||||
заказа и одну объявленную сумму на событие. Тем же индексом берет свою
|
||||
сторону стендовая сверка `purchase_vs_orders`.
|
||||
|
||||
Приводить тип не нужно: в договоре `purchase_revenue` уже
|
||||
`Array(Decimal(18, 2))`. `Float64` эта сумма была только в выгрузке
|
||||
источника, и ODS перевел ее в точные деньги на приеме — сложение Decimal
|
||||
точное, и где стоит `CAST`, до или после `sum`, на результат не влияет.
|
||||
Витрина просто хранит то, что сложила, в `Decimal(18, 2)`.
|
||||
|
||||
`utm_campaign` оставлена обычной строкой: имен кампаний со временем
|
||||
становится много, и словарь `LowCardinality` на растущем множестве
|
||||
перестает окупаться.
|
||||
|
||||
## Ритм пересборки
|
||||
|
||||
Источник — события: день замерзает сразу. Объем прогона и механика те же,
|
||||
что в задании 2: хвост несобранных дней, донор, замена дневных партиций.
|
||||
|
||||
## Ключ шардирования
|
||||
|
||||
Твое решение по [правилу донора](../architecture/storage.md), «Раскладка по
|
||||
шардам». Зерно здесь составное, из трех колонок, и решение «взять хеш от
|
||||
одной из них» надо защищать отдельно: назови, какая доля строк придется на
|
||||
самое частое значение выбранной колонки.
|
||||
|
||||
## Конфликт, ради которого витрина и написана
|
||||
|
||||
Собери за один и тот же день две суммы: `sum(declared_revenue)` из своей
|
||||
витрины и `sum(revenue)` из `dm.revenue_daily_v`. Они не сойдутся, и это
|
||||
верное поведение обеих витрин.
|
||||
|
||||
Базы у двух сумм при этом сравнимые: `revenue_daily` складывает цену позиций
|
||||
на количество и ни скидки, ни доставки в себя не берет — ровно то, что видит
|
||||
клиент. Значит все расхождение до копейки объясняется тем, что случилось с
|
||||
заказом, а не тем, как считали деньги. Ищи ответы среди таких причин:
|
||||
|
||||
- отмененный заказ из кликстрима никуда не девается: событие покупки
|
||||
случилось, а `revenue_daily` считает только оплаченные;
|
||||
- событие покупки может потеряться по дороге, а заказ при этом доехать —
|
||||
и наоборот, событие приехать дважды;
|
||||
- клиент назвал сумму, отличную от серверной суммы позиций: генератор
|
||||
делает это намеренно у одного-двух заказов из ста.
|
||||
|
||||
Как эти же расхождения выглядят на зерне заказа, показывает стендовая
|
||||
витрина `dm.purchase_vs_orders_v` с ее классами расхождений: это готовый
|
||||
ответ на вопрос «а сколько именно и из-за чего». Запиши свой вывод —
|
||||
в двух-трех фразах, своими словами.
|
||||
|
||||
Проверять сходимость этих двух сумм не нужно и нельзя: они и не должны
|
||||
сходиться.
|
||||
|
||||
## Как проверить себя
|
||||
|
||||
Деловой ключ — тройка `utm_source`, `utm_medium`, `utm_campaign` внутри
|
||||
дня. Собери прямой агрегат из `dds.event_v` за день, соедини с
|
||||
`dm.utm_effectiveness_v` полным внешним соединением и посчитай строки, где
|
||||
ключа нет с одной из сторон или где расходится любая из пяти величин. Ноль —
|
||||
зеленый.
|
||||
|
||||
Две проверки покрупнее ловят самые частые ошибки:
|
||||
|
||||
- сумма `pageviews`, `add_to_carts` и `purchases` за день равна числу
|
||||
событий соответствующего типа в `dds.event_v` за тот же день;
|
||||
- сумма `declared_revenue` за день равна сумме `purchase_revenue[1]` по всем
|
||||
событиям `purchase` этого дня.
|
||||
|
||||
Обе не сойдутся, если прямой трафик отфильтрован, и разойдутся ровно на его
|
||||
долю — по этому следу отфильтрованные строки и находят. `visitors`, как и в
|
||||
задании 3, по строкам не складывается: один посетитель приходит по разным
|
||||
меткам.
|
||||
|
||||
## Приемка
|
||||
|
||||
- [ ] пять объектов витрины созданы, донор объявлен через `AS` и шардируется
|
||||
ключом цели;
|
||||
- [ ] строки без UTM-меток в витрине остались;
|
||||
- [ ] `declared_revenue` хранится в `Decimal(18, 2)`;
|
||||
- [ ] расхождение с `dm.revenue_daily_v` объяснено письменно;
|
||||
- [ ] задача `utm_effectiveness` в `homework_check` зеленая;
|
||||
- [ ] задача витрины добавлена в `dm_transform`.
|
||||
@@ -512,8 +512,14 @@ Kafka день переигрывается генератором заново:
|
||||
`dds.identity_map_v`: опыт на ClickHouse 26.3 показал, что публичные `_v`
|
||||
скрывают ко-локацию от локальной подстановки.
|
||||
- `events_enriched_v`, `top_pages_daily_v`, `session_overview_v`,
|
||||
`dq_errors_daily_v` — переезжают на новую модель без смены роли: источник —
|
||||
`dds.event_v`, не `ods.event`.
|
||||
`dq_errors_daily_v` — переезжают на новую модель без смены роли. Источник
|
||||
и зерно у двух из них меняет сама новая модель, и это зафиксировано ТЗ
|
||||
домашек ([`docs/labs/`](../labs/README.md)):
|
||||
`session_overview_v` читает `dds.session_v` и поднимается на зерно «день ×
|
||||
срез», потому что визит стал сущностью DDS и витрина его зерна была бы
|
||||
копией сессий; `dq_errors_daily_v` читает таблицы `ods.*_errors`, у
|
||||
которых в DDS представительства нет. Остальные две читают `dds.event_v`,
|
||||
а не `ods.event`.
|
||||
- `dm.dq_summary` хранит текущее качество дня и обновляется партиционной
|
||||
заменой (политика «без TRUNCATE»).
|
||||
|
||||
@@ -650,9 +656,12 @@ ADR 0002 с приёмочными совпадают почти целиком.
|
||||
расхождение часов клиент/коллектор — туман карты, вернётся своим тикетом.
|
||||
- Лабы и курс: v2 — другой стенд, лабы для него пишутся с нуля отдельной
|
||||
работой после этой спеки; редизайн лаб v1 (#7) остаётся в v1 и сюда не
|
||||
переносится. Спека даёт будущим лабам только опорные точки — контрольные
|
||||
числа описи (сверка, идентичность). Явное следствие: после этапа 9
|
||||
стенд работает, но учебного пути на нём ещё нет.
|
||||
переносится. Исключение — домашки слоя DM: ТЗ витрин менти и даг
|
||||
самопроверки забраны в этап 4 резолюцией #135, потому что витрина менти и
|
||||
конвейер, в который она встает, проектируются вместе. Единый стиль блока
|
||||
домашек — за работой учебного пути. Спека даёт будущим лабам только
|
||||
опорные точки — контрольные числа описи (сверка, идентичность). Явное
|
||||
следствие: после этапа 9 стенд работает, но учебного пути на нём ещё нет.
|
||||
- Инкрементальный ETL (#8) — свой issue.
|
||||
- Реплики (2×2), HAProxy, репликационная эксплуатация — в лекцию, не в стенд.
|
||||
- TLS между нодами, настоящее хранилище и ротация секретов, внешние учётные
|
||||
|
||||
Reference in new issue
Block a user