From 4058e18864273085156b74887a5755246a3b6bb3 Mon Sep 17 00:00:00 2001 From: Dmitry Dementev Date: Sun, 13 Sep 2026 11:01:35 -0400 Subject: [PATCH 1/3] =?UTF-8?q?docs(labs):=20=D1=83=D0=BF=D1=80=D0=BE?= =?UTF-8?q?=D1=89=D0=B5=D0=BD=20=D1=8F=D0=B7=D1=8B=D0=BA=20=D1=83=D1=87?= =?UTF-8?q?=D0=B5=D0=B1=D0=BD=D1=8B=D1=85=20=D0=B7=D0=B0=D0=B4=D0=B0=D0=BD?= =?UTF-8?q?=D0=B8=D0=B9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Зачем: помочь менти понять действия, проверить результат и оценить освоенное умение. Что: вычитаны 13 заданий основной программы и общий индекс. Итоговые списки обращены к учащемуся, убраны история разработки и повторы. Правила учебного текста записаны в программе, из AGENTS.md добавлена ссылка. Проверка: git diff --check, локальные ссылки и ссылки на измененные заголовки прошли. Блоки кода и таблицы требований совпадают с исходными. Упражнения на стенде повторно не выполнялись: изменен только текст. --- AGENTS.md | 3 + docs/labs/1-1-events-enriched.md | 23 +++---- docs/labs/1-2-top-pages-daily.md | 24 +++---- docs/labs/1-3-session-overview.md | 31 ++++----- docs/labs/1-4-dq-errors-daily.md | 28 ++++---- docs/labs/1-5-utm-effectiveness.md | 25 ++++---- docs/labs/2-1-superset-chart.md | 21 +++--- docs/labs/3-1-sessions-by-hand.md | 29 +++++---- docs/labs/4-1-session-pipeline.md | 35 +++++----- docs/labs/5-1-order-checks.md | 52 +++++++-------- docs/labs/5-2-order-parsing.md | 41 ++++++------ docs/labs/5-3-order-pipeline.md | 42 ++++++------ docs/labs/6-1-event-parsing.md | 92 +++++++++++++-------------- docs/labs/7-1-monitoring.md | 33 +++++----- docs/labs/README.md | 42 ++++++------ docs/specs/2026-09-01-labs-program.md | 35 +++++++++- 16 files changed, 287 insertions(+), 269 deletions(-) diff --git a/AGENTS.md b/AGENTS.md index 4b11367..594c75e 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -81,6 +81,9 @@ Airflow) и названия из кода. Если для понятия ес писал: простые слова, короткие фразы, сложную мысль поясняй при первом упоминании. Если понятность и буквальная точность спорят — выбирай понятность. +При создании или редактировании лабораторных следуй +[правилам учебного текста](docs/specs/2026-09-01-labs-program.md#как-писать-задания). + Внутренние рассуждения и промежуточные пометки по ходу работы веди на английском — он экономнее по токенам. На русском остаётся всё, что видит и хранит проект: итоговые ответы, документы, комментарии в коде и SQL, сообщения diff --git a/docs/labs/1-1-events-enriched.md b/docs/labs/1-1-events-enriched.md index 3de404a..0a72405 100644 --- a/docs/labs/1-1-events-enriched.md +++ b/docs/labs/1-1-events-enriched.md @@ -1,7 +1,7 @@ # Задание 1: `dm.events_enriched_v` -**Чему учит:** строить слой как договор о данных, а не как их копию. -Оставлять витрину представлением, если зерно источника не меняется. +**Чему учит:** подготовить удобный для анализа набор полей события +и обойтись представлением, сохранив одну строку на событие. ## Сначала прочитать @@ -9,7 +9,7 @@ - [Как применить свой DDL](README.md#как-применить-свой-ddl): файл для решения и подключение с правами на создание витрины. -## Роль витрины +## Для чего нужна витрина В `dds.event_v` 44 колонки, включая технические поля источника и массивы товаров. Для анализа событий в Superset нужны деловые поля и три готовых @@ -88,15 +88,10 @@ DDS читают из ODS без отдельной загрузки. Эти с Отдельно сверьте `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` нет. +- [ ] у вас есть `dm.events_enriched_v` с явно перечисленными колонками; +- [ ] вы сверили число строк и набор `event_id` с `dds.event_v` за каждый + прожитый день; расхождений нет; +- [ ] вы запустили `events_enriched` в `homework_check`, проверка успешна; +- [ ] витрина работает без физической таблицы и задачи в `dm_transform`. diff --git a/docs/labs/1-2-top-pages-daily.md b/docs/labs/1-2-top-pages-daily.md index 5a1bd41..b936435 100644 --- a/docs/labs/1-2-top-pages-daily.md +++ b/docs/labs/1-2-top-pages-daily.md @@ -1,14 +1,14 @@ # Задание 2: `dm.top_pages_daily` -**Чему учит:** создавать витрину с новым зерном: пять объектов, донор и -замена дневных партиций. Включать ее пересборку в `dm_transform`. +**Чему учит:** собрать дневные показатели страниц и настроить их +обновление в `dm_transform` через замену партиций. ## Сначала прочитать - [«Хранилище и его слои»](../handbook/storage-layers.md); - [«Потоки Airflow и даги модельного времени»](../handbook/airflow-dags.md). -## Роль витрины +## Для чего нужна витрина По дневным показателям страниц аналитик сравнивает просмотры, посетителей и действия с товарами. Добавление в корзину происходит на карточке товара, @@ -98,13 +98,13 @@ Сумма `visitors` по страницам повторно учитывает посетителя, открывшего несколько страниц. Число посетителей за день берите из `dm.daily_traffic_v`. -## Приемка +## Что должно получиться -- [ ] созданы пять объектов, донор объявлен через `AS` и шардируется ключом - цели; -- [ ] в `dm.top_pages_daily_v` явно перечислены колонки, включая `_load_id` - и `_load_ts`; -- [ ] объем прогона определяется по состоянию витрины без ручного задания; -- [ ] задача `top_pages_daily` в `homework_check` завершилась успешно; -- [ ] задача витрины добавлена в `dm_transform`, повторный прогон конвейера - не меняет результат. +- [ ] у вас есть пять объектов витрины; донор объявлен через `AS` и + использует тот же ключ шардирования, что и целевая таблица; +- [ ] вы явно перечислили колонки в `dm.top_pages_daily_v`, включая + `_load_id` и `_load_ts`; +- [ ] ваш запрос сам определяет дни пересборки по состоянию витрины; +- [ ] вы запустили `top_pages_daily` в `homework_check`, проверка успешна; +- [ ] вы включили витрину в `dm_transform` и убедились, что повторный + прогон конвейера не меняет результат. diff --git a/docs/labs/1-3-session-overview.md b/docs/labs/1-3-session-overview.md index c2d2961..c3ee54f 100644 --- a/docs/labs/1-3-session-overview.md +++ b/docs/labs/1-3-session-overview.md @@ -1,24 +1,21 @@ # Задание 3: `dm.session_overview` -**Чему учит:** хранить слагаемые, а не готовые средние и доли. Понимать, -почему сумма `visitors` по строкам сейчас сходится, но начнет завышать -аудиторию, если у одной куки появятся визиты с двух устройств. +**Чему учит:** хранить слагаемые для расчета средних и долей и понимать, +когда сложение числа посетителей по устройствам завышает аудиторию. ## Сначала прочитать - [«Хранилище и его слои»](../handbook/storage-layers.md); - [«Потоки Airflow и даги модельного времени»](../handbook/airflow-dags.md). -## Роль витрины +## Для чего нужна витрина Витрина нужна, чтобы по дням сравнивать визиты с телефона и компьютера: их число, длительность, просмотры страниц и частоту покупок. -На прошлом стенде витрина выделяла визиты сама. Теперь их рассчитывают в -DDS, а повторять зерно сессий ради представления с переименованными -колонками незачем. Здесь вы учитесь дневной агрегации, поэтому зерно — день -и устройство. Выбор описан в разделе «Форма: по зерну, не по цене запроса» -[справочника DM](../architecture/dm.md). +Сессии уже рассчитаны в DDS. Из них вы соберете дневные показатели по +устройствам. Почему такие итоги хранят в отдельной таблице, объясняет +[справочник DM](../architecture/dm.md). ## Что сделать @@ -107,12 +104,12 @@ DDS, а повторять зерно сессий ради представле повторного учета визитов; - сумма `events_total` равна сумме `events_total` по сессиям того же дня. -## Приемка +## Что должно получиться -- [ ] созданы пять объектов, донор объявлен через `AS` и шардируется ключом - цели; -- [ ] в `dm.session_overview_v` колонки перечислены явно; -- [ ] источник — `dds.session_v`, повторного выделения визитов нет; -- [ ] физическая таблица не хранит средние и доли; -- [ ] задача `session_overview` в `homework_check` завершилась успешно; -- [ ] задача витрины добавлена в `dm_transform`. +- [ ] у вас есть пять объектов витрины; донор объявлен через `AS` и + использует ключ шардирования целевой таблицы; +- [ ] вы явно перечислили колонки в `dm.session_overview_v` и собрали + показатели из готовых сессий `dds.session_v`; +- [ ] в таблице хранятся слагаемые для средних и долей; +- [ ] вы запустили `session_overview` в `homework_check`, проверка успешна; +- [ ] вы включили сборку витрины в `dm_transform`. diff --git a/docs/labs/1-4-dq-errors-daily.md b/docs/labs/1-4-dq-errors-daily.md index 13e2bff..ab5c35e 100644 --- a/docs/labs/1-4-dq-errors-daily.md +++ b/docs/labs/1-4-dq-errors-daily.md @@ -1,15 +1,14 @@ # Задание 4: `dm.dq_errors_daily` **Чему учит:** считать брак по дню загрузки и сохранять сводку дольше -исходных строк. Пересборка всех партиций после удаления источника по `TTL` -стирает историю, которую уже не восстановить. +исходных строк. ## Сначала прочитать - [«Хранилище и его слои»](../handbook/storage-layers.md); - [«Потоки Airflow и даги модельного времени»](../handbook/airflow-dags.md). -## Роль витрины +## Для чего нужна витрина Сводку открывают при росте брака в потоке: смотрят, каких ошибок стало больше и сколько строк забраковано. Причину конкретной ошибки исследуют @@ -97,17 +96,16 @@ `ods.event_errors_dist` и `ods.order_errors_dist`. Если она примерно вдвое меньше, вероятная причина — чтение `_rep` вместо `_dist`. -Пустая витрина на чистом стенде — верный ответ: обе стороны пусты, -расхождений ноль, проверка успешна. Требование хотя бы одной строки дало бы -постоянную ошибку, которую со временем стали бы игнорировать. +Если в источниках нет брака, витрина должна быть пустой. В этом случае +расхождений тоже ноль и проверка успешна. -## Приемка +## Что должно получиться -- [ ] созданы пять объектов, донор объявлен через `AS` и шардируется ключом - цели; -- [ ] источники — таблицы `_dist` обоих видов брака; -- [ ] прогон заменяет партиции только дней, еще имеющихся в источниках, и - сохраняет дни за пределами их срока хранения; -- [ ] задача `dq_errors_daily` в `homework_check` завершилась успешно, в том - числе на чистом стенде без брака; -- [ ] задача витрины добавлена в `dm_transform`. +- [ ] у вас есть пять объектов витрины; донор объявлен через `AS` и + использует ключ шардирования целевой таблицы; +- [ ] вы собираете сводку из таблиц `_dist` для ошибок событий и заказов; +- [ ] ваш запрос пересобирает только дни, еще доступные в источниках, + сохраняя более старые итоги; +- [ ] вы запустили `dq_errors_daily` в `homework_check`, проверка успешна; + отсутствие брака тоже дает успешный результат; +- [ ] вы включили сборку витрины в `dm_transform`. diff --git a/docs/labs/1-5-utm-effectiveness.md b/docs/labs/1-5-utm-effectiveness.md index 3853828..4eeb157 100644 --- a/docs/labs/1-5-utm-effectiveness.md +++ b/docs/labs/1-5-utm-effectiveness.md @@ -1,15 +1,14 @@ # Задание 5: `dm.utm_effectiveness` -**Чему учит:** понимать предупреждение в приставке `declared_`: это сумма со -слов клиента, а не деньги отчетности. Самостоятельно находить причины -расхождения двух сумм выручки. +**Чему учит:** различать сумму покупки со слов клиента (`declared_`) и +серверную выручку, находить причины их расхождения. ## Сначала прочитать - [«Хранилище и его слои»](../handbook/storage-layers.md); - [«Потоки Airflow и даги модельного времени»](../handbook/airflow-dags.md). -## Роль витрины +## Для чего нужна витрина Аналитик сравнивает рекламные каналы по дням: трафик, добавления в корзину, покупки и заявленные клиентом суммы. @@ -44,7 +43,7 @@ | `_load_ts` | `DateTime64(3, 'UTC')` | Время сборки | Для `declared_revenue` сложите `purchase_revenue[1]` событий `purchase`. В -параллельных массивах `purchase*` по контракту хранятся один номер заказа и +параллельных массивах `purchase*` хранятся один номер заказа и одна сумма на событие. В сверке `purchase_vs_orders` используют тот же индекс. @@ -127,12 +126,12 @@ Сумма `visitors` по строкам повторно учитывает одного посетителя, приходящего по разным меткам. Для числа посетителей дня она не подходит. -## Приемка +## Что должно получиться -- [ ] созданы пять объектов, донор объявлен через `AS` и шардируется ключом - цели; -- [ ] сохранены строки без UTM-меток; -- [ ] `declared_revenue` хранится в `Decimal(18, 2)`; -- [ ] расхождение с `dm.revenue_daily_v` объяснено письменно; -- [ ] задача `utm_effectiveness` в `homework_check` завершилась успешно; -- [ ] задача витрины добавлена в `dm_transform`. +- [ ] у вас есть пять объектов витрины; донор объявлен через `AS` и + использует ключ шардирования целевой таблицы; +- [ ] вы сохранили строки без UTM-меток и используете `Decimal(18, 2)` + для `declared_revenue`; +- [ ] вы записали, почему сумма отличается от `dm.revenue_daily_v`; +- [ ] вы запустили `utm_effectiveness` в `homework_check`, проверка успешна; +- [ ] вы включили сборку витрины в `dm_transform`. diff --git a/docs/labs/2-1-superset-chart.md b/docs/labs/2-1-superset-chart.md index c300da1..22da07d 100644 --- a/docs/labs/2-1-superset-chart.md +++ b/docs/labs/2-1-superset-chart.md @@ -8,7 +8,7 @@ - [«Немного Superset»](../handbook/superset.md); - [задание 3: `dm.session_overview`](1-3-session-overview.md). -## Роль графика +## На какой вопрос ответит график В задании 3 вы собрали витрину `dm.session_overview_v`. Теперь покажите, с каких устройств чаще уходят после первой страницы. Постройте столбиковый @@ -60,8 +60,8 @@ FROM dm.session_overview_v; ## Как проверить себя -График проверяется глазами, поэтому задачи для него в `homework_check` нет. -В SQL Lab выполните запрос: +Автоматической проверки этого задания нет. Сверьте числа на графике +с результатом запроса в SQL Lab: ```sql SELECT device_category, @@ -76,15 +76,16 @@ ORDER BY device_category; проценты с одним знаком, как формат `.1%`, поэтому числа должны совпасть один в один. -## Приемка +## Что должно получиться -- [ ] создан физический датасет `dm.session_overview_v` в подключении +- [ ] у вас есть физический датасет `dm.session_overview_v` в подключении `ClickHouse`; -- [ ] в датасете есть метрика «Доля отказов» с выражением +- [ ] вы сохранили в нем метрику «Доля отказов» с выражением `SUM(bounce_sessions) / SUM(sessions)` и форматом `.1%`; -- [ ] для `device_category` и `report_date` заданы русские имена «Категория +- [ ] вы назвали `device_category` и `report_date` по-русски: «Категория устройства» и «Дата отчета»; -- [ ] сохранен столбиковый график доли отказов по категориям устройств без +- [ ] у вас сохранен столбиковый график доли отказов по категориям устройств без относительного фильтра по времени; -- [ ] график сверен с запросом из SQL Lab; -- [ ] стендовые объекты не изменены, дашборд не создан. +- [ ] вы сверили числа на графике с запросом из SQL Lab; +- [ ] вы сохранили график отдельно, без создания дашборда и изменения + стендовых объектов. diff --git a/docs/labs/3-1-sessions-by-hand.md b/docs/labs/3-1-sessions-by-hand.md index f991ad5..58ca3b8 100644 --- a/docs/labs/3-1-sessions-by-hand.md +++ b/docs/labs/3-1-sessions-by-hand.md @@ -1,14 +1,14 @@ -# Нарежь сессии сам +# Нарежьте сессии сами -**Чему учит:** собрать сессии из событий и сверить их число с внешним -эталоном. Найти причину расхождения в отдельных строках. +**Чему учит:** собрать сессии из событий, сравнить их число с `VisitID` +источника и найти события, из-за которых расчеты расходятся. ## Сначала прочитать - [Хранилище и его слои, раздел DDS](../handbook/storage-layers.md#dds-язык-бизнеса); - [формат события: Идентификаторы и время](../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 пропускает расследование. +со своими. Сопоставление со стендовой нарезкой по `session_id` остается на ступень 4. -## Приемка +## Что должно получиться -- [ ] запрос нарезает все события `ods.event_v` за все доступные дни; -- [ ] сверка через `FULL OUTER JOIN` дает сводку по дням и список кук; -- [ ] при разборе расхождения указаны кука, день, два события и причина; -- [ ] уточненный расчет дает ноль расхождений во всех днях. +- [ ] ваш запрос собирает сессии из всех событий `ods.event_v` за все + доступные дни; +- [ ] у вас есть сводка расхождений по дням и список кук, полученные через + `FULL OUTER JOIN`; +- [ ] вы объяснили причину одного расхождения и записали куку, день и два + события, на которых оно видно; +- [ ] вы уточнили расчет и получили ноль расхождений во всех днях. diff --git a/docs/labs/4-1-session-pipeline.md b/docs/labs/4-1-session-pipeline.md index 61ce3c1..7671f2c 100644 --- a/docs/labs/4-1-session-pipeline.md +++ b/docs/labs/4-1-session-pipeline.md @@ -1,16 +1,16 @@ -# Перепиши нарезку стенда +# Перепишите нарезку стенда -**Чему учит:** изменить расчетную сущность внутри готового конвейера и -проверить результат внешним эталоном. +**Чему учит:** переписать расчет сессий в готовом конвейере и проверить +результат по исходным событиям. ## Сначала прочитать -- [задание «Нарежь сессии сам»](3-1-sessions-by-hand.md); +- [задание «Нарежьте сессии сами»](3-1-sessions-by-hand.md); - [модель сессии DDS](../architecture/dds/session.md); - [дневной конвейер трансформаций](../architecture/etl.md#объём-прогона); - [раскладка по шардам](../architecture/storage.md#раскладка-по-шардам). -## Граница задания +## Что предстоит сделать Даг, таблицы сессий и проверка их числа уже созданы. Перепишите только три файла: @@ -24,7 +24,7 @@ служебная таблица, где день собирается перед заменой. В `sessions_vs_reference.sql` число сессий сравнивается с `VisitID` из ODS. -## Договор с дагом +## Как даг запускает ваши запросы Объем прогона — дни, которые соберет один запуск. Его вычисляет первый файл. @@ -74,7 +74,7 @@ ClickHouse в `ALTER ... PARTITION` здесь не работает. Состав и типы строки заданы в `dds.session_rep`. Заполните начало, конец и длительность. Посчитайте все события, просмотры, добавления в корзину и покупки. Возьмите входную и выходную страницы, UTM первого события, устройство -и город. Служебные колонки тоже входят в договор таблицы. +и город. Заполните также служебные колонки из схемы таблицы. Пишите через `dds.session_stage_dist`: место строки определит ключ `client_id`. При записи прямо в локальную таблицу оно зависело бы от ноды, @@ -178,15 +178,14 @@ Distributed-таблицу и такую ошибку не заметит. партицию. Расчет объема должен пропустить группу сессий: новых прожитых дней и дыр в слое нет. -## Приемка +## Что должно получиться -- [ ] переписаны только три файла `sql/dds/session_*.sql`, инфраструктура - упражнения не изменена; -- [ ] запуск после удаления партиции восстановил выбранный день и завершил - `etl_pipeline` успешно; -- [ ] сохраненные `session_id`, границы, длительность и счетчики десяти строк - совпали после пересборки; -- [ ] `sessions_vs_reference` дает ноль расхождений во всех прожитых днях; -- [ ] для одной куки вручную сверены границы и атрибуты сессии; -- [ ] пересчет ключа по `hostName()` не нашел строк на чужом шарде; -- [ ] повторный прогон без новой дыры пропустил группу сессий. +- [ ] вы переписали три файла `sql/dds/session_*.sql`, сохранив даг, + таблицы и проверку стенда; +- [ ] ваш расчет восстановил удаленный день, `etl_pipeline` завершился успешно; +- [ ] вы сверили десять сохраненных строк: `session_id`, границы, + длительность и счетчики совпали после пересборки; +- [ ] вы получили ноль расхождений в `sessions_vs_reference` за все + прожитые дни и вручную сверили границы и атрибуты сессии одной куки; +- [ ] ваш запрос с `hostName()` не нашел строк на чужом шарде; +- [ ] вы повторили прогон без удаления данных: он пропустил группу сессий. diff --git a/docs/labs/5-1-order-checks.md b/docs/labs/5-1-order-checks.md index e73bcc2..48f0c9b 100644 --- a/docs/labs/5-1-order-checks.md +++ b/docs/labs/5-1-order-checks.md @@ -1,7 +1,7 @@ -# Сначала сторож, потом ломай: заказы +# Проверьте заказы перед изменением обработки -**Чему учит:** до рискованного изменения задавать проверке явный договор и -испытывать его управляемой ошибкой. +**Чему учит:** написать проверки заказов до изменения их обработки +и убедиться на намеренной ошибке, что проверки ее замечают. ## Сначала прочитать @@ -13,30 +13,30 @@ и на ее включение в [`dq_check`](../../dags/dq_check.py). Готового ответа для двух проверок из этого задания в репозитории нет. -## Граница задания +## Что предстоит сделать -Добавьте два сторожа: +Добавьте две проверки и включите их в даг: - `sql/dq/order_items_total.sql` проверяет сумму позиций заказа; - `sql/dq/orders_vs_ods.sql` сверяет DDS с его источником; - `dags/dq_check.py` запускает обе проверки, публикует их строки в `dm.dq_summary` и создает для них отдельные задачи. -На этом шаге не меняйте разбор ODS и сборку DDS. Даги, DDL, таблицы-доноры, -Kafka и службы остаются несущей частью упражнения. +Разбор ODS и сборку DDS вы перепишете в следующих заданиях. Здесь сохраните +их, даги загрузки, DDL, таблицы-доноры, Kafka и службы как есть. Перед опытами остановите автоматическое движение мира: поставьте `world_live` -на паузу и дождитесь завершения активных прогонов. До чистой точки остановки не +на паузу и дождитесь завершения активных прогонов. До завершения опытов не запускайте руками `world_next_day`, `world_live_day`, `world_initialize` и `world_recreate`. Это условие стабильного модельного времени: одной паузы `world_live` для него мало. -## Два договора качества +## Что будут проверять ваши запросы -Обе проверки следуют договору `dq_check`: запрос возвращает по одной строке +Чтобы `dq_check` мог запустить проверку, ее запрос возвращает по одной строке на день с колонками `data_date`, `reference_rows`, `actual_rows`, `failed_rows`, `diagnostics`. Даг сохраняет в `dm.dq_summary` только счетчики, -статус и координаты запуска. Ограниченные примеры из `diagnostics` ищите в +статус и координаты запуска. Примеры из `diagnostics` ищите в журнале упавшей задачи Airflow. Ограничьте их тем же малым числом, что и существующие запросы в `sql/dq/`. @@ -46,17 +46,18 @@ Kafka и службы остаются несущей частью упражн посчитайте сумму `item_price * item_quantity` и сравните ее с `items_total`. `reference_rows` считает ключи со стороны `items_total`, `actual_rows` - ключи со стороны вычисленной суммы, а `failed_rows` - несовпавшие значения. В -исправном наборе первые два счетчика равны по построению; сам инвариант выражен -через `failed_rows`. Для диагностики достаточно `order_id`, ожидаемой суммы и +исправном наборе первые два счетчика равны; несовпадения сумм считает +`failed_rows`. Для диагностики достаточно `order_id`, ожидаемой суммы и суммы позиций. Проверка относится к типизированному разбору массива `items` на границе -ODS -> DDS. Скидка, доставка и равенство `total` в этот договор не входят. +ODS -> DDS. Скидку, доставку и равенство `total` здесь проверять не нужно. ### DDS против ODS Имя проверки - `orders_vs_ods`. Источник модели DDS здесь - текущее состояние -`ods.order_v`. Внутреннее состояние генератора в этот договор не входит. +`ods.order_v`. С ним вы и сравниваете DDS; обращаться к внутреннему состоянию +генератора для этой проверки не нужно. Одним запросом соберите обе стороны на зерне дня заказа и сравните: - число заказов; @@ -131,18 +132,17 @@ ORDER BY check_name; Проверка суммы должна охватывать каждый день `dds.order_v`. Сверка слоев должна охватывать объединение дней ODS и DDS, включая дни только одной стороны. -## Когда можно остановиться +## Завершите опыт -После двух восстановлений выбранный заказ снова совпадает с ODS. Все семь -проверок дают зеленый результат. В таком состоянии можно закончить работу или +После восстановления заказа и успешного `dq_check` можно закончить работу или перейти к разбору заказов, оставив модельное время на паузе. -## Приемка +## Что должно получиться -- [ ] две новые проверки встроены в `dq_check` и публикуют дневные строки по - принятому договору; -- [ ] каждая проверка дала красный результат на своей управляемой ошибке; -- [ ] журналы упавших задач содержали ограниченную диагностику; -- [ ] `etl_pipeline` восстановил обе измененные строки DDS; -- [ ] `order_items_total`, `orders_vs_ods` и пять прежних DQ зелены; -- [ ] SQL разбора ODS и сборки DDS на этом шаге не менялся. +- [ ] вы включили две новые проверки в `dq_check`; их дневные результаты + появляются в `dm.dq_summary`; +- [ ] вы увидели ошибку каждой проверки после соответствующего изменения + заказа, а в журнале нашли примеры заказов с расхождениями; +- [ ] вы восстановили заказ через `etl_pipeline` после обоих опытов; +- [ ] `order_items_total`, `orders_vs_ods` и пять прежних проверок успешны; +- [ ] разбор ODS и сборка DDS остались прежними. diff --git a/docs/labs/5-2-order-parsing.md b/docs/labs/5-2-order-parsing.md index 1662017..fb20274 100644 --- a/docs/labs/5-2-order-parsing.md +++ b/docs/labs/5-2-order-parsing.md @@ -1,7 +1,7 @@ # Перепишите разбор заказов в ODS -**Чему учит:** сначала измерить обе ветви разбора на управляемом срезе, затем -переписать переход и повторить тот же опыт. +**Чему учит:** переписать разбор заказов и на пробных сообщениях проверить, +что годные строки и брак попадают в свои таблицы без потерь. ## Сначала прочитать @@ -11,9 +11,9 @@ - [`orders_ingest`](../../dags/orders_ingest.py); - [`order_parse.sql`](../../sql/ods/order_parse.sql). -Готового запроса для сверки управляемого среза в репозитории нет. +Запрос для проверки пробных строк вы напишете сами. -## Граница задания +## Что предстоит сделать Сначала проведите опыт на готовом `order_parse.sql`. Затем удалите его SQL, соберите разбор заново и повторите опыт с другим маркером. @@ -72,7 +72,7 @@ jq -cn --arg marker "$LAB_MARKER" --arg day "$LAB_DAY" ' ' > "$LAB_BATCH" ``` -Отправьте файл в обычный топик заказов штатным производителем контейнера: +Отправьте файл в обычный топик заказов утилитой из контейнера Kafka: ```bash 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` команда ждет их завершения на всех репликах. Запросами к трем Distributed-таблицам -докажите, что строк маркера не осталось, затем удалите временный файл. +убедитесь, что строк маркера не осталось, затем удалите временный файл. Группа `clickstream_orders` уже подтвердила эти сообщения, поэтому обычный повтор `orders_ingest` их не прочитает. В Kafka они остаются до удаления по @@ -137,8 +137,8 @@ SETTINGS mutations_sync = 2; ## Перепишите разбор -Удалите готовый SQL из `sql/ods/order_parse.sql` и сохраните его договор с -`orders_ingest`: +Удалите готовый SQL из `sql/ods/order_parse.sql` и напишите свой. Чтобы +`orders_ingest` мог его использовать, сохраните следующие условия: - вход - строки `stg.orders_raw_dist` одного `{load_id:String}`; - каждая строка среза попадает ровно в один результат: годный заказ или брак; @@ -161,21 +161,20 @@ SETTINGS mutations_sync = 2; шард. Затем очистите второй срез теми же адресными командами и запустите `dq_check`. -## Когда можно остановиться +## Завершите опыт -Оба маркера отсутствуют в STG, ODS, таблице брака и DDS; временные файлы -удалены. Семь DQ дают зеленый результат. В таком состоянии можно перейти к +Убедитесь, что оба маркера отсутствуют в STG, ODS, таблице брака и DDS, +временные файлы удалены, а семь проверок успешны. Теперь можно перейти к сборке DDS, не снимая паузу с модельного времени. Если вы прерываетесь и до опыта `world_live` работал, снимите его с паузы. -## Приемка +## Что должно получиться -- [ ] до переписывания зафиксированы баланс 4 = 1 + 3, три класса брака, - деловые поля текущей версии и фактический шард; -- [ ] переписан только `sql/ods/order_parse.sql`, несущая инфраструктура не - изменена; -- [ ] после переписывания новый срез дал тот же результат; -- [ ] обе годные строки различали `created_at` и `updated_at`; -- [ ] проверки `hostName()` не нашли строки ODS на чужом шарде; -- [ ] строки обоих опытов удалены только по своим маркерам; -- [ ] после очистки общий конвейер и семь DQ находятся в зеленом состоянии. +- [ ] у вас сохранены результаты до переписывания: четыре строки дали + один заказ и три класса брака; вы сверили поля текущей версии и шард; +- [ ] вы переписали `sql/ods/order_parse.sql`, сохранив остальные части стенда; +- [ ] ваш разбор дал тот же результат на новой пробной пачке; +- [ ] вы проверили разные значения `created_at` и `updated_at` у годной + строки в обоих опытах и подтвердили правильный шард через `hostName()`; +- [ ] вы удалили строки обоих опытов по их маркерам; +- [ ] после очистки конвейер и все семь проверок успешны. diff --git a/docs/labs/5-3-order-pipeline.md b/docs/labs/5-3-order-pipeline.md index cd5b867..e45f340 100644 --- a/docs/labs/5-3-order-pipeline.md +++ b/docs/labs/5-3-order-pipeline.md @@ -1,7 +1,7 @@ # Перепишите сборку заказов в DDS -**Чему учит:** переписать пересборку заказов по состоянию слоев и доказать ее -результат на пропущенном закрытом дне. +**Чему учит:** настроить пересборку заказов по состоянию ODS и DDS +и проверить, что она восстанавливает пропущенный день за пределами текущего окна. ## Сначала прочитать @@ -11,7 +11,7 @@ - [дневной конвейер](../architecture/etl.md#объём-прогона); - [раскладку по шардам](../architecture/storage.md#раскладка-по-шардам). -## Граница задания +## Что предстоит сделать Удалите готовый SQL и перепишите только три файла: @@ -19,8 +19,8 @@ - `sql/dds/order_rebuild.sql`; - `sql/dds/order_replace.sql`. -Даг `dds_transform`, DDL, донор, ODS и обе проверки из первого задания уже -задают границу работы. Перед началом убедитесь, что пробные строки предыдущего +Даг `dds_transform`, DDL, донор, ODS и обе проверки из первого задания +оставьте как есть. Перед началом убедитесь, что пробные строки предыдущего задания удалены, а семь DQ дают зеленый результат. Поставьте `world_live` на паузу и дождитесь завершения активных прогонов. До @@ -28,7 +28,7 @@ `world_recreate`. `world_next_day` понадобится только в явно указанном ниже случае. -## Договор с дагом +## Как даг запускает ваши запросы Сохраните интерфейс `dds_transform`: @@ -103,22 +103,22 @@ DROP PARTITION 'YYYY-MM-DD'; заказов снова должна пересобрать текущее изменяемое окно. Контрольные суммы деловых полей остаются прежними, а все семь проверок дают зеленый результат. -## Когда можно остановиться +## Завершите опыт -Удаленная партиция восстановлена, ее число строк и контрольная сумма совпали -с исходными. На чужих шардах строк нет, повторный прогон не изменил деловые -данные, семь DQ зелены. Если до упражнения `world_live` работал, теперь его -можно снять с паузы. +После восстановления партиции и успешных проверок снимите `world_live` +с паузы, если он работал до упражнения. -## Приемка +## Что должно получиться -- [ ] переписаны только три файла `sql/dds/order_*.sql`, инфраструктура - упражнения не изменена; -- [ ] удален ближайший к окну закрытый день, а красный DQ зафиксирован до +- [ ] вы переписали три файла `sql/dds/order_*.sql`, сохранив остальные + части стенда; +- [ ] вы удалили ближайший к окну закрытый день и увидели ошибку DQ до восстановления; -- [ ] `first_day` начался с удаленного дня, задачи замены покрыли непрерывный - отрезок до конца окна; -- [ ] число заказов и контрольная сумма восстановленного дня совпали; -- [ ] `order_items_total`, `orders_vs_ods` и пять прежних DQ зелены; -- [ ] проверка `hostName()` не нашла заказов DDS на чужом шарде; -- [ ] повторный `etl_pipeline` сохранил деловые данные и зеленые проверки. +- [ ] ваш расчет выбрал `first_day` с удаленного дня и заменил партиции + всего отрезка до конца окна; +- [ ] вы сверили число заказов и контрольную сумму восстановленного дня + с сохраненными значениями; они совпали; +- [ ] `order_items_total`, `orders_vs_ods` и пять прежних проверок успешны; +- [ ] ваш запрос с `hostName()` не нашел заказов на чужом шарде; +- [ ] вы повторили `etl_pipeline`: деловые данные остались прежними, + проверки прошли. diff --git a/docs/labs/6-1-event-parsing.md b/docs/labs/6-1-event-parsing.md index b817729..0fb0027 100644 --- a/docs/labs/6-1-event-parsing.md +++ b/docs/labs/6-1-event-parsing.md @@ -15,7 +15,7 @@ Готовый стендовый разбор находится в `sql/ddl/30-ods-views.sql`. Откройте его после собственной сверки: в нем лежит ответ на основную часть задания. -## Граница задания +## Что предстоит сделать Создайте в базе `default` одну свою таблицу годных событий и наполните ее одним пакетным `INSERT SELECT` из `stg.hits_raw_dist`. Работайте с первой нодой под @@ -72,11 +72,10 @@ Airflow. При включенном `world_live` посмотрите его т `ods.event_rep`, а также `_load_ts`. Тип `purchaseRevenue` возьмите из ODS: `Array(Decimal(18, 2))`. -Схлопните повтор доставки по договору ODS. Возьмите +Обработайте повторные доставки так же, как ODS. Возьмите `ReplacingMergeTree(_load_ts)`, партицию `EventDate` и полный ключ замены: `(CounterID, EventDate, intHash32(ClientID), WatchID)`. Достаточно одной -локальной таблицы: отдельная Distributed-таблица не добавит урока к пакетному -опыту на одной ноде. +локальной таблицы: загрузка и сверка в этом опыте выполняются на одной ноде. `WatchID` — логический идентификатор события, уникальный по [договору генератора](../specs/2026-08-01-generator.md). Физический ключ ODS @@ -85,8 +84,7 @@ Airflow. При включенном `world_live` посмотрите его т ## Разберите годные строки -Для каждой строки среза примите решение по тому же строгому входу, что и -стенд: +Для каждой строки среза проверьте те же условия приема, что и на стенде: 1. корень `raw` — JSON-объект; 2. набор верхнеуровневых ключей в точности равен 47 ключам формата; @@ -95,25 +93,25 @@ Airflow. При включенном `world_live` посмотрите его т Напишите разбор, который не прерывает пакет из-за одной грязной строки. Сами выберите способ безопасного извлечения пяти опорных полей, форму общих выражений -и место фильтра. Остальные поля извлеките по договору формата. +и место фильтра. Остальные поля извлеките по описанию формата. Перенесите `_load_ts` из STG без пересчета: это время исходной доставки и колонка версии. Время запуска вашего запроса сюда не входит. Вставьте в свою таблицу только годные строки выбранного среза. -Считайте отвергнутые строки прямо в исходном срезе: отдельная таблица брака в -задание не входит. Проверьте работу строгого входа одним запросом. Посчитайте: +Считайте отвергнутые строки прямо в исходном срезе, без отдельной таблицы +брака. Одним запросом посчитайте: - все доставки; -- строки, прошедшие ваш предикат; -- строки, отвергнутые его отрицанием. +- строки, прошедшие ваше условие отбора; +- строки, для которых это условие ложно. Проверьте равенство общего числа строк сумме прошедших и отвергнутых. Так каждая исходная строка учитывается ровно в одной группе. Если отвергнутые строки есть, сохраните их `raw`, `_load_ts` и координаты Kafka. Найдите те же координаты в `ods.event_errors_dist` и запишите стендовый `error_class`. Если брака нет, так и зафиксируйте: опыт проверил успешную ветвь и выполнение -предиката на этом срезе, но не классификатор ошибок. Подкладывать грязные +условия отбора на этом срезе, но не классификатор ошибок. Подкладывать грязные сообщения ради задания не требуется. ## Приведите стороны к одному зерну @@ -126,17 +124,17 @@ Airflow. При включенном `world_live` посмотрите его т полных ключей среди годных доставок. Разница между числом доставок и этим счетом показывает повторы в исходном срезе. -Область эталона постройте независимо от своего предиката и результата вставки. -Снова возьмите весь исходный STG-срез. По координатам Kafka исключите доставки, -которые есть в `ods.event_errors_dist`. У оставшихся доставок извлеките -`WatchID` без риска прервать запрос и выберите эти события из свежего +Выберите строки ODS для сравнения независимо от своего условия отбора и +результата вставки. Снова возьмите весь исходный STG-срез. По координатам Kafka +исключите доставки, которые есть в `ods.event_errors_dist`. У оставшихся +доставок извлеките `WatchID` без риска прервать запрос и выберите эти события из свежего `ods.event_v`. Свяжите сырую доставку с ODS по `WatchID`: по договору генератора это уникальный -идентификатор события. Он задает только область эталона. Дедупликация и -последующее соединение используют полный физический ключ. Не берите область из -своей таблицы и не применяйте к ней свой предикат. Иначе ошибочно отвергнутая -строка исчезнет с обеих сторон сверки. +идентификатор события. Здесь он нужен только для выбора событий ODS. +Удаление повторов и последующее соединение используют полный физический ключ. +Не берите список событий из своей таблицы и не применяйте к нему свое условие +отбора. Иначе ошибочно отвергнутая строка исчезнет с обеих сторон сверки. ## Сверьте 47 полей @@ -171,44 +169,46 @@ ClickHouse по умолчанию заполняет отсутствующую Для измененной строки назовите поля, которые расходятся, и сравните каждое с сырой строкой. Для пропавшей или лишней строки проверьте пять опорных полей и -решение строгого предиката. Если координаты доставки есть в +результат условия отбора. Если координаты доставки есть в `ods.event_errors_dist`, посмотрите `error_class`. Исправьте разбор, удалите и заново создайте только собственную таблицу, затем повторите вставку того же среза. Остановитесь, когда счет исходных доставок по -прежним границам не изменился, баланс строгого входа сошелся, а построчная -сверка пуста. +прежним границам не изменился, число годных и отвергнутых строк в сумме дает +число исходных доставок, а построчная сверка пуста. -## Где заканчивается проверка +## Чего не подтверждает сравнение с ODS `ods.event_v` — образец для сравнения. Оба разбора могли повторить одну -ошибку. Для пяти опорных полей у вас есть проверка разбираемости и исходный -JSON. Остальные 42 поля можно вручную сопоставить с `raw`, однако второго -независимого источника для всех значений на стенде нет. +ошибку. Для пяти опорных полей у вас есть проверка преобразования в нужный тип +и исходный JSON. Остальные 42 поля можно вручную сопоставить с `raw`, однако +второго независимого источника для всех значений на стенде нет. ## Уберите свой объект -Сохраните DDL таблицы, запрос разбора, границы среза, баланс строгого входа и -пустую итоговую сверку. Затем удалите свою таблицу командой `DROP TABLE` с ее -точным именем. Убедитесь через `system.tables`, что объекта больше нет. +Сохраните DDL таблицы, запрос разбора, границы среза, число исходных, годных +и отвергнутых строк и итоговую сверку без расхождений. Запишите также, чего +не подтверждает сравнение с ODS и как TTL ограничивает время для повторения +опыта. Затем удалите свою таблицу командой `DROP TABLE` с ее точным именем. +Убедитесь через `system.tables`, что объекта больше нет. Прикладные строки стенда вы не меняли, поэтому удалять данные из STG, ODS или Kafka после опыта не нужно. -## Приемка +## Что должно получиться -- [ ] записаны границы малого завершенного среза, его счет и координаты +- [ ] у вас сохранены границы выбранного среза, число строк и координаты доставки по партициям Kafka; -- [ ] создана одна собственная таблица со всеми 47 полями, перенесенным - `_load_ts` и физическим договором дедупликации ODS; -- [ ] пакетный разбор принимает только JSON-объекты с точным набором ключей и - пятью разбираемыми опорными полями; -- [ ] счет исходных строк равен сумме годных и отвергнутых, а найденный брак - рассмотрен по `raw`, координатам и стендовому классу; -- [ ] область ODS получена из всех координат исходного среза и стендовой - таблицы ошибок, независимо от собственного предиката и таблицы; -- [ ] обе стороны приведены к зерну полного физического ключа; -- [ ] `FULL OUTER JOIN` сравнивает все 47 полей, а числа пропавших, лишних и - измененных строк равны нулю; -- [ ] отдельно подтвержден перенос `_load_ts` и учтены повторы доставки; -- [ ] границы стендового оракула и TTL записаны вместе с результатом; -- [ ] собственная таблица удалена, объекты и данные стенда не изменены. +- [ ] вы собрали свою таблицу со всеми 47 полями и `_load_ts`, используя + те же правила удаления повторов, что и ODS; +- [ ] ваш разбор принимает только JSON-объекты с точным набором ключей и + пятью опорными полями, которые преобразуются в нужные типы; +- [ ] вы сверили число исходных строк с суммой годных и отвергнутых, + а найденный брак разобрали по `raw`, координатам и классу ошибки стенда; +- [ ] вы выбрали события ODS по исходному срезу и таблице ошибок, + независимо от своей таблицы и условия отбора; +- [ ] вы сравнили обе стороны по полному физическому ключу через + `FULL OUTER JOIN`: во всех 47 полях нет расхождений, + пропавших и лишних строк тоже нет; +- [ ] вы отдельно проверили перенос `_load_ts` и учли повторные доставки; +- [ ] вы записали ограничения сравнения с ODS и срок повторения опыта; +- [ ] после опыта вы удалили свою таблицу, сохранив объекты и данные стенда. diff --git a/docs/labs/7-1-monitoring.md b/docs/labs/7-1-monitoring.md index 9f5dd9e..4c0ad11 100644 --- a/docs/labs/7-1-monitoring.md +++ b/docs/labs/7-1-monitoring.md @@ -99,21 +99,20 @@ [«Данные»](../architecture/monitoring/data.md). Устройство живого дня и доставки слепка описано в [главе об Airflow](../handbook/airflow-dags.md). -## Приемка +## Что должно получиться -- [ ] для каждого из трех дашбордов записан вопрос, на который он отвечает; -- [ ] сохранены окно, текст и показатели верхнего запроса, определены его - источник и назначение; -- [ ] среди запросов открытых дашбордов найден тот, который прочитал больше - всего строк, и соответствующая панель; -- [ ] объяснено, как журнал собирают с двух нод и какие записи учитывают для - счета запросов; -- [ ] до наблюдения нарисован прогноз свежести событий и заказов, а форма - линии объяснена через метку последней доставки; -- [ ] записаны несколько наблюдений, на наброске видны рост и сброс свежести - заказов, а доставка подтверждена в Airflow и на панели слепков; -- [ ] модельная дата отделена от реального времени наблюдения; -- [ ] объяснен выбор слоев для свежести, потока и доли брака, а также общее - окно числителя и знаменателя; -- [ ] службы не остановлены, данные вручную не правились, стендовые дашборды - не изменены. +- [ ] вы записали по одному вопросу для каждого из трех дашбордов; +- [ ] у вас сохранены окно, текст и показатели верхнего запроса; + вы выяснили, кто его отправил и зачем; +- [ ] вы нашли запрос открытых дашбордов, который прочитал больше всего + строк, и панель, которая его отправила; +- [ ] вы можете объяснить, зачем журнал собирают с двух нод и какие записи + учитывают при подсчете запросов; +- [ ] у вас есть прогноз свежести, сделанный до наблюдения, и несколько + замеров; рост и сброс на рисунке объяснены через время последней доставки; +- [ ] вы подтвердили доставку заказов в Airflow и на панели слепков, + подписали модельную дату отдельно от реального времени наблюдений; +- [ ] вы можете объяснить выбор слоев для свежести, потока и доли брака + и почему числитель и знаменатель берут за одно окно; +- [ ] после наблюдений службы продолжают работать, данные и стендовые + дашборды остались без изменений. diff --git a/docs/labs/README.md b/docs/labs/README.md index 0ca33ee..05c69e2 100644 --- a/docs/labs/README.md +++ b/docs/labs/README.md @@ -10,28 +10,28 @@ |---|---|---| | 1 | Пять витрин слоя DM | построить результат для потребителя хранилища; уроки каждой витрины — в таблице ниже | | 2 | [Свой график в Superset](2-1-superset-chart.md) | превратить свой результат из хранилища в отчет в BI | -| 3 | [Нарежь сессии сам](3-1-sessions-by-hand.md) | сверить число сессий в своей нарезке с эталоном и найти события, из-за которых оно расходится | -| 4 | [Перепиши нарезку стенда](4-1-session-pipeline.md) | изменить расчетную сущность внутри готового конвейера и проверить ее внешним эталоном | -| 5 | [Сначала сторож, потом ломай: заказы](5-1-order-checks.md)
[Перепишите разбор заказов в ODS](5-2-order-parsing.md)
[Перепишите сборку заказов в DDS](5-3-order-pipeline.md) | написать проверки качества до рискованного изменения, затем переписать разбор и сборку заказов | -| 6 | [Разбери сырые события и сверь результат со стендом](6-1-event-parsing.md) | изучить разбор потока отдельно от живого приема и сверить его со стендовым результатом | +| 3 | [Нарежьте сессии сами](3-1-sessions-by-hand.md) | сверить число сессий в своей нарезке с эталоном и найти события, из-за которых оно расходится | +| 4 | [Перепишите нарезку стенда](4-1-session-pipeline.md) | переписать расчет сессий в готовом конвейере и проверить по исходным событиям | +| 5 | [Проверьте заказы перед изменением обработки](5-1-order-checks.md)
[Перепишите разбор заказов в ODS](5-2-order-parsing.md)
[Перепишите сборку заказов в DDS](5-3-order-pipeline.md) | написать проверки качества до рискованного изменения, затем переписать разбор и сборку заказов | +| 6 | [Разберите сырые события рядом со стендом](6-1-event-parsing.md) | изучить разбор потока отдельно от живого приема и сверить его со стендовым результатом | | 7 | [Мониторинг: куда смотреть](7-1-monitoring.md) | выбрать, куда и на что смотреть в живой системе | ## Ступень 1: пять витрин слоя 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) | создавать материализованную витрину с дневной пересборкой в конвейере | | 3 | [`session_overview`](1-3-session-overview.md) | хранить слагаемые для расчета средних и долей | | 4 | [`dq_errors_daily`](1-4-dq-errors-daily.md) | считать брак по дню загрузки и хранить итоги дольше источника | | 5 | [`utm_effectiveness`](1-5-utm-effectiveness.md) | читать `declared_` как предупреждение о сумме со слов клиента | -Общее устройство слоя — [дока DM](../architecture/dm.md). Она отвечает на +Общее устройство слоя — [справочник DM](../architecture/dm.md). Он отвечает на вопросы «почему так», задание отвечает на вопрос «что сделать». ## Как принято на ступени 1 @@ -99,9 +99,8 @@ DM](../architecture/dm.md). собрано верно. В сводку качества конвейера `dm.dq_summary` он не пишет: это инструмент лабы, а не история качества стенда. -Проверка пяти заданий по витринам устроена одинаково и золотых констант не -содержит: рядом с витриной независимым запросом собирается тот же ответ — -прямо из источника витрины, минуя ее саму. У четырех заданий источник +Для каждой витрины проверка независимо рассчитывает ожидаемый результат +прямо по ее источнику. У четырех заданий источник эталона тот же, что у витрины, `dds.session_v` или `dds.event_v`; у задания 4 это таблицы брака ODS, потому что в DDS нет сущности брака. Обе стороны соединяются по деловому ключу через `FULL OUTER JOIN`, и считается число @@ -109,16 +108,13 @@ DM](../architecture/dm.md). соединение сохраняет ключи обеих сторон: при внутреннем пропавший день или ключ не попал бы в результат, и потеря осталась бы незамеченной. -Раздел «Как проверить себя» каждого ТЗ по витрине называет деловой ключ и +Раздел «Как проверить себя» каждого задания по витрине называет деловой ключ и сравниваемые величины. Тот же прием в стендовом исполнении — [даг `dq_check`](../../dags/dq_check.py) и запросы в [`sql/dq/`](../../sql/dq). -**Предупреждение.** Запросы самой проверки лежат в `sql/dq/` открыто, и -имена называют витрину и роль сверки. Проверка на инвариантах неизбежно -близка к ответу: чтобы сверить витрину, надо ее пересчитать. Если открыть -эти файлы до выполнения задания, можно скопировать готовый SQL, не -разобравшись в расчете самостоятельно. Смотреть туда стоит после своей -сборки: сравнить два решения полезно, списать одно — нет. +Запросы проверок лежат в `sql/dq/`. Откройте их после собственного решения: +они заново рассчитывают показатели витрины, поэтому содержат ответ на +существенную часть задания. Сравните свой способ расчета с готовым. ## Факультативы @@ -152,12 +148,12 @@ DM](../architecture/dm.md). ### Что делать, когда стенд сломался -Открывается после ступени 7. Поломки стенда и runbook по итогам -расследований; весь раздел — повышенная сложность. +Открывается после ступени 7. Поломки стенда и инструкции по восстановлению +по итогам расследований; весь раздел — повышенная сложность. ## Уборка -Черновики и опыты за собой сносить: правило проверок стенда — -[дока тестирования](../architecture/testing.md). Снесенная витрина обязана -снова покраснить свою задачу в `homework_check` — если не покраснила, -проверка ничего и не проверяла. +После опытов удаляйте свои временные объекты. Если удалили учебную витрину, +ее задача в `homework_check` должна снова завершиться ошибкой: отсутствие +витрины тоже проверяется. Подробнее — в +[справочнике проверок](../architecture/testing.md). diff --git a/docs/specs/2026-09-01-labs-program.md b/docs/specs/2026-09-01-labs-program.md index a0bb100..bec0091 100644 --- a/docs/specs/2026-09-01-labs-program.md +++ b/docs/specs/2026-09-01-labs-program.md @@ -40,6 +40,35 @@ - *расследование* — данные уже лежат на стенде или стенд намеренно ломается, а менти ищет причину и объясняет ее. +### Как писать задания + +Задание обращается к учащемуся на «вы». Начните с «Чему учит»: назовите +умение, которое он получит. Затем объясните, для чего нужен результат, и +ведите читателя по действиям. Термины раскрывайте там, где без них нельзя +понять следующий шаг. Историю разработки оставляйте в рабочих справочниках. + +Раздел «Как проверить себя» описывает способ проверки: что выполнить, +с чем сравнить и где искать причину расхождения. Если проверка и есть ход +исследования, сохраните ее в шагах, без повторного раздела. В конце задания +«Что должно получиться» помогает учащемуся оценить результат: что у него +работает, что он проверил и что может объяснить. Чекбоксы обращены к нему: +«вы сверили числа», «у вас сохранен запрос». Подробный порядок действий +остается в основной части. + +Заголовки остальных разделов выбирайте по содержанию: «Для чего нужна +витрина», «Найдите причину», «Как даг запускает ваши запросы». Число разделов +и пунктов определяется упражнением. Ограничения объясняйте рядом с действием, +которого они касаются; завершение опыта включает восстановление данных или +удаление временных объектов, когда это требуется. + +При вычитке сохраняйте состав результата, технические условия, способы +проверки и самостоятельную часть работы. Новое упражнение или готовое решение +под видом пояснения меняет учебный смысл и требует отдельного обсуждения. +Формат инженерных issues из правил трекера относится к разработке стенда; +учебные задания пишутся по этим правилам. Образцы: +[дашборд Superset](../labs/optional/superset-dashboard.md) и +[расследование подключения](../labs/optional/superset-missing-object.md). + ## 3. Основная программа — семь ступеней ### Ступень 1. Построй пять витрин слоя DM @@ -64,7 +93,7 @@ ступени предстоит написать при реализации программы. Полный собственный дашборд остается факультативом в разделе «Superset и BI». -### Ступень 3. Нарежь сессии сам +### Ступень 3. Нарежьте сессии сами До того как открыть стендовый SQL нарезки сессий в DDS, менти самостоятельно делит события на визиты. Сначала он сравнивает число своих сессий с эталоном @@ -75,7 +104,7 @@ Форма — построить свое по ТЗ и провести расследование. Обратная связь состоит из счетной сверки с `VisitID` и ручного сопоставления границ. -### Ступень 4. Перепиши нарезку стенда +### Ступень 4. Перепишите нарезку стенда Менти удаляет и заново пишет SQL нарезки сессий DDS: расчет объема, пересборку и замену. Урок — изменить расчетную сущность внутри готового конвейера и @@ -86,7 +115,7 @@ Форма — вычитание. Обратную связь дают `dq_check` и поштучная сверка, построенная на третьей ступени. -### Ступень 5. Сначала сторож, потом ломай: заказы +### Ступень 5. Проверьте заказы перед изменением обработки Сначала менти строит две недостающие DQ-проверки. Первая утверждает равенство «цена × количество = items_total». Вторая сверяет слой с источником по дням. Их From 3bfe9ca4ee6f5711eaf61516330f64b1188616e4 Mon Sep 17 00:00:00 2001 From: Dmitry Dementev Date: Sun, 13 Sep 2026 11:20:03 -0400 Subject: [PATCH 2/3] =?UTF-8?q?docs(labs):=20=D1=83=D1=82=D0=BE=D1=87?= =?UTF-8?q?=D0=BD=D0=B5=D0=BD=D1=8B=20=D1=81=D0=B0=D0=BC=D0=BE=D0=BF=D1=80?= =?UTF-8?q?=D0=BE=D0=B2=D0=B5=D1=80=D0=BA=D0=B0=20=D0=B8=20=D1=84=D0=BE?= =?UTF-8?q?=D1=80=D0=BC=D1=83=D0=BB=D0=B8=D1=80=D0=BE=D0=B2=D0=BA=D0=B8=20?= =?UTF-8?q?=D0=B7=D0=B0=D0=B4=D0=B0=D0=BD=D0=B8=D0=B9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Зачем: убрать механические повторы в итогах и избежать ложных расхождений при проверке графика Superset. Что: уточнены правила итоговых списков и название задания о проверках заказов. В разборе событий разделены учет повторных доставок и выбор событий ODS для сравнения. Первый график Superset и проверочный SQL используют весь период витрины. Проверка: git diff --check, локальные ссылки и переходы к измененным заголовкам прошли. Блоки кода и таблицы требований сохранены. Согласованность периода графика с SQL проверена по тексту; повторный проход через интерфейс Superset не выполнялся. --- docs/labs/1-1-events-enriched.md | 2 +- docs/labs/1-2-top-pages-daily.md | 4 ++-- docs/labs/1-3-session-overview.md | 4 ++-- docs/labs/1-4-dq-errors-daily.md | 6 +++--- docs/labs/1-5-utm-effectiveness.md | 8 ++++---- docs/labs/2-1-superset-chart.md | 18 ++++++++--------- docs/labs/3-1-sessions-by-hand.md | 2 +- docs/labs/4-1-session-pipeline.md | 2 +- docs/labs/5-1-order-checks.md | 4 ++-- docs/labs/5-2-order-parsing.md | 2 +- docs/labs/5-3-order-pipeline.md | 3 +-- docs/labs/6-1-event-parsing.md | 29 ++++++++++++++++----------- docs/labs/7-1-monitoring.md | 2 +- docs/labs/README.md | 2 +- docs/specs/2026-09-01-labs-program.md | 9 +++++---- 15 files changed, 51 insertions(+), 46 deletions(-) diff --git a/docs/labs/1-1-events-enriched.md b/docs/labs/1-1-events-enriched.md index 0a72405..18625a5 100644 --- a/docs/labs/1-1-events-enriched.md +++ b/docs/labs/1-1-events-enriched.md @@ -93,5 +93,5 @@ DDS читают из ODS без отдельной загрузки. Эти с - [ ] у вас есть `dm.events_enriched_v` с явно перечисленными колонками; - [ ] вы сверили число строк и набор `event_id` с `dds.event_v` за каждый прожитый день; расхождений нет; -- [ ] вы запустили `events_enriched` в `homework_check`, проверка успешна; +- [ ] проверка `events_enriched` в `homework_check` прошла успешно; - [ ] витрина работает без физической таблицы и задачи в `dm_transform`. diff --git a/docs/labs/1-2-top-pages-daily.md b/docs/labs/1-2-top-pages-daily.md index b936435..bd1741c 100644 --- a/docs/labs/1-2-top-pages-daily.md +++ b/docs/labs/1-2-top-pages-daily.md @@ -102,9 +102,9 @@ - [ ] у вас есть пять объектов витрины; донор объявлен через `AS` и использует тот же ключ шардирования, что и целевая таблица; -- [ ] вы явно перечислили колонки в `dm.top_pages_daily_v`, включая +- [ ] в `dm.top_pages_daily_v` явно перечислены колонки, включая `_load_id` и `_load_ts`; - [ ] ваш запрос сам определяет дни пересборки по состоянию витрины; -- [ ] вы запустили `top_pages_daily` в `homework_check`, проверка успешна; +- [ ] проверка `top_pages_daily` в `homework_check` прошла успешно; - [ ] вы включили витрину в `dm_transform` и убедились, что повторный прогон конвейера не меняет результат. diff --git a/docs/labs/1-3-session-overview.md b/docs/labs/1-3-session-overview.md index c3ee54f..3e71140 100644 --- a/docs/labs/1-3-session-overview.md +++ b/docs/labs/1-3-session-overview.md @@ -111,5 +111,5 @@ - [ ] вы явно перечислили колонки в `dm.session_overview_v` и собрали показатели из готовых сессий `dds.session_v`; - [ ] в таблице хранятся слагаемые для средних и долей; -- [ ] вы запустили `session_overview` в `homework_check`, проверка успешна; -- [ ] вы включили сборку витрины в `dm_transform`. +- [ ] проверка `session_overview` в `homework_check` прошла успешно; +- [ ] витрина обновляется при запуске `dm_transform`. diff --git a/docs/labs/1-4-dq-errors-daily.md b/docs/labs/1-4-dq-errors-daily.md index ab5c35e..a9506f5 100644 --- a/docs/labs/1-4-dq-errors-daily.md +++ b/docs/labs/1-4-dq-errors-daily.md @@ -103,9 +103,9 @@ - [ ] у вас есть пять объектов витрины; донор объявлен через `AS` и использует ключ шардирования целевой таблицы; -- [ ] вы собираете сводку из таблиц `_dist` для ошибок событий и заказов; +- [ ] сводка включает ошибки событий и заказов из таблиц `_dist`; - [ ] ваш запрос пересобирает только дни, еще доступные в источниках, сохраняя более старые итоги; -- [ ] вы запустили `dq_errors_daily` в `homework_check`, проверка успешна; +- [ ] проверка `dq_errors_daily` в `homework_check` прошла успешно; отсутствие брака тоже дает успешный результат; -- [ ] вы включили сборку витрины в `dm_transform`. +- [ ] витрина обновляется при запуске `dm_transform`. diff --git a/docs/labs/1-5-utm-effectiveness.md b/docs/labs/1-5-utm-effectiveness.md index 4eeb157..e2735ab 100644 --- a/docs/labs/1-5-utm-effectiveness.md +++ b/docs/labs/1-5-utm-effectiveness.md @@ -130,8 +130,8 @@ - [ ] у вас есть пять объектов витрины; донор объявлен через `AS` и использует ключ шардирования целевой таблицы; -- [ ] вы сохранили строки без UTM-меток и используете `Decimal(18, 2)` - для `declared_revenue`; +- [ ] строки без UTM-меток сохранены, а `declared_revenue` хранится + в `Decimal(18, 2)`; - [ ] вы записали, почему сумма отличается от `dm.revenue_daily_v`; -- [ ] вы запустили `utm_effectiveness` в `homework_check`, проверка успешна; -- [ ] вы включили сборку витрины в `dm_transform`. +- [ ] проверка `utm_effectiveness` в `homework_check` прошла успешно; +- [ ] витрина обновляется при запуске `dm_transform`. diff --git a/docs/labs/2-1-superset-chart.md b/docs/labs/2-1-superset-chart.md index 22da07d..fcb68eb 100644 --- a/docs/labs/2-1-superset-chart.md +++ b/docs/labs/2-1-superset-chart.md @@ -45,10 +45,10 @@ укажите «Категория устройства», а «Dimensions» не заполняйте. Дашборд создавать не нужно: сохраненного графика достаточно. -По умолчанию в графике выбран период «No filter». Оставьте его таким или -задайте явные даты. Относительный период, например «Last week», даст пустой -график: модельный календарь стенда отстает от настоящего. Последнюю дату -витрины посмотрите в SQL Lab: +Для этого задания оставьте период «No filter»: график и проверочный запрос +ниже считают долю отказов за весь период витрины. Относительный период, +например «Last week», может дать пустой график: модельный календарь стенда +отстает от настоящего. Последнюю дату витрины посмотрите в SQL Lab: ```sql SELECT max(report_date) @@ -80,12 +80,12 @@ ORDER BY device_category; - [ ] у вас есть физический датасет `dm.session_overview_v` в подключении `ClickHouse`; -- [ ] вы сохранили в нем метрику «Доля отказов» с выражением +- [ ] в датасете есть метрика «Доля отказов» с выражением `SUM(bounce_sessions) / SUM(sessions)` и форматом `.1%`; -- [ ] вы назвали `device_category` и `report_date` по-русски: «Категория +- [ ] колонки `device_category` и `report_date` подписаны «Категория устройства» и «Дата отчета»; -- [ ] у вас сохранен столбиковый график доли отказов по категориям устройств без - относительного фильтра по времени; -- [ ] вы сверили числа на графике с запросом из SQL Lab; +- [ ] сохраненный столбиковый график показывает долю отказов по категориям + устройств за весь период витрины; +- [ ] числа на графике совпадают с результатом запроса из SQL Lab; - [ ] вы сохранили график отдельно, без создания дашборда и изменения стендовых объектов. diff --git a/docs/labs/3-1-sessions-by-hand.md b/docs/labs/3-1-sessions-by-hand.md index 58ca3b8..937b00c 100644 --- a/docs/labs/3-1-sessions-by-hand.md +++ b/docs/labs/3-1-sessions-by-hand.md @@ -89,4 +89,4 @@ ORDER BY EventDate, ClientID; `FULL OUTER JOIN`; - [ ] вы объяснили причину одного расхождения и записали куку, день и два события, на которых оно видно; -- [ ] вы уточнили расчет и получили ноль расхождений во всех днях. +- [ ] уточненный расчет дает ноль расхождений во всех днях. diff --git a/docs/labs/4-1-session-pipeline.md b/docs/labs/4-1-session-pipeline.md index 7671f2c..cb16984 100644 --- a/docs/labs/4-1-session-pipeline.md +++ b/docs/labs/4-1-session-pipeline.md @@ -188,4 +188,4 @@ Distributed-таблицу и такую ошибку не заметит. - [ ] вы получили ноль расхождений в `sessions_vs_reference` за все прожитые дни и вручную сверили границы и атрибуты сессии одной куки; - [ ] ваш запрос с `hostName()` не нашел строк на чужом шарде; -- [ ] вы повторили прогон без удаления данных: он пропустил группу сессий. +- [ ] повторный прогон без удаления данных пропустил группу сессий. diff --git a/docs/labs/5-1-order-checks.md b/docs/labs/5-1-order-checks.md index 48f0c9b..3906d56 100644 --- a/docs/labs/5-1-order-checks.md +++ b/docs/labs/5-1-order-checks.md @@ -1,4 +1,4 @@ -# Проверьте заказы перед изменением обработки +# Научите проверки замечать ошибки в заказах **Чему учит:** написать проверки заказов до изменения их обработки и убедиться на намеренной ошибке, что проверки ее замечают. @@ -139,7 +139,7 @@ ORDER BY check_name; ## Что должно получиться -- [ ] вы включили две новые проверки в `dq_check`; их дневные результаты +- [ ] две новые проверки запускаются в `dq_check`, а их дневные результаты появляются в `dm.dq_summary`; - [ ] вы увидели ошибку каждой проверки после соответствующего изменения заказа, а в журнале нашли примеры заказов с расхождениями; diff --git a/docs/labs/5-2-order-parsing.md b/docs/labs/5-2-order-parsing.md index fb20274..dc61cf8 100644 --- a/docs/labs/5-2-order-parsing.md +++ b/docs/labs/5-2-order-parsing.md @@ -176,5 +176,5 @@ SETTINGS mutations_sync = 2; - [ ] ваш разбор дал тот же результат на новой пробной пачке; - [ ] вы проверили разные значения `created_at` и `updated_at` у годной строки в обоих опытах и подтвердили правильный шард через `hostName()`; -- [ ] вы удалили строки обоих опытов по их маркерам; +- [ ] строки обоих опытов удалены по их маркерам; - [ ] после очистки конвейер и все семь проверок успешны. diff --git a/docs/labs/5-3-order-pipeline.md b/docs/labs/5-3-order-pipeline.md index e45f340..a5d531d 100644 --- a/docs/labs/5-3-order-pipeline.md +++ b/docs/labs/5-3-order-pipeline.md @@ -120,5 +120,4 @@ DROP PARTITION 'YYYY-MM-DD'; с сохраненными значениями; они совпали; - [ ] `order_items_total`, `orders_vs_ods` и пять прежних проверок успешны; - [ ] ваш запрос с `hostName()` не нашел заказов на чужом шарде; -- [ ] вы повторили `etl_pipeline`: деловые данные остались прежними, - проверки прошли. +- [ ] повторный `etl_pipeline` сохранил деловые данные, проверки прошли. diff --git a/docs/labs/6-1-event-parsing.md b/docs/labs/6-1-event-parsing.md index 0fb0027..8c09c92 100644 --- a/docs/labs/6-1-event-parsing.md +++ b/docs/labs/6-1-event-parsing.md @@ -116,6 +116,8 @@ Airflow. При включенном `world_live` посмотрите его т ## Приведите стороны к одному зерну +### Учтите повторные доставки + В STG одна строка представляет доставку. Повтор сообщения остается второй строкой. В `ods.event_v` одна строка представляет актуальное событие, потому что представление читает физическую таблицу с `FINAL`. @@ -124,17 +126,20 @@ Airflow. При включенном `world_live` посмотрите его т полных ключей среди годных доставок. Разница между числом доставок и этим счетом показывает повторы в исходном срезе. -Выберите строки ODS для сравнения независимо от своего условия отбора и -результата вставки. Снова возьмите весь исходный STG-срез. По координатам Kafka -исключите доставки, которые есть в `ods.event_errors_dist`. У оставшихся -доставок извлеките `WatchID` без риска прервать запрос и выберите эти события из свежего -`ods.event_v`. +### Выберите события ODS для сравнения -Свяжите сырую доставку с ODS по `WatchID`: по договору генератора это уникальный -идентификатор события. Здесь он нужен только для выбора событий ODS. -Удаление повторов и последующее соединение используют полный физический ключ. -Не берите список событий из своей таблицы и не применяйте к нему свое условие -отбора. Иначе ошибочно отвергнутая строка исчезнет с обеих сторон сверки. +Теперь нужно найти в ODS события из выбранного среза. Список стройте по +исходным доставкам: если взять его из своей таблицы, ошибочно отвергнутое +событие исчезнет с обеих сторон сверки и потеря останется незамеченной. + +Снова возьмите весь исходный STG-срез. По координатам Kafka исключите +доставки, которые есть в `ods.event_errors_dist`. У оставшихся извлеките +`WatchID` без риска прервать запрос и выберите эти события из свежего +`ods.event_v`. Свое условие отбора здесь не применяйте: решение о годности +доставки вы берете из результата стендового разбора. + +`WatchID` связывает доставку с событием в ODS. Для удаления повторов и +сравнения полей дальше используйте полный физический ключ из схемы таблицы. ## Сверьте 47 полей @@ -198,8 +203,8 @@ Kafka после опыта не нужно. - [ ] у вас сохранены границы выбранного среза, число строк и координаты доставки по партициям Kafka; -- [ ] вы собрали свою таблицу со всеми 47 полями и `_load_ts`, используя - те же правила удаления повторов, что и ODS; +- [ ] ваша таблица содержит все 47 полей и `_load_ts` и обрабатывает + повторы по тем же правилам, что и ODS; - [ ] ваш разбор принимает только JSON-объекты с точным набором ключей и пятью опорными полями, которые преобразуются в нужные типы; - [ ] вы сверили число исходных строк с суммой годных и отвергнутых, diff --git a/docs/labs/7-1-monitoring.md b/docs/labs/7-1-monitoring.md index 4c0ad11..4929a47 100644 --- a/docs/labs/7-1-monitoring.md +++ b/docs/labs/7-1-monitoring.md @@ -101,7 +101,7 @@ ## Что должно получиться -- [ ] вы записали по одному вопросу для каждого из трех дашбордов; +- [ ] для каждого из трех дашбордов записан вопрос, на который он отвечает; - [ ] у вас сохранены окно, текст и показатели верхнего запроса; вы выяснили, кто его отправил и зачем; - [ ] вы нашли запрос открытых дашбордов, который прочитал больше всего diff --git a/docs/labs/README.md b/docs/labs/README.md index 05c69e2..cf593f1 100644 --- a/docs/labs/README.md +++ b/docs/labs/README.md @@ -12,7 +12,7 @@ | 2 | [Свой график в Superset](2-1-superset-chart.md) | превратить свой результат из хранилища в отчет в BI | | 3 | [Нарежьте сессии сами](3-1-sessions-by-hand.md) | сверить число сессий в своей нарезке с эталоном и найти события, из-за которых оно расходится | | 4 | [Перепишите нарезку стенда](4-1-session-pipeline.md) | переписать расчет сессий в готовом конвейере и проверить по исходным событиям | -| 5 | [Проверьте заказы перед изменением обработки](5-1-order-checks.md)
[Перепишите разбор заказов в ODS](5-2-order-parsing.md)
[Перепишите сборку заказов в DDS](5-3-order-pipeline.md) | написать проверки качества до рискованного изменения, затем переписать разбор и сборку заказов | +| 5 | [Научите проверки замечать ошибки в заказах](5-1-order-checks.md)
[Перепишите разбор заказов в ODS](5-2-order-parsing.md)
[Перепишите сборку заказов в DDS](5-3-order-pipeline.md) | написать проверки качества до рискованного изменения, затем переписать разбор и сборку заказов | | 6 | [Разберите сырые события рядом со стендом](6-1-event-parsing.md) | изучить разбор потока отдельно от живого приема и сверить его со стендовым результатом | | 7 | [Мониторинг: куда смотреть](7-1-monitoring.md) | выбрать, куда и на что смотреть в живой системе | diff --git a/docs/specs/2026-09-01-labs-program.md b/docs/specs/2026-09-01-labs-program.md index bec0091..68bc651 100644 --- a/docs/specs/2026-09-01-labs-program.md +++ b/docs/specs/2026-09-01-labs-program.md @@ -51,9 +51,10 @@ с чем сравнить и где искать причину расхождения. Если проверка и есть ход исследования, сохраните ее в шагах, без повторного раздела. В конце задания «Что должно получиться» помогает учащемуся оценить результат: что у него -работает, что он проверил и что может объяснить. Чекбоксы обращены к нему: -«вы сверили числа», «у вас сохранен запрос». Подробный порядок действий -остается в основной части. +работает, что он проверил и что может объяснить. Пишите о наблюдаемом +результате: «числа на графике совпадают с запросом», «вы можете объяснить +причину ошибки». Местоимение «вы» нужно там, где оно помогает обратиться +к читателю. Подробный порядок действий остается в основной части. Заголовки остальных разделов выбирайте по содержанию: «Для чего нужна витрина», «Найдите причину», «Как даг запускает ваши запросы». Число разделов @@ -115,7 +116,7 @@ Форма — вычитание. Обратную связь дают `dq_check` и поштучная сверка, построенная на третьей ступени. -### Ступень 5. Проверьте заказы перед изменением обработки +### Ступень 5. Научите проверки замечать ошибки в заказах Сначала менти строит две недостающие DQ-проверки. Первая утверждает равенство «цена × количество = items_total». Вторая сверяет слой с источником по дням. Их From 1b95ce456ca65a4b0469dc771c271701b8cd8e1e Mon Sep 17 00:00:00 2001 From: Dmitry Dementev Date: Sun, 13 Sep 2026 11:26:55 -0400 Subject: [PATCH 3/3] =?UTF-8?q?docs(labs):=20=D1=81=D0=BE=D0=BA=D1=80?= =?UTF-8?q?=D0=B0=D1=89=D0=B5=D0=BD=D1=8B=20=D0=BF=D1=80=D0=B0=D0=B2=D0=B8?= =?UTF-8?q?=D0=BB=D0=B0=20=D0=BD=D0=B0=D0=BF=D0=B8=D1=81=D0=B0=D0=BD=D0=B8?= =?UTF-8?q?=D1=8F=20=D0=B7=D0=B0=D0=B4=D0=B0=D0=BD=D0=B8=D0=B9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Зачем: оставить только необходимые ориентиры для автора учебного текста. Что: раздел сокращен до трех согласованных абзацев, ссылки на оба образца сохранены. Проверка: git diff --check прошел; проверен diff единственного измененного файла. --- docs/specs/2026-09-01-labs-program.md | 32 +++++++-------------------- 1 file changed, 8 insertions(+), 24 deletions(-) diff --git a/docs/specs/2026-09-01-labs-program.md b/docs/specs/2026-09-01-labs-program.md index 68bc651..87f2627 100644 --- a/docs/specs/2026-09-01-labs-program.md +++ b/docs/specs/2026-09-01-labs-program.md @@ -42,32 +42,16 @@ ### Как писать задания -Задание обращается к учащемуся на «вы». Начните с «Чему учит»: назовите -умение, которое он получит. Затем объясните, для чего нужен результат, и -ведите читателя по действиям. Термины раскрывайте там, где без них нельзя -понять следующий шаг. Историю разработки оставляйте в рабочих справочниках. +Пишите для учащегося: объясняйте, чему он научится, зачем нужны действия +и как понять результат. Историю разработки оставляйте за пределами задания. -Раздел «Как проверить себя» описывает способ проверки: что выполнить, -с чем сравнить и где искать причину расхождения. Если проверка и есть ход -исследования, сохраните ее в шагах, без повторного раздела. В конце задания -«Что должно получиться» помогает учащемуся оценить результат: что у него -работает, что он проверил и что может объяснить. Пишите о наблюдаемом -результате: «числа на графике совпадают с запросом», «вы можете объяснить -причину ошибки». Местоимение «вы» нужно там, где оно помогает обратиться -к читателю. Подробный порядок действий остается в основной части. +«Как проверить себя» описывает действия для проверки и поиска ошибок. +«Что должно получиться» — результат работы и то, что учащийся теперь может +объяснить. Если проверка уже встроена в шаги упражнения, повторять ее +отдельным разделом не нужно. -Заголовки остальных разделов выбирайте по содержанию: «Для чего нужна -витрина», «Найдите причину», «Как даг запускает ваши запросы». Число разделов -и пунктов определяется упражнением. Ограничения объясняйте рядом с действием, -которого они касаются; завершение опыта включает восстановление данных или -удаление временных объектов, когда это требуется. - -При вычитке сохраняйте состав результата, технические условия, способы -проверки и самостоятельную часть работы. Новое упражнение или готовое решение -под видом пояснения меняет учебный смысл и требует отдельного обсуждения. -Формат инженерных issues из правил трекера относится к разработке стенда; -учебные задания пишутся по этим правилам. Образцы: -[дашборд Superset](../labs/optional/superset-dashboard.md) и +Остальные заголовки и структуру выбирайте по содержанию. Образцы — задания +про [дашборд Superset](../labs/optional/superset-dashboard.md) и [расследование подключения](../labs/optional/superset-missing-object.md). ## 3. Основная программа — семь ступеней