docs(labs): упрощен язык учебных заданий

Зачем: помочь менти понять действия, проверить результат и оценить освоенное умение.

Что: вычитаны 13 заданий основной программы и общий индекс. Итоговые списки обращены к учащемуся, убраны история разработки и повторы. Правила учебного текста записаны в программе, из AGENTS.md добавлена ссылка.

Проверка: git diff --check, локальные ссылки и ссылки на измененные заголовки прошли. Блоки кода и таблицы требований совпадают с исходными. Упражнения на стенде повторно не выполнялись: изменен только текст.
This commit is contained in:
ddadmin committed 2026-09-13 11:01:35 -04:00
1 parent a03838b953
commit 4058e18864
16 files changed
+287 -269

No files matched your search

+3
View File
@@ -81,6 +81,9 @@ Airflow) и названия из кода. Если для понятия ес
писал: простые слова, короткие фразы, сложную мысль поясняй при первом писал: простые слова, короткие фразы, сложную мысль поясняй при первом
упоминании. Если понятность и буквальная точность спорят — выбирай понятность. упоминании. Если понятность и буквальная точность спорят — выбирай понятность.
При создании или редактировании лабораторных следуй
[правилам учебного текста](docs/specs/2026-09-01-labs-program.md#как-писать-задания).
Внутренние рассуждения и промежуточные пометки по ходу работы веди на Внутренние рассуждения и промежуточные пометки по ходу работы веди на
английском — он экономнее по токенам. На русском остаётся всё, что видит и английском — он экономнее по токенам. На русском остаётся всё, что видит и
хранит проект: итоговые ответы, документы, комментарии в коде и SQL, сообщения хранит проект: итоговые ответы, документы, комментарии в коде и SQL, сообщения
+9 -14
View File
@@ -1,7 +1,7 @@
# Задание 1: `dm.events_enriched_v` # Задание 1: `dm.events_enriched_v`
**Чему учит:** строить слой как договор о данных, а не как их копию. **Чему учит:** подготовить удобный для анализа набор полей события
Оставлять витрину представлением, если зерно источника не меняется. и обойтись представлением, сохранив одну строку на событие.
## Сначала прочитать ## Сначала прочитать
@@ -9,7 +9,7 @@
- [Как применить свой DDL](README.md#как-применить-свой-ddl): файл для решения - [Как применить свой DDL](README.md#как-применить-свой-ddl): файл для решения
и подключение с правами на создание витрины. и подключение с правами на создание витрины.
## Роль витрины ## Для чего нужна витрина
В `dds.event_v` 44 колонки, включая технические поля источника и массивы В `dds.event_v` 44 колонки, включая технические поля источника и массивы
товаров. Для анализа событий в Superset нужны деловые поля и три готовых товаров. Для анализа событий в Superset нужны деловые поля и три готовых
@@ -88,15 +88,10 @@ DDS читают из ODS без отдельной загрузки. Эти с
Отдельно сверьте `event_hour`, `page_path` и `referer_host` с теми же Отдельно сверьте `event_hour`, `page_path` и `referer_host` с теми же
выражениями, рассчитанными прямо по `dds.event_v`. выражениями, рассчитанными прямо по `dds.event_v`.
Если строк в витрине в разы меньше, чем в источнике, и остались только ## Что должно получиться
события корзины и покупки, это признак развернутых массивов: `ARRAY JOIN`
исключил все события с пустым массивом товаров.
## Приемка - [ ] у вас есть `dm.events_enriched_v` с явно перечисленными колонками;
- [ ] вы сверили число строк и набор `event_id` с `dds.event_v` за каждый
- [ ] представление `dm.events_enriched_v` создано без физических таблиц; прожитый день; расхождений нет;
- [ ] список колонок в представлении задан явно; - [ ] вы запустили `events_enriched` в `homework_check`, проверка успешна;
- [ ] число строк и набор `event_id` совпадают с `dds.event_v` за каждый - [ ] витрина работает без физической таблицы и задачи в `dm_transform`.
прожитый день;
- [ ] задача `events_enriched` в `homework_check` завершилась успешно;
- [ ] задачи для этой витрины в `dm_transform` нет.
+12 -12
View File
@@ -1,14 +1,14 @@
# Задание 2: `dm.top_pages_daily` # Задание 2: `dm.top_pages_daily`
**Чему учит:** создавать витрину с новым зерном: пять объектов, донор и **Чему учит:** собрать дневные показатели страниц и настроить их
замена дневных партиций. Включать ее пересборку в `dm_transform`. обновление в `dm_transform` через замену партиций.
## Сначала прочитать ## Сначала прочитать
- [«Хранилище и его слои»](../handbook/storage-layers.md); - [«Хранилище и его слои»](../handbook/storage-layers.md);
- [«Потоки Airflow и даги модельного времени»](../handbook/airflow-dags.md). - [«Потоки Airflow и даги модельного времени»](../handbook/airflow-dags.md).
## Роль витрины ## Для чего нужна витрина
По дневным показателям страниц аналитик сравнивает просмотры, посетителей и По дневным показателям страниц аналитик сравнивает просмотры, посетителей и
действия с товарами. Добавление в корзину происходит на карточке товара, действия с товарами. Добавление в корзину происходит на карточке товара,
@@ -98,13 +98,13 @@
Сумма `visitors` по страницам повторно учитывает посетителя, открывшего Сумма `visitors` по страницам повторно учитывает посетителя, открывшего
несколько страниц. Число посетителей за день берите из `dm.daily_traffic_v`. несколько страниц. Число посетителей за день берите из `dm.daily_traffic_v`.
## Приемка ## Что должно получиться
- [ ] созданы пять объектов, донор объявлен через `AS` и шардируется ключом - [ ] у вас есть пять объектов витрины; донор объявлен через `AS` и
цели; использует тот же ключ шардирования, что и целевая таблица;
- [ ] в `dm.top_pages_daily_v` явно перечислены колонки, включая `_load_id` - [ ] вы явно перечислили колонки в `dm.top_pages_daily_v`, включая
и `_load_ts`; `_load_id` и `_load_ts`;
- [ ] объем прогона определяется по состоянию витрины без ручного задания; - [ ] ваш запрос сам определяет дни пересборки по состоянию витрины;
- [ ] задача `top_pages_daily` в `homework_check` завершилась успешно; - [ ] вы запустили `top_pages_daily` в `homework_check`, проверка успешна;
- [ ] задача витрины добавлена в `dm_transform`, повторный прогон конвейера - [ ] вы включили витрину в `dm_transform` и убедились, что повторный
не меняет результат. прогон конвейера не меняет результат.
+14 -17
View File
@@ -1,24 +1,21 @@
# Задание 3: `dm.session_overview` # Задание 3: `dm.session_overview`
**Чему учит:** хранить слагаемые, а не готовые средние и доли. Понимать, **Чему учит:** хранить слагаемые для расчета средних и долей и понимать,
почему сумма `visitors` по строкам сейчас сходится, но начнет завышать когда сложение числа посетителей по устройствам завышает аудиторию.
аудиторию, если у одной куки появятся визиты с двух устройств.
## Сначала прочитать ## Сначала прочитать
- [«Хранилище и его слои»](../handbook/storage-layers.md); - [«Хранилище и его слои»](../handbook/storage-layers.md);
- [«Потоки Airflow и даги модельного времени»](../handbook/airflow-dags.md). - [«Потоки Airflow и даги модельного времени»](../handbook/airflow-dags.md).
## Роль витрины ## Для чего нужна витрина
Витрина нужна, чтобы по дням сравнивать визиты с телефона и компьютера: их Витрина нужна, чтобы по дням сравнивать визиты с телефона и компьютера: их
число, длительность, просмотры страниц и частоту покупок. число, длительность, просмотры страниц и частоту покупок.
На прошлом стенде витрина выделяла визиты сама. Теперь их рассчитывают в Сессии уже рассчитаны в DDS. Из них вы соберете дневные показатели по
DDS, а повторять зерно сессий ради представления с переименованными устройствам. Почему такие итоги хранят в отдельной таблице, объясняет
колонками незачем. Здесь вы учитесь дневной агрегации, поэтому зерно — день [справочник DM](../architecture/dm.md).
и устройство. Выбор описан в разделе «Форма: по зерну, не по цене запроса»
[справочника DM](../architecture/dm.md).
## Что сделать ## Что сделать
@@ -107,12 +104,12 @@ DDS, а повторять зерно сессий ради представле
повторного учета визитов; повторного учета визитов;
- сумма `events_total` равна сумме `events_total` по сессиям того же дня. - сумма `events_total` равна сумме `events_total` по сессиям того же дня.
## Приемка ## Что должно получиться
- [ ] созданы пять объектов, донор объявлен через `AS` и шардируется ключом - [ ] у вас есть пять объектов витрины; донор объявлен через `AS` и
цели; использует ключ шардирования целевой таблицы;
- [ ] в `dm.session_overview_v` колонки перечислены явно; - [ ] вы явно перечислили колонки в `dm.session_overview_v` и собрали
- [ ] источник — `dds.session_v`, повторного выделения визитов нет; показатели из готовых сессий `dds.session_v`;
- [ ] физическая таблица не хранит средние и доли; - [ ] в таблице хранятся слагаемые для средних и долей;
- [ ] задача `session_overview` в `homework_check` завершилась успешно; - [ ] вы запустили `session_overview` в `homework_check`, проверка успешна;
- [ ] задача витрины добавлена в `dm_transform`. - [ ] вы включили сборку витрины в `dm_transform`.
+13 -15
View File
@@ -1,15 +1,14 @@
# Задание 4: `dm.dq_errors_daily` # Задание 4: `dm.dq_errors_daily`
**Чему учит:** считать брак по дню загрузки и сохранять сводку дольше **Чему учит:** считать брак по дню загрузки и сохранять сводку дольше
исходных строк. Пересборка всех партиций после удаления источника по `TTL` исходных строк.
стирает историю, которую уже не восстановить.
## Сначала прочитать ## Сначала прочитать
- [«Хранилище и его слои»](../handbook/storage-layers.md); - [«Хранилище и его слои»](../handbook/storage-layers.md);
- [«Потоки Airflow и даги модельного времени»](../handbook/airflow-dags.md). - [«Потоки Airflow и даги модельного времени»](../handbook/airflow-dags.md).
## Роль витрины ## Для чего нужна витрина
Сводку открывают при росте брака в потоке: смотрят, каких ошибок стало Сводку открывают при росте брака в потоке: смотрят, каких ошибок стало
больше и сколько строк забраковано. Причину конкретной ошибки исследуют больше и сколько строк забраковано. Причину конкретной ошибки исследуют
@@ -97,17 +96,16 @@
`ods.event_errors_dist` и `ods.order_errors_dist`. Если она примерно вдвое `ods.event_errors_dist` и `ods.order_errors_dist`. Если она примерно вдвое
меньше, вероятная причина — чтение `_rep` вместо `_dist`. меньше, вероятная причина — чтение `_rep` вместо `_dist`.
Пустая витрина на чистом стенде — верный ответ: обе стороны пусты, Если в источниках нет брака, витрина должна быть пустой. В этом случае
расхождений ноль, проверка успешна. Требование хотя бы одной строки дало бы расхождений тоже ноль и проверка успешна.
постоянную ошибку, которую со временем стали бы игнорировать.
## Приемка ## Что должно получиться
- [ ] созданы пять объектов, донор объявлен через `AS` и шардируется ключом - [ ] у вас есть пять объектов витрины; донор объявлен через `AS` и
цели; использует ключ шардирования целевой таблицы;
- [ ] источники — таблицы `_dist` обоих видов брака; - [ ] вы собираете сводку из таблиц `_dist` для ошибок событий и заказов;
- [ ] прогон заменяет партиции только дней, еще имеющихся в источниках, и - [ ] ваш запрос пересобирает только дни, еще доступные в источниках,
сохраняет дни за пределами их срока хранения; сохраняя более старые итоги;
- [ ] задача `dq_errors_daily` в `homework_check` завершилась успешно, в том - [ ] вы запустили `dq_errors_daily` в `homework_check`, проверка успешна;
числе на чистом стенде без брака; отсутствие брака тоже дает успешный результат;
- [ ] задача витрины добавлена в `dm_transform`. - [ ] вы включили сборку витрины в `dm_transform`.
+12 -13
View File
@@ -1,15 +1,14 @@
# Задание 5: `dm.utm_effectiveness` # Задание 5: `dm.utm_effectiveness`
**Чему учит:** понимать предупреждение в приставке `declared_`: это сумма со **Чему учит:** различать сумму покупки со слов клиента (`declared_`) и
слов клиента, а не деньги отчетности. Самостоятельно находить причины серверную выручку, находить причины их расхождения.
расхождения двух сумм выручки.
## Сначала прочитать ## Сначала прочитать
- [«Хранилище и его слои»](../handbook/storage-layers.md); - [«Хранилище и его слои»](../handbook/storage-layers.md);
- [«Потоки Airflow и даги модельного времени»](../handbook/airflow-dags.md). - [«Потоки Airflow и даги модельного времени»](../handbook/airflow-dags.md).
## Роль витрины ## Для чего нужна витрина
Аналитик сравнивает рекламные каналы по дням: трафик, добавления в корзину, Аналитик сравнивает рекламные каналы по дням: трафик, добавления в корзину,
покупки и заявленные клиентом суммы. покупки и заявленные клиентом суммы.
@@ -44,7 +43,7 @@
| `_load_ts` | `DateTime64(3, 'UTC')` | Время сборки | | `_load_ts` | `DateTime64(3, 'UTC')` | Время сборки |
Для `declared_revenue` сложите `purchase_revenue[1]` событий `purchase`. В Для `declared_revenue` сложите `purchase_revenue[1]` событий `purchase`. В
параллельных массивах `purchase*` по контракту хранятся один номер заказа и параллельных массивах `purchase*` хранятся один номер заказа и
одна сумма на событие. В сверке `purchase_vs_orders` используют тот же одна сумма на событие. В сверке `purchase_vs_orders` используют тот же
индекс. индекс.
@@ -127,12 +126,12 @@
Сумма `visitors` по строкам повторно учитывает одного посетителя, Сумма `visitors` по строкам повторно учитывает одного посетителя,
приходящего по разным меткам. Для числа посетителей дня она не подходит. приходящего по разным меткам. Для числа посетителей дня она не подходит.
## Приемка ## Что должно получиться
- [ ] созданы пять объектов, донор объявлен через `AS` и шардируется ключом - [ ] у вас есть пять объектов витрины; донор объявлен через `AS` и
цели; использует ключ шардирования целевой таблицы;
- [ ] сохранены строки без UTM-меток; - [ ] вы сохранили строки без UTM-меток и используете `Decimal(18, 2)`
- [ ] `declared_revenue` хранится в `Decimal(18, 2)`; для `declared_revenue`;
- [ ] расхождение с `dm.revenue_daily_v` объяснено письменно; - [ ] вы записали, почему сумма отличается от `dm.revenue_daily_v`;
- [ ] задача `utm_effectiveness` в `homework_check` завершилась успешно; - [ ] вы запустили `utm_effectiveness` в `homework_check`, проверка успешна;
- [ ] задача витрины добавлена в `dm_transform`. - [ ] вы включили сборку витрины в `dm_transform`.
+11 -10
View File
@@ -8,7 +8,7 @@
- [«Немного Superset»](../handbook/superset.md); - [«Немного Superset»](../handbook/superset.md);
- [задание 3: `dm.session_overview`](1-3-session-overview.md). - [задание 3: `dm.session_overview`](1-3-session-overview.md).
## Роль графика ## На какой вопрос ответит график
В задании 3 вы собрали витрину `dm.session_overview_v`. Теперь покажите, с В задании 3 вы собрали витрину `dm.session_overview_v`. Теперь покажите, с
каких устройств чаще уходят после первой страницы. Постройте столбиковый каких устройств чаще уходят после первой страницы. Постройте столбиковый
@@ -60,8 +60,8 @@ FROM dm.session_overview_v;
## Как проверить себя ## Как проверить себя
График проверяется глазами, поэтому задачи для него в `homework_check` нет. Автоматической проверки этого задания нет. Сверьте числа на графике
В SQL Lab выполните запрос: с результатом запроса в SQL Lab:
```sql ```sql
SELECT device_category, SELECT device_category,
@@ -76,15 +76,16 @@ ORDER BY device_category;
проценты с одним знаком, как формат `.1%`, поэтому числа должны совпасть проценты с одним знаком, как формат `.1%`, поэтому числа должны совпасть
один в один. один в один.
## Приемка ## Что должно получиться
- [ ] создан физический датасет `dm.session_overview_v` в подключении - [ ] у вас есть физический датасет `dm.session_overview_v` в подключении
`ClickHouse`; `ClickHouse`;
- [ ] в датасете есть метрика «Доля отказов» с выражением - [ ] вы сохранили в нем метрику «Доля отказов» с выражением
`SUM(bounce_sessions) / SUM(sessions)` и форматом `.1%`; `SUM(bounce_sessions) / SUM(sessions)` и форматом `.1%`;
- [ ] для `device_category` и `report_date` заданы русские имена «Категория - [ ] вы назвали `device_category` и `report_date` по-русски: «Категория
устройства» и «Дата отчета»; устройства» и «Дата отчета»;
- [ ] сохранен столбиковый график доли отказов по категориям устройств без - [ ] у вас сохранен столбиковый график доли отказов по категориям устройств без
относительного фильтра по времени; относительного фильтра по времени;
- [ ] график сверен с запросом из SQL Lab; - [ ] вы сверили числа на графике с запросом из SQL Lab;
- [ ] стендовые объекты не изменены, дашборд не создан. - [ ] вы сохранили график отдельно, без создания дашборда и изменения
стендовых объектов.
+16 -13
View File
@@ -1,14 +1,14 @@
# Нарежь сессии сам # Нарежьте сессии сами
**Чему учит:** собрать сессии из событий и сверить их число с внешним **Чему учит:** собрать сессии из событий, сравнить их число с `VisitID`
эталоном. Найти причину расхождения в отдельных строках. источника и найти события, из-за которых расчеты расходятся.
## Сначала прочитать ## Сначала прочитать
- [Хранилище и его слои, раздел DDS](../handbook/storage-layers.md#dds-язык-бизнеса); - [Хранилище и его слои, раздел DDS](../handbook/storage-layers.md#dds-язык-бизнеса);
- [формат события: Идентификаторы и время](../formats/clickstream-event.md#идентификаторы-и-время). - [формат события: Идентификаторы и время](../formats/clickstream-event.md#идентификаторы-и-время).
## Роль сессии ## Для чего нужны сессии
Визит, или сессия, объединяет действия посетителя за один заход на сайт. Визит, или сессия, объединяет действия посетителя за один заход на сайт.
По визитам аналитик считает длительность и глубину просмотра. По визитам аналитик считает длительность и глубину просмотра.
@@ -71,19 +71,22 @@ ORDER BY EventDate, ClientID;
## Как проверить себя ## Как проверить себя
Результат задания проверяется глазами, поэтому задачи в `homework_check`
для него нет. Таблицы и представления создавать не нужно.
Сохраните исходный и уточненный расчеты, результаты сверки и найденные строки. Сохраните исходный и уточненный расчеты, результаты сверки и найденные строки.
Таблицы и представления создавать не нужно. Задачи в `homework_check`
для этого задания нет: вы проверяете расчет своим запросом.
**Предупреждение.** В `sql/dds/session_rebuild.sql` и В `sql/dds/session_rebuild.sql` и
`sql/dq/sessions_vs_reference.sql` лежат готовые запросы нарезки и сверки. `sql/dq/sessions_vs_reference.sql` лежат готовые запросы нарезки и сверки.
Откройте их после собственного решения и расследования. Сравните запросы Откройте их после собственного решения и расследования. Сравните запросы
со своими: копирование готового SQL пропускает расследование. со своими.
Сопоставление со стендовой нарезкой по `session_id` остается на ступень 4. Сопоставление со стендовой нарезкой по `session_id` остается на ступень 4.
## Приемка ## Что должно получиться
- [ ] запрос нарезает все события `ods.event_v` за все доступные дни; - [ ] ваш запрос собирает сессии из всех событий `ods.event_v` за все
- [ ] сверка через `FULL OUTER JOIN` дает сводку по дням и список кук; доступные дни;
- [ ] при разборе расхождения указаны кука, день, два события и причина; - [ ] у вас есть сводка расхождений по дням и список кук, полученные через
- [ ] уточненный расчет дает ноль расхождений во всех днях. `FULL OUTER JOIN`;
- [ ] вы объяснили причину одного расхождения и записали куку, день и два
события, на которых оно видно;
- [ ] вы уточнили расчет и получили ноль расхождений во всех днях.
+17 -18
View File
@@ -1,16 +1,16 @@
# Перепиши нарезку стенда # Перепишите нарезку стенда
**Чему учит:** изменить расчетную сущность внутри готового конвейера и **Чему учит:** переписать расчет сессий в готовом конвейере и проверить
проверить результат внешним эталоном. результат по исходным событиям.
## Сначала прочитать ## Сначала прочитать
- [задание «Нарежь сессии сам»](3-1-sessions-by-hand.md); - [задание «Нарежьте сессии сами»](3-1-sessions-by-hand.md);
- [модель сессии DDS](../architecture/dds/session.md); - [модель сессии DDS](../architecture/dds/session.md);
- [дневной конвейер трансформаций](../architecture/etl.md#объём-прогона); - [дневной конвейер трансформаций](../architecture/etl.md#объём-прогона);
- [раскладка по шардам](../architecture/storage.md#раскладка-по-шардам). - [раскладка по шардам](../architecture/storage.md#раскладка-по-шардам).
## Граница задания ## Что предстоит сделать
Даг, таблицы сессий и проверка их числа уже созданы. Даг, таблицы сессий и проверка их числа уже созданы.
Перепишите только три файла: Перепишите только три файла:
@@ -24,7 +24,7 @@
служебная таблица, где день собирается перед заменой. В служебная таблица, где день собирается перед заменой. В
`sessions_vs_reference.sql` число сессий сравнивается с `VisitID` из ODS. `sessions_vs_reference.sql` число сессий сравнивается с `VisitID` из ODS.
## Договор с дагом ## Как даг запускает ваши запросы
Объем прогона — дни, которые соберет один запуск. Его вычисляет первый файл. Объем прогона — дни, которые соберет один запуск. Его вычисляет первый файл.
@@ -74,7 +74,7 @@ ClickHouse в `ALTER ... PARTITION` здесь не работает.
Состав и типы строки заданы в `dds.session_rep`. Заполните начало, конец и Состав и типы строки заданы в `dds.session_rep`. Заполните начало, конец и
длительность. Посчитайте все события, просмотры, добавления в корзину и длительность. Посчитайте все события, просмотры, добавления в корзину и
покупки. Возьмите входную и выходную страницы, UTM первого события, устройство покупки. Возьмите входную и выходную страницы, UTM первого события, устройство
и город. Служебные колонки тоже входят в договор таблицы. и город. Заполните также служебные колонки из схемы таблицы.
Пишите через `dds.session_stage_dist`: место строки определит ключ Пишите через `dds.session_stage_dist`: место строки определит ключ
`client_id`. При записи прямо в локальную таблицу оно зависело бы от ноды, `client_id`. При записи прямо в локальную таблицу оно зависело бы от ноды,
@@ -178,15 +178,14 @@ Distributed-таблицу и такую ошибку не заметит.
партицию. Расчет объема должен пропустить группу сессий: новых прожитых дней и партицию. Расчет объема должен пропустить группу сессий: новых прожитых дней и
дыр в слое нет. дыр в слое нет.
## Приемка ## Что должно получиться
- [ ] переписаны только три файла `sql/dds/session_*.sql`, инфраструктура - [ ] вы переписали три файла `sql/dds/session_*.sql`, сохранив даг,
упражнения не изменена; таблицы и проверку стенда;
- [ ] запуск после удаления партиции восстановил выбранный день и завершил - [ ] ваш расчет восстановил удаленный день, `etl_pipeline` завершился успешно;
`etl_pipeline` успешно; - [ ] вы сверили десять сохраненных строк: `session_id`, границы,
- [ ] сохраненные `session_id`, границы, длительность и счетчики десяти строк длительность и счетчики совпали после пересборки;
совпали после пересборки; - [ ] вы получили ноль расхождений в `sessions_vs_reference` за все
- [ ] `sessions_vs_reference` дает ноль расхождений во всех прожитых днях; прожитые дни и вручную сверили границы и атрибуты сессии одной куки;
- [ ] для одной куки вручную сверены границы и атрибуты сессии; - [ ] ваш запрос с `hostName()` не нашел строк на чужом шарде;
- [ ] пересчет ключа по `hostName()` не нашел строк на чужом шарде; - [ ] вы повторили прогон без удаления данных: он пропустил группу сессий.
- [ ] повторный прогон без новой дыры пропустил группу сессий.
+26 -26
View File
@@ -1,7 +1,7 @@
# Сначала сторож, потом ломай: заказы # Проверьте заказы перед изменением обработки
**Чему учит:** до рискованного изменения задавать проверке явный договор и **Чему учит:** написать проверки заказов до изменения их обработки
испытывать его управляемой ошибкой. и убедиться на намеренной ошибке, что проверки ее замечают.
## Сначала прочитать ## Сначала прочитать
@@ -13,30 +13,30 @@
и на ее включение в [`dq_check`](../../dags/dq_check.py). Готового ответа для и на ее включение в [`dq_check`](../../dags/dq_check.py). Готового ответа для
двух проверок из этого задания в репозитории нет. двух проверок из этого задания в репозитории нет.
## Граница задания ## Что предстоит сделать
Добавьте два сторожа: Добавьте две проверки и включите их в даг:
- `sql/dq/order_items_total.sql` проверяет сумму позиций заказа; - `sql/dq/order_items_total.sql` проверяет сумму позиций заказа;
- `sql/dq/orders_vs_ods.sql` сверяет DDS с его источником; - `sql/dq/orders_vs_ods.sql` сверяет DDS с его источником;
- `dags/dq_check.py` запускает обе проверки, публикует их строки в - `dags/dq_check.py` запускает обе проверки, публикует их строки в
`dm.dq_summary` и создает для них отдельные задачи. `dm.dq_summary` и создает для них отдельные задачи.
На этом шаге не меняйте разбор ODS и сборку DDS. Даги, DDL, таблицы-доноры, Разбор ODS и сборку DDS вы перепишете в следующих заданиях. Здесь сохраните
Kafka и службы остаются несущей частью упражнения. их, даги загрузки, DDL, таблицы-доноры, Kafka и службы как есть.
Перед опытами остановите автоматическое движение мира: поставьте `world_live` Перед опытами остановите автоматическое движение мира: поставьте `world_live`
на паузу и дождитесь завершения активных прогонов. До чистой точки остановки не на паузу и дождитесь завершения активных прогонов. До завершения опытов не
запускайте руками `world_next_day`, `world_live_day`, `world_initialize` и запускайте руками `world_next_day`, `world_live_day`, `world_initialize` и
`world_recreate`. Это условие стабильного модельного времени: одной паузы `world_recreate`. Это условие стабильного модельного времени: одной паузы
`world_live` для него мало. `world_live` для него мало.
## Два договора качества ## Что будут проверять ваши запросы
Обе проверки следуют договору `dq_check`: запрос возвращает по одной строке Чтобы `dq_check` мог запустить проверку, ее запрос возвращает по одной строке
на день с колонками `data_date`, `reference_rows`, `actual_rows`, на день с колонками `data_date`, `reference_rows`, `actual_rows`,
`failed_rows`, `diagnostics`. Даг сохраняет в `dm.dq_summary` только счетчики, `failed_rows`, `diagnostics`. Даг сохраняет в `dm.dq_summary` только счетчики,
статус и координаты запуска. Ограниченные примеры из `diagnostics` ищите в статус и координаты запуска. Примеры из `diagnostics` ищите в
журнале упавшей задачи Airflow. Ограничьте их тем же малым числом, что и журнале упавшей задачи Airflow. Ограничьте их тем же малым числом, что и
существующие запросы в `sql/dq/`. существующие запросы в `sql/dq/`.
@@ -46,17 +46,18 @@ Kafka и службы остаются несущей частью упражн
посчитайте сумму `item_price * item_quantity` и сравните ее с `items_total`. посчитайте сумму `item_price * item_quantity` и сравните ее с `items_total`.
`reference_rows` считает ключи со стороны `items_total`, `actual_rows` - ключи `reference_rows` считает ключи со стороны `items_total`, `actual_rows` - ключи
со стороны вычисленной суммы, а `failed_rows` - несовпавшие значения. В со стороны вычисленной суммы, а `failed_rows` - несовпавшие значения. В
исправном наборе первые два счетчика равны по построению; сам инвариант выражен исправном наборе первые два счетчика равны; несовпадения сумм считает
через `failed_rows`. Для диагностики достаточно `order_id`, ожидаемой суммы и `failed_rows`. Для диагностики достаточно `order_id`, ожидаемой суммы и
суммы позиций. суммы позиций.
Проверка относится к типизированному разбору массива `items` на границе Проверка относится к типизированному разбору массива `items` на границе
ODS -> DDS. Скидка, доставка и равенство `total` в этот договор не входят. ODS -> DDS. Скидку, доставку и равенство `total` здесь проверять не нужно.
### DDS против ODS ### DDS против ODS
Имя проверки - `orders_vs_ods`. Источник модели DDS здесь - текущее состояние Имя проверки - `orders_vs_ods`. Источник модели DDS здесь - текущее состояние
`ods.order_v`. Внутреннее состояние генератора в этот договор не входит. `ods.order_v`. С ним вы и сравниваете DDS; обращаться к внутреннему состоянию
генератора для этой проверки не нужно.
Одним запросом соберите обе стороны на зерне дня заказа и сравните: Одним запросом соберите обе стороны на зерне дня заказа и сравните:
- число заказов; - число заказов;
@@ -131,18 +132,17 @@ ORDER BY check_name;
Проверка суммы должна охватывать каждый день `dds.order_v`. Сверка слоев Проверка суммы должна охватывать каждый день `dds.order_v`. Сверка слоев
должна охватывать объединение дней ODS и DDS, включая дни только одной стороны. должна охватывать объединение дней ODS и DDS, включая дни только одной стороны.
## Когда можно остановиться ## Завершите опыт
После двух восстановлений выбранный заказ снова совпадает с ODS. Все семь После восстановления заказа и успешного `dq_check` можно закончить работу или
проверок дают зеленый результат. В таком состоянии можно закончить работу или
перейти к разбору заказов, оставив модельное время на паузе. перейти к разбору заказов, оставив модельное время на паузе.
## Приемка ## Что должно получиться
- [ ] две новые проверки встроены в `dq_check` и публикуют дневные строки по - [ ] вы включили две новые проверки в `dq_check`; их дневные результаты
принятому договору; появляются в `dm.dq_summary`;
- [ ] каждая проверка дала красный результат на своей управляемой ошибке; - [ ] вы увидели ошибку каждой проверки после соответствующего изменения
- [ ] журналы упавших задач содержали ограниченную диагностику; заказа, а в журнале нашли примеры заказов с расхождениями;
- [ ] `etl_pipeline` восстановил обе измененные строки DDS; - [ ] вы восстановили заказ через `etl_pipeline` после обоих опытов;
- [ ] `order_items_total`, `orders_vs_ods` и пять прежних DQ зелены; - [ ] `order_items_total`, `orders_vs_ods` и пять прежних проверок успешны;
- [ ] SQL разбора ODS и сборки DDS на этом шаге не менялся. - [ ] разбор ODS и сборка DDS остались прежними.
+20 -21
View File
@@ -1,7 +1,7 @@
# Перепишите разбор заказов в ODS # Перепишите разбор заказов в ODS
**Чему учит:** сначала измерить обе ветви разбора на управляемом срезе, затем **Чему учит:** переписать разбор заказов и на пробных сообщениях проверить,
переписать переход и повторить тот же опыт. что годные строки и брак попадают в свои таблицы без потерь.
## Сначала прочитать ## Сначала прочитать
@@ -11,9 +11,9 @@
- [`orders_ingest`](../../dags/orders_ingest.py); - [`orders_ingest`](../../dags/orders_ingest.py);
- [`order_parse.sql`](../../sql/ods/order_parse.sql). - [`order_parse.sql`](../../sql/ods/order_parse.sql).
Готового запроса для сверки управляемого среза в репозитории нет. Запрос для проверки пробных строк вы напишете сами.
## Граница задания ## Что предстоит сделать
Сначала проведите опыт на готовом `order_parse.sql`. Затем удалите его SQL, Сначала проведите опыт на готовом `order_parse.sql`. Затем удалите его SQL,
соберите разбор заново и повторите опыт с другим маркером. соберите разбор заново и повторите опыт с другим маркером.
@@ -72,7 +72,7 @@ jq -cn --arg marker "$LAB_MARKER" --arg day "$LAB_DAY" '
' > "$LAB_BATCH" ' > "$LAB_BATCH"
``` ```
Отправьте файл в обычный топик заказов штатным производителем контейнера: Отправьте файл в обычный топик заказов утилитой из контейнера Kafka:
```bash ```bash
docker compose exec -T kafka /opt/kafka/bin/kafka-console-producer.sh \ docker compose exec -T kafka /opt/kafka/bin/kafka-console-producer.sh \
@@ -123,7 +123,7 @@ SETTINGS mutations_sync = 2;
В ClickHouse 26.3 мутации выполняются в фоне. С `mutations_sync = 2` команда В ClickHouse 26.3 мутации выполняются в фоне. С `mutations_sync = 2` команда
ждет их завершения на всех репликах. Запросами к трем Distributed-таблицам ждет их завершения на всех репликах. Запросами к трем Distributed-таблицам
докажите, что строк маркера не осталось, затем удалите временный файл. убедитесь, что строк маркера не осталось, затем удалите временный файл.
Группа `clickstream_orders` уже подтвердила эти сообщения, поэтому обычный Группа `clickstream_orders` уже подтвердила эти сообщения, поэтому обычный
повтор `orders_ingest` их не прочитает. В Kafka они остаются до удаления по повтор `orders_ingest` их не прочитает. В Kafka они остаются до удаления по
@@ -137,8 +137,8 @@ SETTINGS mutations_sync = 2;
## Перепишите разбор ## Перепишите разбор
Удалите готовый SQL из `sql/ods/order_parse.sql` и сохраните его договор с Удалите готовый SQL из `sql/ods/order_parse.sql` и напишите свой. Чтобы
`orders_ingest`: `orders_ingest` мог его использовать, сохраните следующие условия:
- вход - строки `stg.orders_raw_dist` одного `{load_id:String}`; - вход - строки `stg.orders_raw_dist` одного `{load_id:String}`;
- каждая строка среза попадает ровно в один результат: годный заказ или брак; - каждая строка среза попадает ровно в один результат: годный заказ или брак;
@@ -161,21 +161,20 @@ SETTINGS mutations_sync = 2;
шард. Затем очистите второй срез теми же адресными командами и запустите шард. Затем очистите второй срез теми же адресными командами и запустите
`dq_check`. `dq_check`.
## Когда можно остановиться ## Завершите опыт
Оба маркера отсутствуют в STG, ODS, таблице брака и DDS; временные файлы Убедитесь, что оба маркера отсутствуют в STG, ODS, таблице брака и DDS,
удалены. Семь DQ дают зеленый результат. В таком состоянии можно перейти к временные файлы удалены, а семь проверок успешны. Теперь можно перейти к
сборке DDS, не снимая паузу с модельного времени. Если вы прерываетесь и до сборке DDS, не снимая паузу с модельного времени. Если вы прерываетесь и до
опыта `world_live` работал, снимите его с паузы. опыта `world_live` работал, снимите его с паузы.
## Приемка ## Что должно получиться
- [ ] до переписывания зафиксированы баланс 4 = 1 + 3, три класса брака, - [ ] у вас сохранены результаты до переписывания: четыре строки дали
деловые поля текущей версии и фактический шард; один заказ и три класса брака; вы сверили поля текущей версии и шард;
- [ ] переписан только `sql/ods/order_parse.sql`, несущая инфраструктура не - [ ] вы переписали `sql/ods/order_parse.sql`, сохранив остальные части стенда;
изменена; - [ ] ваш разбор дал тот же результат на новой пробной пачке;
- [ ] после переписывания новый срез дал тот же результат; - [ ] вы проверили разные значения `created_at` и `updated_at` у годной
- [ ] обе годные строки различали `created_at` и `updated_at`; строки в обоих опытах и подтвердили правильный шард через `hostName()`;
- [ ] проверки `hostName()` не нашли строки ODS на чужом шарде; - [ ] вы удалили строки обоих опытов по их маркерам;
- [ ] строки обоих опытов удалены только по своим маркерам; - [ ] после очистки конвейер и все семь проверок успешны.
- [ ] после очистки общий конвейер и семь DQ находятся в зеленом состоянии.
+21 -21
View File
@@ -1,7 +1,7 @@
# Перепишите сборку заказов в DDS # Перепишите сборку заказов в DDS
**Чему учит:** переписать пересборку заказов по состоянию слоев и доказать ее **Чему учит:** настроить пересборку заказов по состоянию ODS и DDS
результат на пропущенном закрытом дне. и проверить, что она восстанавливает пропущенный день за пределами текущего окна.
## Сначала прочитать ## Сначала прочитать
@@ -11,7 +11,7 @@
- [дневной конвейер](../architecture/etl.md#объём-прогона); - [дневной конвейер](../architecture/etl.md#объём-прогона);
- [раскладку по шардам](../architecture/storage.md#раскладка-по-шардам). - [раскладку по шардам](../architecture/storage.md#раскладка-по-шардам).
## Граница задания ## Что предстоит сделать
Удалите готовый SQL и перепишите только три файла: Удалите готовый SQL и перепишите только три файла:
@@ -19,8 +19,8 @@
- `sql/dds/order_rebuild.sql`; - `sql/dds/order_rebuild.sql`;
- `sql/dds/order_replace.sql`. - `sql/dds/order_replace.sql`.
Даг `dds_transform`, DDL, донор, ODS и обе проверки из первого задания уже Даг `dds_transform`, DDL, донор, ODS и обе проверки из первого задания
задают границу работы. Перед началом убедитесь, что пробные строки предыдущего оставьте как есть. Перед началом убедитесь, что пробные строки предыдущего
задания удалены, а семь DQ дают зеленый результат. задания удалены, а семь DQ дают зеленый результат.
Поставьте `world_live` на паузу и дождитесь завершения активных прогонов. До Поставьте `world_live` на паузу и дождитесь завершения активных прогонов. До
@@ -28,7 +28,7 @@
`world_recreate`. `world_next_day` понадобится только в явно указанном ниже `world_recreate`. `world_next_day` понадобится только в явно указанном ниже
случае. случае.
## Договор с дагом ## Как даг запускает ваши запросы
Сохраните интерфейс `dds_transform`: Сохраните интерфейс `dds_transform`:
@@ -103,22 +103,22 @@ DROP PARTITION 'YYYY-MM-DD';
заказов снова должна пересобрать текущее изменяемое окно. Контрольные суммы заказов снова должна пересобрать текущее изменяемое окно. Контрольные суммы
деловых полей остаются прежними, а все семь проверок дают зеленый результат. деловых полей остаются прежними, а все семь проверок дают зеленый результат.
## Когда можно остановиться ## Завершите опыт
Удаленная партиция восстановлена, ее число строк и контрольная сумма совпали После восстановления партиции и успешных проверок снимите `world_live`
с исходными. На чужих шардах строк нет, повторный прогон не изменил деловые с паузы, если он работал до упражнения.
данные, семь DQ зелены. Если до упражнения `world_live` работал, теперь его
можно снять с паузы.
## Приемка ## Что должно получиться
- [ ] переписаны только три файла `sql/dds/order_*.sql`, инфраструктура - [ ] вы переписали три файла `sql/dds/order_*.sql`, сохранив остальные
упражнения не изменена; части стенда;
- [ ] удален ближайший к окну закрытый день, а красный DQ зафиксирован до - [ ] вы удалили ближайший к окну закрытый день и увидели ошибку DQ до
восстановления; восстановления;
- [ ] `first_day` начался с удаленного дня, задачи замены покрыли непрерывный - [ ] ваш расчет выбрал `first_day` с удаленного дня и заменил партиции
отрезок до конца окна; всего отрезка до конца окна;
- [ ] число заказов и контрольная сумма восстановленного дня совпали; - [ ] вы сверили число заказов и контрольную сумму восстановленного дня
- [ ] `order_items_total`, `orders_vs_ods` и пять прежних DQ зелены; с сохраненными значениями; они совпали;
- [ ] проверка `hostName()` не нашла заказов DDS на чужом шарде; - [ ] `order_items_total`, `orders_vs_ods` и пять прежних проверок успешны;
- [ ] повторный `etl_pipeline` сохранил деловые данные и зеленые проверки. - [ ] ваш запрос с `hostName()` не нашел заказов на чужом шарде;
- [ ] вы повторили `etl_pipeline`: деловые данные остались прежними,
проверки прошли.
+46 -46
View File
@@ -15,7 +15,7 @@
Готовый стендовый разбор находится в `sql/ddl/30-ods-views.sql`. Откройте его Готовый стендовый разбор находится в `sql/ddl/30-ods-views.sql`. Откройте его
после собственной сверки: в нем лежит ответ на основную часть задания. после собственной сверки: в нем лежит ответ на основную часть задания.
## Граница задания ## Что предстоит сделать
Создайте в базе `default` одну свою таблицу годных событий и наполните ее одним Создайте в базе `default` одну свою таблицу годных событий и наполните ее одним
пакетным `INSERT SELECT` из `stg.hits_raw_dist`. Работайте с первой нодой под пакетным `INSERT SELECT` из `stg.hits_raw_dist`. Работайте с первой нодой под
@@ -72,11 +72,10 @@ Airflow. При включенном `world_live` посмотрите его т
`ods.event_rep`, а также `_load_ts`. Тип `purchaseRevenue` возьмите из ODS: `ods.event_rep`, а также `_load_ts`. Тип `purchaseRevenue` возьмите из ODS:
`Array(Decimal(18, 2))`. `Array(Decimal(18, 2))`.
Схлопните повтор доставки по договору ODS. Возьмите Обработайте повторные доставки так же, как ODS. Возьмите
`ReplacingMergeTree(_load_ts)`, партицию `EventDate` и полный ключ замены: `ReplacingMergeTree(_load_ts)`, партицию `EventDate` и полный ключ замены:
`(CounterID, EventDate, intHash32(ClientID), WatchID)`. Достаточно одной `(CounterID, EventDate, intHash32(ClientID), WatchID)`. Достаточно одной
локальной таблицы: отдельная Distributed-таблица не добавит урока к пакетному локальной таблицы: загрузка и сверка в этом опыте выполняются на одной ноде.
опыту на одной ноде.
`WatchID` — логический идентификатор события, уникальный по `WatchID` — логический идентификатор события, уникальный по
[договору генератора](../specs/2026-08-01-generator.md). Физический ключ ODS [договору генератора](../specs/2026-08-01-generator.md). Физический ключ ODS
@@ -85,8 +84,7 @@ Airflow. При включенном `world_live` посмотрите его т
## Разберите годные строки ## Разберите годные строки
Для каждой строки среза примите решение по тому же строгому входу, что и Для каждой строки среза проверьте те же условия приема, что и на стенде:
стенд:
1. корень `raw` — JSON-объект; 1. корень `raw` — JSON-объект;
2. набор верхнеуровневых ключей в точности равен 47 ключам формата; 2. набор верхнеуровневых ключей в точности равен 47 ключам формата;
@@ -95,25 +93,25 @@ Airflow. При включенном `world_live` посмотрите его т
Напишите разбор, который не прерывает пакет из-за одной грязной строки. Сами Напишите разбор, который не прерывает пакет из-за одной грязной строки. Сами
выберите способ безопасного извлечения пяти опорных полей, форму общих выражений выберите способ безопасного извлечения пяти опорных полей, форму общих выражений
и место фильтра. Остальные поля извлеките по договору формата. и место фильтра. Остальные поля извлеките по описанию формата.
Перенесите `_load_ts` из STG без пересчета: это время исходной доставки и Перенесите `_load_ts` из STG без пересчета: это время исходной доставки и
колонка версии. Время запуска вашего запроса сюда не входит. Вставьте в свою колонка версии. Время запуска вашего запроса сюда не входит. Вставьте в свою
таблицу только годные строки выбранного среза. таблицу только годные строки выбранного среза.
Считайте отвергнутые строки прямо в исходном срезе: отдельная таблица брака в Считайте отвергнутые строки прямо в исходном срезе, без отдельной таблицы
задание не входит. Проверьте работу строгого входа одним запросом. Посчитайте: брака. Одним запросом посчитайте:
- все доставки; - все доставки;
- строки, прошедшие ваш предикат; - строки, прошедшие ваше условие отбора;
- строки, отвергнутые его отрицанием. - строки, для которых это условие ложно.
Проверьте равенство общего числа строк сумме прошедших и отвергнутых. Так каждая Проверьте равенство общего числа строк сумме прошедших и отвергнутых. Так каждая
исходная строка учитывается ровно в одной группе. Если отвергнутые строки есть, исходная строка учитывается ровно в одной группе. Если отвергнутые строки есть,
сохраните их `raw`, `_load_ts` и координаты Kafka. Найдите те же сохраните их `raw`, `_load_ts` и координаты Kafka. Найдите те же
координаты в `ods.event_errors_dist` и запишите стендовый `error_class`. Если координаты в `ods.event_errors_dist` и запишите стендовый `error_class`. Если
брака нет, так и зафиксируйте: опыт проверил успешную ветвь и выполнение брака нет, так и зафиксируйте: опыт проверил успешную ветвь и выполнение
предиката на этом срезе, но не классификатор ошибок. Подкладывать грязные условия отбора на этом срезе, но не классификатор ошибок. Подкладывать грязные
сообщения ради задания не требуется. сообщения ради задания не требуется.
## Приведите стороны к одному зерну ## Приведите стороны к одному зерну
@@ -126,17 +124,17 @@ Airflow. При включенном `world_live` посмотрите его т
полных ключей среди годных доставок. Разница между числом доставок и этим полных ключей среди годных доставок. Разница между числом доставок и этим
счетом показывает повторы в исходном срезе. счетом показывает повторы в исходном срезе.
Область эталона постройте независимо от своего предиката и результата вставки. Выберите строки ODS для сравнения независимо от своего условия отбора и
Снова возьмите весь исходный STG-срез. По координатам Kafka исключите доставки, результата вставки. Снова возьмите весь исходный STG-срез. По координатам Kafka
которые есть в `ods.event_errors_dist`. У оставшихся доставок извлеките исключите доставки, которые есть в `ods.event_errors_dist`. У оставшихся
`WatchID` без риска прервать запрос и выберите эти события из свежего доставок извлеките `WatchID` без риска прервать запрос и выберите эти события из свежего
`ods.event_v`. `ods.event_v`.
Свяжите сырую доставку с ODS по `WatchID`: по договору генератора это уникальный Свяжите сырую доставку с ODS по `WatchID`: по договору генератора это уникальный
идентификатор события. Он задает только область эталона. Дедупликация и идентификатор события. Здесь он нужен только для выбора событий ODS.
последующее соединение используют полный физический ключ. Не берите область из Удаление повторов и последующее соединение используют полный физический ключ.
своей таблицы и не применяйте к ней свой предикат. Иначе ошибочно отвергнутая Не берите список событий из своей таблицы и не применяйте к нему свое условие
строка исчезнет с обеих сторон сверки. отбора. Иначе ошибочно отвергнутая строка исчезнет с обеих сторон сверки.
## Сверьте 47 полей ## Сверьте 47 полей
@@ -171,44 +169,46 @@ ClickHouse по умолчанию заполняет отсутствующую
Для измененной строки назовите поля, которые расходятся, и сравните каждое с Для измененной строки назовите поля, которые расходятся, и сравните каждое с
сырой строкой. Для пропавшей или лишней строки проверьте пять опорных полей и сырой строкой. Для пропавшей или лишней строки проверьте пять опорных полей и
решение строгого предиката. Если координаты доставки есть в результат условия отбора. Если координаты доставки есть в
`ods.event_errors_dist`, посмотрите `error_class`. `ods.event_errors_dist`, посмотрите `error_class`.
Исправьте разбор, удалите и заново создайте только собственную таблицу, затем Исправьте разбор, удалите и заново создайте только собственную таблицу, затем
повторите вставку того же среза. Остановитесь, когда счет исходных доставок по повторите вставку того же среза. Остановитесь, когда счет исходных доставок по
прежним границам не изменился, баланс строгого входа сошелся, а построчная прежним границам не изменился, число годных и отвергнутых строк в сумме дает
сверка пуста. число исходных доставок, а построчная сверка пуста.
## Где заканчивается проверка ## Чего не подтверждает сравнение с ODS
`ods.event_v` — образец для сравнения. Оба разбора могли повторить одну `ods.event_v` — образец для сравнения. Оба разбора могли повторить одну
ошибку. Для пяти опорных полей у вас есть проверка разбираемости и исходный ошибку. Для пяти опорных полей у вас есть проверка преобразования в нужный тип
JSON. Остальные 42 поля можно вручную сопоставить с `raw`, однако второго и исходный JSON. Остальные 42 поля можно вручную сопоставить с `raw`, однако
независимого источника для всех значений на стенде нет. второго независимого источника для всех значений на стенде нет.
## Уберите свой объект ## Уберите свой объект
Сохраните DDL таблицы, запрос разбора, границы среза, баланс строгого входа и Сохраните DDL таблицы, запрос разбора, границы среза, число исходных, годных
пустую итоговую сверку. Затем удалите свою таблицу командой `DROP TABLE` с ее и отвергнутых строк и итоговую сверку без расхождений. Запишите также, чего
точным именем. Убедитесь через `system.tables`, что объекта больше нет. не подтверждает сравнение с ODS и как TTL ограничивает время для повторения
опыта. Затем удалите свою таблицу командой `DROP TABLE` с ее точным именем.
Убедитесь через `system.tables`, что объекта больше нет.
Прикладные строки стенда вы не меняли, поэтому удалять данные из STG, ODS или Прикладные строки стенда вы не меняли, поэтому удалять данные из STG, ODS или
Kafka после опыта не нужно. Kafka после опыта не нужно.
## Приемка ## Что должно получиться
- [ ] записаны границы малого завершенного среза, его счет и координаты - [ ] у вас сохранены границы выбранного среза, число строк и координаты
доставки по партициям Kafka; доставки по партициям Kafka;
- [ ] создана одна собственная таблица со всеми 47 полями, перенесенным - [ ] вы собрали свою таблицу со всеми 47 полями и `_load_ts`, используя
`_load_ts` и физическим договором дедупликации ODS; те же правила удаления повторов, что и ODS;
- [ ] пакетный разбор принимает только JSON-объекты с точным набором ключей и - [ ] ваш разбор принимает только JSON-объекты с точным набором ключей и
пятью разбираемыми опорными полями; пятью опорными полями, которые преобразуются в нужные типы;
- [ ] счет исходных строк равен сумме годных и отвергнутых, а найденный брак - [ ] вы сверили число исходных строк с суммой годных и отвергнутых,
рассмотрен по `raw`, координатам и стендовому классу; а найденный брак разобрали по `raw`, координатам и классу ошибки стенда;
- [ ] область ODS получена из всех координат исходного среза и стендовой - [ ] вы выбрали события ODS по исходному срезу и таблице ошибок,
таблицы ошибок, независимо от собственного предиката и таблицы; независимо от своей таблицы и условия отбора;
- [ ] обе стороны приведены к зерну полного физического ключа; - [ ] вы сравнили обе стороны по полному физическому ключу через
- [ ] `FULL OUTER JOIN` сравнивает все 47 полей, а числа пропавших, лишних и `FULL OUTER JOIN`: во всех 47 полях нет расхождений,
измененных строк равны нулю; пропавших и лишних строк тоже нет;
- [ ] отдельно подтвержден перенос `_load_ts` и учтены повторы доставки; - [ ] вы отдельно проверили перенос `_load_ts` и учли повторные доставки;
- [ ] границы стендового оракула и TTL записаны вместе с результатом; - [ ] вы записали ограничения сравнения с ODS и срок повторения опыта;
- [ ] собственная таблица удалена, объекты и данные стенда не изменены. - [ ] после опыта вы удалили свою таблицу, сохранив объекты и данные стенда.
+16 -17
View File
@@ -99,21 +99,20 @@
[«Данные»](../architecture/monitoring/data.md). Устройство живого дня и [«Данные»](../architecture/monitoring/data.md). Устройство живого дня и
доставки слепка описано в [главе об Airflow](../handbook/airflow-dags.md). доставки слепка описано в [главе об Airflow](../handbook/airflow-dags.md).
## Приемка ## Что должно получиться
- [ ] для каждого из трех дашбордов записан вопрос, на который он отвечает; - [ ] вы записали по одному вопросу для каждого из трех дашбордов;
- [ ] сохранены окно, текст и показатели верхнего запроса, определены его - [ ] у вас сохранены окно, текст и показатели верхнего запроса;
источник и назначение; вы выяснили, кто его отправил и зачем;
- [ ] среди запросов открытых дашбордов найден тот, который прочитал больше - [ ] вы нашли запрос открытых дашбордов, который прочитал больше всего
всего строк, и соответствующая панель; строк, и панель, которая его отправила;
- [ ] объяснено, как журнал собирают с двух нод и какие записи учитывают для - [ ] вы можете объяснить, зачем журнал собирают с двух нод и какие записи
счета запросов; учитывают при подсчете запросов;
- [ ] до наблюдения нарисован прогноз свежести событий и заказов, а форма - [ ] у вас есть прогноз свежести, сделанный до наблюдения, и несколько
линии объяснена через метку последней доставки; замеров; рост и сброс на рисунке объяснены через время последней доставки;
- [ ] записаны несколько наблюдений, на наброске видны рост и сброс свежести - [ ] вы подтвердили доставку заказов в Airflow и на панели слепков,
заказов, а доставка подтверждена в Airflow и на панели слепков; подписали модельную дату отдельно от реального времени наблюдений;
- [ ] модельная дата отделена от реального времени наблюдения; - [ ] вы можете объяснить выбор слоев для свежести, потока и доли брака
- [ ] объяснен выбор слоев для свежести, потока и доли брака, а также общее и почему числитель и знаменатель берут за одно окно;
окно числителя и знаменателя; - [ ] после наблюдений службы продолжают работать, данные и стендовые
- [ ] службы не остановлены, данные вручную не правились, стендовые дашборды дашборды остались без изменений.
не изменены.
+19 -23
View File
@@ -10,28 +10,28 @@
|---|---|---| |---|---|---|
| 1 | Пять витрин слоя DM | построить результат для потребителя хранилища; уроки каждой витрины — в таблице ниже | | 1 | Пять витрин слоя DM | построить результат для потребителя хранилища; уроки каждой витрины — в таблице ниже |
| 2 | [Свой график в Superset](2-1-superset-chart.md) | превратить свой результат из хранилища в отчет в BI | | 2 | [Свой график в Superset](2-1-superset-chart.md) | превратить свой результат из хранилища в отчет в BI |
| 3 | [Нарежь сессии сам](3-1-sessions-by-hand.md) | сверить число сессий в своей нарезке с эталоном и найти события, из-за которых оно расходится | | 3 | [Нарежьте сессии сами](3-1-sessions-by-hand.md) | сверить число сессий в своей нарезке с эталоном и найти события, из-за которых оно расходится |
| 4 | [Перепиши нарезку стенда](4-1-session-pipeline.md) | изменить расчетную сущность внутри готового конвейера и проверить ее внешним эталоном | | 4 | [Перепишите нарезку стенда](4-1-session-pipeline.md) | переписать расчет сессий в готовом конвейере и проверить по исходным событиям |
| 5 | [Сначала сторож, потом ломай: заказы](5-1-order-checks.md)<br>[Перепишите разбор заказов в ODS](5-2-order-parsing.md)<br>[Перепишите сборку заказов в DDS](5-3-order-pipeline.md) | написать проверки качества до рискованного изменения, затем переписать разбор и сборку заказов | | 5 | [Проверьте заказы перед изменением обработки](5-1-order-checks.md)<br>[Перепишите разбор заказов в ODS](5-2-order-parsing.md)<br>[Перепишите сборку заказов в DDS](5-3-order-pipeline.md) | написать проверки качества до рискованного изменения, затем переписать разбор и сборку заказов |
| 6 | [Разбери сырые события и сверь результат со стендом](6-1-event-parsing.md) | изучить разбор потока отдельно от живого приема и сверить его со стендовым результатом | | 6 | [Разберите сырые события рядом со стендом](6-1-event-parsing.md) | изучить разбор потока отдельно от живого приема и сверить его со стендовым результатом |
| 7 | [Мониторинг: куда смотреть](7-1-monitoring.md) | выбрать, куда и на что смотреть в живой системе | | 7 | [Мониторинг: куда смотреть](7-1-monitoring.md) | выбрать, куда и на что смотреть в живой системе |
## Ступень 1: пять витрин слоя DM ## Ступень 1: пять витрин слоя DM
Слой DM в стенде построен наполовину: витрины трех сюжетов дашборда и контроль Слой DM в стенде построен наполовину: витрины трех сюжетов дашборда и контроль
качества уже построены, остальные пять витрин строите вы. Каждая из них — качества уже построены, остальные пять витрин строите вы. Каждая из них —
самостоятельное ТЗ: в нем есть состав витрины, зерно, источники и способ самостоятельное задание: в нем есть состав витрины, зерно, источники и способ
проверки. проверки.
| № | Результат | Чему учит | | № | Результат | Чему учит |
|---|---|---| |---|---|---|
| 1 | [`events_enriched_v`](1-1-events-enriched.md) | строить слой как договор о данных, а не как их копию | | 1 | [`events_enriched_v`](1-1-events-enriched.md) | подготовить удобный для анализа набор полей, сохранив одну строку на событие |
| 2 | [`top_pages_daily`](1-2-top-pages-daily.md) | создавать материализованную витрину с дневной пересборкой в конвейере | | 2 | [`top_pages_daily`](1-2-top-pages-daily.md) | создавать материализованную витрину с дневной пересборкой в конвейере |
| 3 | [`session_overview`](1-3-session-overview.md) | хранить слагаемые для расчета средних и долей | | 3 | [`session_overview`](1-3-session-overview.md) | хранить слагаемые для расчета средних и долей |
| 4 | [`dq_errors_daily`](1-4-dq-errors-daily.md) | считать брак по дню загрузки и хранить итоги дольше источника | | 4 | [`dq_errors_daily`](1-4-dq-errors-daily.md) | считать брак по дню загрузки и хранить итоги дольше источника |
| 5 | [`utm_effectiveness`](1-5-utm-effectiveness.md) | читать `declared_` как предупреждение о сумме со слов клиента | | 5 | [`utm_effectiveness`](1-5-utm-effectiveness.md) | читать `declared_` как предупреждение о сумме со слов клиента |
Общее устройство слоя — [дока DM](../architecture/dm.md). Она отвечает на Общее устройство слоя — [справочник DM](../architecture/dm.md). Он отвечает на
вопросы «почему так», задание отвечает на вопрос «что сделать». вопросы «почему так», задание отвечает на вопрос «что сделать».
## Как принято на ступени 1 ## Как принято на ступени 1
@@ -99,9 +99,8 @@ DM](../architecture/dm.md).
собрано верно. В сводку качества конвейера `dm.dq_summary` он не пишет: это собрано верно. В сводку качества конвейера `dm.dq_summary` он не пишет: это
инструмент лабы, а не история качества стенда. инструмент лабы, а не история качества стенда.
Проверка пяти заданий по витринам устроена одинаково и золотых констант не Для каждой витрины проверка независимо рассчитывает ожидаемый результат
содержит: рядом с витриной независимым запросом собирается тот же ответ — прямо по ее источнику. У четырех заданий источник
прямо из источника витрины, минуя ее саму. У четырех заданий источник
эталона тот же, что у витрины, `dds.session_v` или `dds.event_v`; у задания эталона тот же, что у витрины, `dds.session_v` или `dds.event_v`; у задания
4 это таблицы брака ODS, потому что в DDS нет сущности брака. Обе стороны 4 это таблицы брака ODS, потому что в DDS нет сущности брака. Обе стороны
соединяются по деловому ключу через `FULL OUTER JOIN`, и считается число соединяются по деловому ключу через `FULL OUTER JOIN`, и считается число
@@ -109,16 +108,13 @@ DM](../architecture/dm.md).
соединение сохраняет ключи обеих сторон: при внутреннем пропавший день или соединение сохраняет ключи обеих сторон: при внутреннем пропавший день или
ключ не попал бы в результат, и потеря осталась бы незамеченной. ключ не попал бы в результат, и потеря осталась бы незамеченной.
Раздел «Как проверить себя» каждого ТЗ по витрине называет деловой ключ и Раздел «Как проверить себя» каждого задания по витрине называет деловой ключ и
сравниваемые величины. Тот же прием в стендовом исполнении — [даг сравниваемые величины. Тот же прием в стендовом исполнении — [даг
`dq_check`](../../dags/dq_check.py) и запросы в [`sql/dq/`](../../sql/dq). `dq_check`](../../dags/dq_check.py) и запросы в [`sql/dq/`](../../sql/dq).
**Предупреждение.** Запросы самой проверки лежат в `sql/dq/` открыто, и Запросы проверок лежат в `sql/dq/`. Откройте их после собственного решения:
имена называют витрину и роль сверки. Проверка на инвариантах неизбежно они заново рассчитывают показатели витрины, поэтому содержат ответ на
близка к ответу: чтобы сверить витрину, надо ее пересчитать. Если открыть существенную часть задания. Сравните свой способ расчета с готовым.
эти файлы до выполнения задания, можно скопировать готовый SQL, не
разобравшись в расчете самостоятельно. Смотреть туда стоит после своей
сборки: сравнить два решения полезно, списать одно — нет.
## Факультативы ## Факультативы
@@ -152,12 +148,12 @@ DM](../architecture/dm.md).
### Что делать, когда стенд сломался ### Что делать, когда стенд сломался
Открывается после ступени 7. Поломки стенда и runbook по итогам Открывается после ступени 7. Поломки стенда и инструкции по восстановлению
расследований; весь раздел — повышенная сложность. по итогам расследований; весь раздел — повышенная сложность.
## Уборка ## Уборка
Черновики и опыты за собой сносить: правило проверок стенда — После опытов удаляйте свои временные объекты. Если удалили учебную витрину,
[дока тестирования](../architecture/testing.md). Снесенная витрина обязана ее задача в `homework_check` должна снова завершиться ошибкой: отсутствие
снова покраснить свою задачу в `homework_check` — если не покраснила, витрины тоже проверяется. Подробнее — в
проверка ничего и не проверяла. [справочнике проверок](../architecture/testing.md).
+32 -3
View File
@@ -40,6 +40,35 @@
- *расследование* — данные уже лежат на стенде или стенд намеренно ломается, а - *расследование* — данные уже лежат на стенде или стенд намеренно ломается, а
менти ищет причину и объясняет ее. менти ищет причину и объясняет ее.
### Как писать задания
Задание обращается к учащемуся на «вы». Начните с «Чему учит»: назовите
умение, которое он получит. Затем объясните, для чего нужен результат, и
ведите читателя по действиям. Термины раскрывайте там, где без них нельзя
понять следующий шаг. Историю разработки оставляйте в рабочих справочниках.
Раздел «Как проверить себя» описывает способ проверки: что выполнить,
с чем сравнить и где искать причину расхождения. Если проверка и есть ход
исследования, сохраните ее в шагах, без повторного раздела. В конце задания
«Что должно получиться» помогает учащемуся оценить результат: что у него
работает, что он проверил и что может объяснить. Чекбоксы обращены к нему:
«вы сверили числа», «у вас сохранен запрос». Подробный порядок действий
остается в основной части.
Заголовки остальных разделов выбирайте по содержанию: «Для чего нужна
витрина», «Найдите причину», «Как даг запускает ваши запросы». Число разделов
и пунктов определяется упражнением. Ограничения объясняйте рядом с действием,
которого они касаются; завершение опыта включает восстановление данных или
удаление временных объектов, когда это требуется.
При вычитке сохраняйте состав результата, технические условия, способы
проверки и самостоятельную часть работы. Новое упражнение или готовое решение
под видом пояснения меняет учебный смысл и требует отдельного обсуждения.
Формат инженерных issues из правил трекера относится к разработке стенда;
учебные задания пишутся по этим правилам. Образцы:
[дашборд Superset](../labs/optional/superset-dashboard.md) и
[расследование подключения](../labs/optional/superset-missing-object.md).
## 3. Основная программа — семь ступеней ## 3. Основная программа — семь ступеней
### Ступень 1. Построй пять витрин слоя DM ### Ступень 1. Построй пять витрин слоя DM
@@ -64,7 +93,7 @@
ступени предстоит написать при реализации программы. Полный собственный дашборд ступени предстоит написать при реализации программы. Полный собственный дашборд
остается факультативом в разделе «Superset и BI». остается факультативом в разделе «Superset и BI».
### Ступень 3. Нарежь сессии сам ### Ступень 3. Нарежьте сессии сами
До того как открыть стендовый SQL нарезки сессий в DDS, менти самостоятельно До того как открыть стендовый SQL нарезки сессий в DDS, менти самостоятельно
делит события на визиты. Сначала он сравнивает число своих сессий с эталоном делит события на визиты. Сначала он сравнивает число своих сессий с эталоном
@@ -75,7 +104,7 @@
Форма — построить свое по ТЗ и провести расследование. Обратная связь состоит Форма — построить свое по ТЗ и провести расследование. Обратная связь состоит
из счетной сверки с `VisitID` и ручного сопоставления границ. из счетной сверки с `VisitID` и ручного сопоставления границ.
### Ступень 4. Перепиши нарезку стенда ### Ступень 4. Перепишите нарезку стенда
Менти удаляет и заново пишет SQL нарезки сессий DDS: расчет объема, пересборку Менти удаляет и заново пишет SQL нарезки сессий DDS: расчет объема, пересборку
и замену. Урок — изменить расчетную сущность внутри готового конвейера и и замену. Урок — изменить расчетную сущность внутри готового конвейера и
@@ -86,7 +115,7 @@
Форма — вычитание. Обратную связь дают `dq_check` и поштучная сверка, Форма — вычитание. Обратную связь дают `dq_check` и поштучная сверка,
построенная на третьей ступени. построенная на третьей ступени.
### Ступень 5. Сначала сторож, потом ломай: заказы ### Ступень 5. Проверьте заказы перед изменением обработки
Сначала менти строит две недостающие DQ-проверки. Первая утверждает равенство Сначала менти строит две недостающие DQ-проверки. Первая утверждает равенство
«цена × количество = items_total». Вторая сверяет слой с источником по дням. Их «цена × количество = items_total». Вторая сверяет слой с источником по дням. Их