# Программа учебных лаб Эта спека собирает программу лаб из [инвентаризации сюжетов](../research/2026-08-31-labs-inventory.md), выполненной в тикете #201. Тикет спеки — #202, карта — #182. Статус: спека принята владельцем; судьба идей-кандидатов решена здесь поименно. ## 1. Кому и зачем Менти уже прошел большой теоретический курс ClickHouse объемом около 60 часов. Он знает устройство системы в теории, но почти ничего не делал своими руками. Поэтому в программе нет вводного блока с теорией. Здесь теория становится ремеслом: менти руками осваивает слои хранилища, DQ, оркестрацию и идемпотентность. Обещание программы: менти проходит конвейер стенда автором, а не читателем. ## 2. Устройство программы: основная программа и факультативы У частей программы два статуса, третьего нет. **Основная программа** — обязательная часть. Менти проходит ее целиком и по порядку, потому что каждая следующая ступень опирается на предыдущую. От ступени к ступени растет самостоятельность: сначала менти получает готовые ТЗ с готовыми сторожами (DQ-проверками), а затем строит сторожей сам. **Факультативы** — необязательные лабы. Они собраны в тематические разделы, чтобы менти мог одним взглядом выбрать интересное. Для каждого раздела указано, после какой ступени основной программы он открывается. В программе используются четыре формы заданий: - *построить свое по ТЗ* — рядом есть образец, но готового ответа нет; - *вычитание* — SQL готовой трансформации удаляется и пишется заново, а даги остаются как несущая инфраструктура; - *собери рядом* — менти строит свою копию результата рядом с нетронутым стендовым и сверяет их построчно; - *расследование* — данные уже лежат на стенде или стенд намеренно ломается, а менти ищет причину и объясняет ее. ### Как писать задания Пишите для учащегося: объясняйте, чему он научится, зачем нужны действия и как понять результат. Историю разработки оставляйте за пределами задания. Начинайте задание коротким описанием предстоящей работы. Заголовки должны называть содержание разделов: «Подготовка», «Создание витрины», «Проверка результата». Шаблонные вопросы «Чему учит» и «Что сделать» не нужны. Раздел проверки описывает действия для проверки и поиска ошибок. Раздел результата перечисляет итог работы и то, что учащийся теперь может объяснить. Если проверка уже встроена в шаги упражнения, повторять ее отдельным разделом не нужно. Структуру выбирайте по содержанию. Образцы — задания про [дашборд Superset](../labs/optional/superset-dashboard.md) и [расследование подключения](../labs/optional/superset-missing-object.md). ## 3. Основная программа — семь ступеней ### Ступень 1. Построй пять витрин слоя DM Первая часть программы опирается на шесть существующих заданий из [`docs/labs/`](../labs/README.md). Пять ТЗ по витринам входят в первую ступень основной программы и выполняются как есть. Их точечный пересмотр — работа реализации. Шестое задание, про свой дашборд в Superset, остается факультативом в разделе «Superset и BI». Набор уроков пяти витрин уже записан в их общем описании: от слоя как договора до честных имен показателей. Форма — построить свое по ТЗ. Обратную связь дает даг `homework_check`. ### Ступень 2. Свой отчет в Superset Менти строит один график в Superset поверх витрины, которую сделал на первой ступени. Урок — увидеть, как собственный результат из хранилища становится отчетом в BI, не превращая знакомство с инструментом в отдельный большой проект. Форма — построить свое по ТЗ. Результат проверяется глазами. ТЗ для этой новой ступени предстоит написать при реализации программы. Полный собственный дашборд остается факультативом в разделе «Superset и BI». ### Ступень 3. Нарежьте сессии сами До того как открыть стендовый SQL нарезки сессий в DDS, менти самостоятельно делит события на визиты. Сначала он сравнивает число своих сессий с эталоном `VisitID`. Затем вручную соединяет свою сессию с эталонной и поштучно разбирает хотя бы одно расхождение границ. Урок — счет называет проблему, но причину приходится искать в отдельных строках. Форма — построить свое по ТЗ и провести расследование. Обратная связь состоит из счетной сверки с `VisitID` и ручного сопоставления границ. ### Ступень 4. Перепишите нарезку стенда Менти удаляет и заново пишет SQL нарезки сессий DDS: расчет объема, пересборку и замену. Урок — изменить расчетную сущность внутри готового конвейера и проверить ее внешним эталоном. Это единственный поток, для которого инвентаризация подтвердила вычитание без оговорок: здесь есть настоящие места для ошибок, а проверка не содержит готового ответа. Форма — вычитание. Обратную связь дают `dq_check` и поштучная сверка, построенная на третьей ступени. ### Ступень 5. Научите проверки замечать ошибки в заказах Сначала менти строит две недостающие DQ-проверки. Первая утверждает равенство «цена × количество = items_total». Вторая сверяет слой с источником по дням. Их менти строит по образцу пяти стендовых проверок в `sql/dq/`. Обе проверки должны покраснеть на управляемой ошибке. Затем менти вручную подкладывает по строке каждого класса брака, проверяет разбор и убирает эти строки за собой. Только после этого он вычитает разбор заказов ODS `order_parse` и трансформацию заказа DDS. Урок — сторожа строят до рискованного изменения, а не после первой поломки. Форма — построить свои проверки по ТЗ, провести управляемое расследование и выполнить вычитание. Обратную связь дают две новые проверки, баланс годных и ошибочных строк по срезу и существующие сверки заказов. ### Ступень 6. Разбери сырые события и сверь результат со стендом Менти пакетно разбирает срез событий из STG в собственную таблицу и построчно сверяет результат с нетронутым стендовым ODS. Урок — изучить разбор потока отдельно от живого приема и использовать стендовый результат как образец для сверки. Матвью разбора остаются несущей стеной: их пересоздание молча теряет сообщения. Форма — «собери рядом». Обратная связь — построчная сверка двух разборов. Ее границы называются прямо: срок работы ограничен окном хранения сырых данных, а содержимое неопорных колонок можно сравнить только со стендовым разбором, не с независимым эталоном. ### Ступень 7. Мониторинг: куда смотреть Сначала менти знакомится с панелями стенда. Затем проводит два безопасных расследования. В первом он находит в `query_log`, что верх списка тяжелых запросов занимает сам мониторинг. Во втором объясняет, почему свежесть второго источника выглядит как пила, а не как линия, и выбирает правильный слой данных для панели. Форма — расследование. Обратная связь — найденные запросы и верное объяснение формы графика и выбора слоя. Ломательные опыты остаются в факультативе «Что делать, когда стенд сломался». Урок ступени — куда и на что смотреть в живой системе. **Общее правило вычитания:** ТЗ каждого вычитания на ступенях 4 и 5 включает пересчет ключа шардирования по `hostName()`. Счетные сверки читают `_dist`, поэтому учитывают строку даже на чужом шарде и остаются зелеными: они не видят саму ошибку раскладки. ## 4. Факультативы — пять разделов Ниже перечислены сюжеты, их уроки и принятые решения по форме заданий. Исходный материал и способы проверки — в [инвентаризации](../research/2026-08-31-labs-inventory.md). ### Почему выручка не сходится Этот раздел рекомендован первым из факультативов и открывается после ступени 1 основной программы. Шесть сюжетов собраны в четыре лабораторные: три расследования готовых данных и одно наблюдение во времени. Опоздания, изменение выручки и история отмен используют одну историю заказов, поэтому для них выбрана общая работа вместо трех отдельных. Результат расследования — запрос сверки и инженерное решение, подтвержденное данными: выбор источника, ключа, полей для точного расчета или правила пересчета и хранения истории. Объяснение суммы служит проверкой решения. Вариант закончить работы только объяснением показателей отклонен: он смещал акцент в анализ отчетов. Постройка новых DAG и подключение проверок к конвейеру оставлены основной программе, чтобы факультативы не повторяли ее задания. - **[Куда пропали покупки](../labs/optional/missing-purchases.md) (#186).** Сверка по номерам заказов обосновывает источник витрины и условие окончательной проверки полноты. Счетчики событий помогают обнаружить расхождение, но не устанавливают место потери. - **[Почему повтор покупки завышает выручку](../labs/optional/duplicate-purchases.md) (#187).** Учащийся собирает расчет с одной строкой на заказ, проверяет ключ и полноту набора номеров, отличает его зерно от зерна исходных событий. - **[Какая позиция объясняет разницу в сумме](../labs/optional/order-amount-difference.md).** Сверка позиций обосновывает выбор исходных полей и типа денег, отделяет ошибку собственного разбора от различия данных события и заказа. - **[Почему вчерашняя выручка меняется сегодня](../labs/optional/revenue-over-time.md) (#188).** Наблюдение опозданий и смены статусов обосновывает правило пересчета при ежедневной загрузке без пропусков. Учащийся определяет момент закрытия дня и требования к истории для воспроизведения расчета и проверки факта оплаты. В четвертой лабораторной выбрано собственное наблюдение: учащийся продвигает мир по одному дню через Airflow и сохраняет результаты после обновления витрин в две локальные таблицы ClickHouse под `student`: состояния заказов и итоги наблюдений. Сравнение выполняется SQL-запросами в том же подключении DBeaver. Учащийся может писать только в `default`; источники доступны ему на чтение. Отдельная строка итогов фиксирует и наблюдение без заказов. Экспорт в CSV отклонен как основной способ: он требовал неописанного перехода к другому инструменту или обратному импорту файлов. Готовые слепки и сочетание готового примера с собственным наблюдением отклонены: нужную историю можно получить на стенде и самому увидеть, как новые данные меняют уже рассчитанный отчет. День считается закрытым после получения и обработки последнего слепка семидневного окна изменяемости; этот слепок доставляется следующим модельным днем. Одного совпадения сумм в соседних наблюдениях для такого вывода недостаточно. История отмен тоже имеет границу: оплата и отмена между двумя дневными слепками могут остаться невидимыми. Отсутствие наблюдаемой оплаты не доказывает, что оплаты не было. ### Тонкий анализ данных Открывается после ступени 1 основной программы. - **[Как новые заказы меняют отчет о посетителях](../labs/optional/identity-over-time.md) (#145).** Учащийся собирает связи с подтверждающими покупками и сохраняет три ежедневных наблюдения. На одном наборе прежних сессий сравнивает расчет с прежними и новыми связями, выбирает историю для воспроизведения расчета. - **Добавь названия регионов в отчет (#143).** Менти собирает свой словарь по образцу каталога и обогащает отчет через `dictGet`. Работа о связях продолжается в инженерном разделе лабораторной про инкрементальный `daily_traffic`. В обеих учащийся продвигает мир через `world_next_day` и `etl_pipeline`, сохраняет наблюдения под `student` в свои локальные таблицы `default` и сравнивает их SQL-запросами. Время первой подтверждающей покупки и время обнаружения связи разделены. Минимум времени `purchase` не принят за границу появления новых связей: заказ может поступить позже события. Нужные изменения определяются по разности сохраненных наборов пар. Ежедневное наблюдение ограничивает момент появления связи интервалом; для пар начального наблюдения более ранняя история неизвестна. История сессий в этом опыте растет без исправлений прошлых дней, а связи не отзываются — поэтому сохраненных наборов связей достаточно для сравнения расчетов на прежних сессиях. ### Superset и BI Открывается после ступени 2 основной программы. - **[Свой дашборд](../labs/optional/superset-dashboard.md).** Менти использует результат ступени 2, собирает отчет из пяти своих витрин и подтверждает выводы запросами. Пользовательские объекты остаются отдельно от стендовых. - **[В DBeaver работает, в Superset нет](../labs/optional/superset-missing-object.md).** Менти создает отдельное представление на первой ноде и расследует ошибку чтения из Superset, подключенного ко второй. Опыт заканчивается объяснением, исправлением DDL и удалением учебного объекта. Задания самостоятельны: расследование использует готовую витрину стенда. Отсутствие объекта проверяется в SQL Lab, поскольку создание физического датасета может остановиться раньше, чем появится график. Чтение только одного шарда остается материалом инженерного раздела: это отдельная причина неполного результата, которую добавление `ON CLUSTER` само по себе не устраняет. Технический опыт проверен 2026-09-13 на ClickHouse 26.3.17.56: локальное представление недоступно пользователю `bi` на второй ноде; после создания на кластере обе ноды возвращают тот же счет, что исходная витрина. ### Инженерия стенда Открывается после ступени 1 основной программы. Сюжеты про переигрывание дня и пошардовую атомарность раскроются полнее после ступени 4, когда менти уже спускался в DDS. - **Как принимать данные из Kafka: потоком или слепком.** Поток и слепок требуют разных способов приема и дают разную наблюдаемость. - **Почему данные попадают на свои шарды независимо от ноды чтения.** Нода-потребитель может меняться, а раскладку строк определяет ключ шардирования. - **Почему вторая группа потребителей дублирует поток.** Еще одна группа потребителей создает еще одну полную копию потока. - **Как заметить пропущенные сообщения по офсетам.** Разрыв в офсетах показывает потерю, которую трудно заметить по самим данным. - **Почему сырые данные удаляются по времени загрузки.** Очистка сырых данных идёт по времени загрузки, а не по модельному дню. - **Куда попадают битые строки.** Годные строки и брак делят поток без пропусков и пересечений; менти подкладывает по строке каждого класса брака событий и находит их в `ods.event_errors_dist`. - **Какая версия заказа считается текущей.** Версию заказа задает слепок источника, а `FINAL` скрывается за представлением. - **Почему один момент попадает в разные дни.** Один момент дает разные дни в разных поясах, поэтому день и пояс нужно называть явно. - **Проверь, как стенд разобрал JSON в `ecommerce`.** Типизированный разбор может молча подменить ошибку значением, поэтому менти сам разбирает сырой JSON и сравнивает результат с массивами стенда. - **Как конвейер восстанавливает пропущенный день.** Конвейер сам находит дыру и определяет объем повторной обработки по состоянию хранилища. - **Что будет, если день заменился не на всех шардах.** Замена дня завершается отдельно на каждом шарде, поэтому целостность результата подтверждает отдельная сверка. - **[Как обновлять витрину без полной пересборки](../labs/optional/incremental-daily-traffic.md) (#149) — повышенная сложность.** Продолжение работы о связях пользователей: учащийся строит свою витрину, выбирает затронутые дни по новым сессиям и связям, сверяет весь результат с `dm.daily_traffic_v`. Журнал успешных загрузок позволяет повторить опыт с частично записанным результатом. Инкрементальная работа начинается с полной загрузки текущего состояния, затем охватывает два новых модельных дня. Обновление собственной витрины выполнено вставкой версий в `ReplacingMergeTree`, чтение — с `FINAL`. Это оставляет права `student` прежними и не требует менять конвейер стенда. Версия — дата наблюдения; ключ — день отчета. История связей хранится отдельно от версий витрины. Выбор опирается на [договор движка](https://clickhouse.com/docs/engines/table-engines/mergetree-family/replacingmergetree): максимальная версия по ключу и удаление прежних версий при слияниях. DDL, обновление вставкой и чтение с `FINAL` проверены под `student` на ClickHouse 26.3.17.56. Полный пересчет остается эталоном, а экономия оценивается числом выбранных дней. На малом объеме ускорение не обещано. Поиск связей продолжает читать историю источников целиком; инкрементальным становится расчет дневной витрины. Полная замена стендового ETL отклонена: для учебного сравнения достаточно своей витрины и журнала рядом с работающим конвейером. ### Что делать, когда стенд сломался Открывается после ступени 7 основной программы; весь раздел — повышенная сложность. - **Что ломается вместе с keeper.** Остановка keeper связывает состояние DDL и репликации с тем, что видно на панелях и в системных таблицах. - **Где искать запрос DDL, который не завершился.** Очередь распределенного DDL показывает путь запроса и ответ каждой ноды. Итог раздела — runbook, которого сейчас не хватает стенду. Менти пишет его по результатам опытов, и документ остается в проекте. ## 5. Границы вычитания по потокам Подробное сравнение потоков и таблица доказательств остаются в [инвентаризации](../research/2026-08-31-labs-inventory.md). Спека берет это сравнение за основу и проводит свои границы. Без оговорок вычитанию отдаются только сессии DDS. Заказы можно вычитать лишь после самодельных сторожей. Для событий ODS выбрана форма «собери рядом»; вычитание здесь отклонено. Прием STG и забор заказов остаются несущей стеной и доступны только для расследований. Карту идентичностей спека решает не отдавать вычитанию; причины этого выбора названы в следующем разделе. ## 6. Что не вошло Вычитание стендовых витрин DM не входит в программу. Оно повторяет урок готового ядра DM, а открытые сверочные запросы в `sql/dq/` почти дословно содержат ответ. Форма «собери рядом» для карты идентичностей тоже не входит. Независимого эталона нет, а слабые инварианты проверяют допустимость связей, но не полноту. Аналитический урок идентичности уже несет склейка во времени (#145) в факультативе «Тонкий анализ данных». Сюжеты, которые инвентаризация относит к лекциям, остаются лекциями: `Sign` и CollapsingMergeTree, топологии в бою и идентичность «как в бою». Эта спека оставляет их в [списке опорных точек раздела 12 спеки v2](2026-07-30-stand-v2-realism.md#опорные-точки-для-будущих-лекций-и-лаб). ## 7. Судьба идей-кандидатов Эти решения закрывают тикеты после приемки спеки: - #143, справочник регионов — вошла в факультатив «Тонкий анализ данных». - #145, склейка во времени — вошла в факультатив «Тонкий анализ данных». - #148, домашки ниже DM и вычитание — вошла в основную программу как ступени 4–6. Форму для каждого потока уточнила инвентаризация #201: вычитание, «сначала сторож» или «собери рядом». - #149, инкрементальный `daily_traffic` — вошла в факультатив «Инженерия стенда» с пометкой «повышенная сложность». - #186, воронка подтверждений — вошла в факультатив «Почему выручка не сходится». - #187, бизнес-дубль — вошла в факультатив «Почему выручка не сходится». - #188, опоздавшие заказы — вошла в факультатив «Почему выручка не сходится». ## 8. Отклоненные варианты оси - **Чистое «снизу вверх»: STG → ODS → DDS → DM.** Оно начинает программу с самого тяжелого по инфраструктуре участка. Внизу быстро кончается эталон обратной связи, а вычитание вслепую заставляет переписывать слой, которым менти еще не пользовался. - **Два прохода: сначала расследования снизу вверх, потом постройка сверху вниз.** Пролог из восьми расследований откладывает первый собственный результат. Вместо этого расследования разнесены по факультативам и открываются по мере прохождения основной программы. - **Сюжетами поперек слоев.** Такой порядок ломает принятое решение, по которому готовые задания открывают программу, и превращает путь в сетку зависимостей. ## 9. Границы спеки и что дальше Спека не переписывает существующие ТЗ в `docs/labs/` и общий README; точечный пересмотр относится к реализации. Реализация программы тоже находится за границей спеки. В нее входят новые ТЗ для ступеней 2–7 основной программы и факультативов, а также отражение порядка программы в `docs/labs/README.md`. Карту #182 приемка этой спеки закрыла; исполнение ведет отдельная карта — тикет #206 (решение тикета #203).