Learning Platform
Глоссарий Troubleshooting
Урок 17.07 · 23 мин
Продвинутый
capstonearchitecture-reviewcode-reviewbest-practices

Разбор архитектуры решения и code review

Капстоун-проект построен: сырые файлы прошли staging, превратились в партиционированную витрину в DuckLake, обновляются инкрементально через MERGE и публикуются для браузера через DuckDB-WASM. Финальный урок — рефлексия. Разберём принятые архитектурные решения и проверим, почему каждое из них правильное; пройдёмся по типичным ошибкам, которые легко допустить в таком пайплайне; и сведём всё в чеклист готовности.

Этот урок не добавляет новых механизмов — он учит смотреть на готовое решение глазами ревьюера. Умение оценить чужой (и свой) пайплайн — отдельный навык аналитического инженера.


Разбор архитектурных решений

Пройдём по ключевым решениям проекта и для каждого зафиксируем, какую альтернативу мы отвергли и почему.

Архитектура капстоуна: решения и их обоснование
Трёхслойность raw/staging/martsАльтернатива — один монолитный запрос от сырья до витрины. Отвергнута: теряется изоляция изменений источника, переиспользование и диагностика сбоев.
DuckDB как движокАльтернатива — Spark-кластер. Отвергнута: для десятков ГБ на одной машине кластер это лишняя инфраструктура и накладные расходы.
DuckLake для martsАльтернатива — обычные Parquet-файлы. Отвергнута: витрине нужны ACID, снапшоты и инкрементальный MERGE, которых у голых файлов нет.
Партиционирование + MERGEАльтернатива — полный пересчёт витрины на каждый новый файл. Отвергнута: пересчёт 41 млн строк ради одного месяца расточителен.

Трёхслойность. Альтернатива — один гигантский запрос от сырья сразу к витрине. Отвергнута: монолит теряет изоляцию (правка схемы источника задевает весь запрос), переиспользование (staging нельзя подключить к другой витрине) и диагностику (при сбое неясно, данные виноваты или бизнес-логика). Цена слоёв — чуть больше кода; выгода — поддерживаемость.

DuckDB как движок. Альтернатива — Spark. Отвергнута для масштаба капстоуна: десятки гигабайт на одной машине не требуют распределённого кластера, а Spark принёс бы JVM, кластерный планировщик и инфраструктуру, которые здесь чистый overhead. Бенчмарк это подтвердил измерением.

DuckLake для marts. Альтернатива — материализовать витрину обычными Parquet-файлами. Отвергнута: витрине нужны ACID (атомарность обновления), снапшоты (откат неудачной загрузки, воспроизводимость) и инкрементальный MERGE — у голых файлов этого нет. При этом raw мы намеренно оставили обычными файлами: сырью эти свойства не нужны.

Партиционирование плюс MERGE. Альтернатива — полный пересчёт витрины при каждом новом файле. Отвергнута: пересчитывать 41 миллион строк ради одного месяца расточительно. Партиционирование по месяцам дало локальность обновления, MERGE дал идемпотентный инкрементальный апдейт.

Общий принцип, видимый во всех четырёх решениях: каждое выбрано под конкретный масштаб и характер задачи, а не по моде. Это и есть то, что проверяет ревьюер — не «модный ли стек», а «соответствует ли стек задаче».


Типичные ошибки в коде пайплайна

Теперь — code review. Пройдёмся по ошибкам, которые легко допустить в DuckDB-пайплайне такого типа. Каждая — реальная, и в нашем проекте её нет именно потому, что о ней знали.

Деньги в DOUBLE вместо DECIMAL. Сумма поездки в DOUBLE — это накопление ошибки округления плавающей точки на миллионах строк агрегации. В капстоуне total_amount объявлен DECIMAL — для денег это обязательно.

SELECT * в staging. Соблазн написать SELECT * FROM read_parquet(...) в staging вместо явного списка колонок. Ошибка: при появлении новой колонки в источнике она молча протечёт через все слои, а типы останутся теми, что угадал источник. Явный список колонок с явными кастами — это контракт.

Инкрементальный шаг на INSERT вместо MERGE. INSERT нового месяца кажется проще MERGE. Но INSERT не идемпотентен: повторный запуск (упал на полпути, перезапустили) задублирует строки. MERGE при повторе перезаписывает совпавшее и не плодит дубли.

Бизнес-логика в staging. Посчитать выручку прямо в stg_trips кажется экономией слоя. Но это ломает границу: staging перестаёт быть переиспользуемым и тестируемым, а при сбое неясно, чья вина.

Игнорирование memory_limit на больших данных. Полагаться на умолчания, когда датасет больше RAM, — рецепт либо неожиданного спилла, либо медленного запроса. Лимит и temp_directory задаются явно.

Публикация сырья в браузер. Отдать DuckDB-WASM витрину целиком или сырые поездки — перегрузить вкладку. В браузер идёт компактный предагрегированный срез.

WARNING

Самая коварная из этих ошибок — INSERT вместо MERGE в инкрементальном шаге, потому что она не проявляется на счастливом пути. Пайплайн работает месяцами, пока однажды шаг не упадёт на полпути и его не перезапустят — и тогда витрина тихо получает дубли, а ошибка в данных всплывает гораздо позже. Идемпотентность не опция, а требование к любому шагу, который может быть перезапущен.


Чеклист code review пайплайна

Сведём проверку в чеклист, применимый к любому DuckDB-пайплайну такого типа.

КатегорияЧто проверить
СлоиRaw иммутабелен; staging чистит без бизнес-логики; marts моделирует. Границы не размыты.
ТипыДеньги в DECIMAL, не DOUBLE. Даты в DATE/TIMESTAMP. Касты явные, не на усмотрение источника.
StagingЯвный список колонок, не SELECT *. Мусор отсеян в WHERE. Единый нейминг.
ИнкрементальностьНовый файл не требует пересчёта истории. Обновление через MERGE, идемпотентно.
ПартиционированиеКлюч партиции соответствует тому, как данные приходят и читаются.
Памятьmemory_limit и temp_directory заданы явно; temp_directory на быстром диске.
ХранениеВитрины с ACID-потребностями в DuckLake; сырьё — обычные файлы.
ПубликацияВ браузер идёт компактный срез, а не вся витрина и не сырьё.
ИдемпотентностьПовторный запуск любого шага даёт тот же результат.

Этот чеклист — не формальность. Каждый пункт соответствует ошибке, которая в реальном пайплайне рано или поздно стреляет.


Куда расти от капстоуна

Проект сделан, но в production его можно усилить. Это направления, в которые капстоун естественно продолжается.

Развитие капстоуна за рамки курса
Тесты данныхПроверки качества: not-null на ключах, диапазоны метрик, отсутствие дублей после MERGE. Прогонять на каждый запуск пайплайна.
ОркестрацияЗапуск пайплайна по расписанию при появлении нового файла. Например, через dbt-duckdb или внешний шедулер.
МониторингНаблюдение за временем расчёта, объёмом спилла, числом строк витрины. Алерт на аномалии.

Тесты данных. Сейчас корректность мы проверяли запросами вручную. В production нужны автоматические проверки качества: ключи не NULL, метрики в разумных диапазонах, после MERGE нет дублей. Их прогоняют на каждый запуск, и пайплайн падает при нарушении.

Оркестрация. Капстоун-пайплайн запускается руками. Production-вариант запускается сам — по расписанию или по событию появления нового файла. DuckDB вписывается в dbt-duckdb и в обычные шедулеры; для облачного варианта расписание даёт MotherDuck.

Мониторинг. Стоит наблюдать за временем расчёта, объёмом спилла, числом строк витрины от запуска к запуску — резкое отклонение это сигнал проблемы в данных или в нагрузке.

И стоит вспомнить альтернативы хранения каталога: капстоун использовал DuckLake с каталогом в SQLite, потому что проект локальный и однопользовательский. Как только пайплайн становится командным и писателей несколько, каталог переносится на PostgreSQL — код пайплайна при этом не меняется, меняется только строка ATTACH.


Что вы построили

Сведём итог капстоуна. За семь уроков собран работающий локальный аналитический lakehouse: сырые помесячные Parquet и грязный CSV на объектном хранилище проходят через staging-очистку, моделируются в партиционированную витрину в DuckLake с ACID и снапшотами, обновляются инкрементально и идемпотентно через MERGE без пересчёта истории, считаются out-of-core при датасете больше RAM, и публикуются компактным Parquet для интерактивных запросов из DuckDB-WASM в браузере.

Один движок — DuckDB — прошёл весь путь: от чтения десятков гигабайт сырья до запроса на вкладке браузера. Ни сервера, ни кластера, ни отдельного движка трансформаций. Это и есть то, ради чего курс шёл «до железа»: понимание векторизованного исполнения, storage-формата, сжатия, параллелизма, larger-than-memory механики и DuckLake складывается в умение спроектировать и собрать такой пайплайн осознанно, а не по шаблону.


Попробуй сам

Финальное задание — провести полноценный code review собственного капстоун-проекта.

Задания:

  1. Пройдите свой пайплайн по чеклисту code review из урока, пункт за пунктом. Для каждого пункта зафиксируйте «выполнено» или «надо исправить».
  2. Найдите в своём коде хотя бы одно место, где границу слоёв можно случайно нарушить (бизнес-логика в staging, SELECT *, INSERT вместо MERGE), и либо подтвердите, что её там нет, либо исправьте.
  3. Намеренно сломайте идемпотентность: замените инкрементальный MERGE на INSERT, запустите шаг дважды и убедитесь, что появились дубли. Верните MERGE и проверьте, что дублей нет. Это закрепит, почему идемпотентность обязательна.
  4. Для каждого из четырёх ключевых архитектурных решений (слои, движок, DuckLake, партиционирование+MERGE) запишите одним предложением, какую альтернативу вы отвергли и почему. Это и есть то, что вы должны уметь объяснить на собеседовании.

dbt: автоматические тесты данных как production-стандарт
Проверка знанийKnowledge check
Какие ключевые архитектурные решения приняты в капстоун-проекте, какие альтернативы отвергнуты и какие типичные ошибки кода чеклист code review призван поймать?
ОтветAnswer
В капстоуне четыре ключевых решения, и каждое выбрано под масштаб и характер задачи, а не по моде. Первое — трёхслойность raw/staging/marts вместо одного монолитного запроса: монолит теряет изоляцию изменений источника, переиспользование staging и диагностику сбоев. Второе — DuckDB как движок вместо Spark-кластера: для десятков гигабайт на одной машине кластер это лишняя инфраструктура и накладные расходы JVM и планировщика, что подтвердил бенчмарк. Третье — DuckLake для marts вместо обычных Parquet-файлов: витрине нужны ACID, снапшоты и инкрементальный MERGE, которых у голых файлов нет, тогда как сырьё намеренно оставлено обычными файлами. Четвёртое — партиционирование по месяцам плюс MERGE вместо полного пересчёта витрины на каждый новый файл: партиционирование даёт локальность обновления, MERGE даёт идемпотентный инкрементальный апдейт. Чеклист code review ловит типичные ошибки: деньги в DOUBLE вместо DECIMAL (накопление ошибки округления); SELECT * в staging вместо явного списка колонок (новая колонка молча протекает через слои); INSERT вместо MERGE в инкрементальном шаге (не идемпотентен, повторный запуск даёт дубли — самая коварная ошибка, потому что не проявляется на счастливом пути); бизнес-логика в staging (ломает границу слоёв, теряется переиспользуемость и тестируемость); игнорирование memory_limit и temp_directory на датасете больше RAM; публикация сырья или всей витрины в браузер вместо компактного среза. Главный принцип, который проверяет ревьюер, — не модность стека, а соответствие стека задаче, и идемпотентность каждого шага, который может быть перезапущен. За рамками курса проект усиливается тестами качества данных, оркестрацией и мониторингом, а каталог DuckLake переносится с SQLite на PostgreSQL при переходе к командной работе с несколькими писателями.

Проверьте понимание

Результат: 0 из 0
Концептуальный
Вопрос 1 из 4. Какой общий принцип объединяет все четыре ключевых архитектурных решения капстоуна (слои, движок, DuckLake, партиционирование+MERGE)?

Закончили урок?

Отметьте его как пройденный, чтобы отслеживать свой прогресс

Войдите чтобы оценить урок

Прогресс модуля
0 из 7