Капстоун: анти-паттерны, документация, защита
Четыре урока капстоуна прошли путь от текста заказчика до готовых моделей: conceptual model, нормализованная OLTP-схема, star schema с SCD2 и date dimension. Финальный урок — про то, что отличает работу профессионала от работы новичка: критическую проверку собственной модели, её документирование и умение её защитить. Спроектировать схему — половина дела. Вторая половина — убедиться, что в ней нет типичных ошибок, объяснить её другим и обосновать каждое решение.
Этот урок завершает и капстоун, и весь курс. Мы пройдём по модели проката с критическим взглядом, разберём анти-паттерны (которых мы избежали — и проверим, что избежали), научимся документировать модель и защищать решения на code review.
Анти-паттерны: чек-лист самопроверки
Из модуля про naming и анти-паттерны вы знаете типичные ошибки моделирования. Сейчас прогоним модель проката по чек-листу — это и есть навык самопроверки, который должен быть у каждого моделировщика.
Анти-паттерн 1: списки через запятую (нарушение 1NF). Ошибка — хранить в одном поле bike_types = 'regular,electric' или несколько телефонов в одной ячейке. Проверяем нашу OLTP-схему: каждое поле атомарно, списков в ячейках нет. [x] Избежали.
Анти-паттерн 2: отсутствие FK-constraints в OLTP. Ошибка — «свяжем таблицы логически, а constraints не поставим, чтобы не тормозило». Результат — мусорные ссылки на несуществующие строки. В нашей OLTP-схеме все связи закреплены REFERENCES. [x] Избежали.
Анти-паттерн 3: смешанный grain в fact-таблице. Ошибка — в одной fact-таблице строки разного уровня детализации (и отдельные аренды, и дневные итоги). В fct_rental grain объявлен одним предложением — «одна завершённая аренда» — и все строки ему соответствуют. [x] Избежали.
Анти-паттерн 4: smart keys с зашитым смыслом. Ошибка — primary key вида MSK-2026-001, где зашиты город и год; при смене города запись «ломается». Наши surrogate keys бессмысленны. Исключение — date_key формата YYYYMMDD, но это признанное исключение (даты не меняются), а не ошибка. [x] Избежали.
Анти-паттерн 5: EAV без необходимости. EAV (entity-attribute-value) — хранение атрибутов строками таблицы (сущность, имя_атрибута, значение) вместо нормальных столбцов. Иногда оправдан, но как замена обычной схемы — анти-паттерн: запросы превращаются в кошмар. У нас обычные типизированные столбцы. [x] Избежали.
Анти-паттерн 6: неверный тип SCD (потеря нужной истории). Ошибка — применить Type 1 там, где бизнесу нужна история. Мы разобрали это в уроке 4: город клиента ведём по SCD2 именно потому, что Type 1 дал бы неверные исторические отчёты. [x] Избежали осознанно.
Пройти этот чек-лист по своей модели — обязательная привычка. Анти-паттерн, найденный самопроверкой, исправляется за минуты; найденный в проде — за недели миграций.
Naming conventions: модель должна читаться
Из того же модуля — naming conventions. Имена в модели проката не случайны, они следуют системе:
- Единый стиль — везде
snake_case(start_station_id, неStartStationId). - Префиксы слоёв —
dim_для dimensions,fct_для fact-таблиц. Видно тип таблицы по имени. - Суффиксы по смыслу —
_idдля natural/business идентификаторов и FK,_keyдля surrogate keys в DWH,_atдля timestamp,_dateдля дат. - Последовательность числа — таблицы в единственном числе (
customer,rental), и так везде.
Зачем это строго? Naming — это документация, встроенная в код. Когда новый человек видит fct_rental.start_station_key, он без единого комментария понимает: это fact-таблица, поле — surrogate key, ссылается на станцию в роли старта. Несогласованные имена (Rentals, rental_tbl, tblRental в одной базе) заставляют каждый раз гадать. Соглашение об именах экономит время всей команды на годы вперёд.
Особенно важна последовательность суффиксов _id и _key — в нашей модели они разводят два мира. _id — это бизнес-идентификаторы и ключи OLTP (customer_id, rental_id): то, чем сущность опознаётся в реальном мире. _key — surrogate keys размерной модели (customer_key, date_key): технические ключи DWH. Увидев в таблице и customer_id, и customer_key, инженер сразу понимает: это dimension под SCD2, где _key уникален на версию, а _id — durable key на сущность. Один символ суффикса несёт это знание. Поэтому суффиксы — не косметика: они кодируют роль столбца, и менять их по настроению нельзя. Договорённость должна быть записана в гайдлайн команды и соблюдаться всеми — иначе она бесполезна.
Документация модели
Готовая схема без документации — наполовину готовая работа. Что именно документируют:
- Описание каждой таблицы и столбца. Не «
amount— это amount», а «rental_amount— сумма оплаты за аренду в рублях; 0 для неоплаченных аренд». Описание снимает двусмысленность. - Grain каждой fact-таблицы. Зерно
fct_rental— «одна завершённая аренда» — должно быть записано явно, а не жить в голове автора. - Бизнес-определения метрик. Что такое «выручка» —
SUM(rental_amount)только по оплаченным? с возвратами? Это определение (вспомните semantic layer из модуля про современное моделирование) фиксируется письменно. - Решения по SCD. Какие атрибуты ведутся по SCD2 и почему; какие по Type 1.
- Lineage — откуда данные:
fct_rentalстроится из OLTP-таблицrentalиpayment.
Современный инструмент (dbt из модуля про современное моделирование) генерирует значительную часть этого автоматически: описания из YAML, lineage из ref(). Но письменно зафиксировать grain, бизнес-определения и решения по SCD — ответственность моделировщика, инструмент за вас это не придумает.
Почему документация — не «бюрократия после работы», а часть самой работы? Потому что недокументированная модель имеет ровно одну точку отказа — голову её автора. Пока автор в команде и помнит детали — всё работает. Автор ушёл в отпуск, сменил проект, уволился — и знание «выручка считается только по оплаченным арендам», «duration_minutes нельзя складывать по дням», «город ведём по SCD2, а телефон нет» исчезает вместе с ним. Новый человек либо переоткрывает эти решения заново (медленно и с ошибками), либо принимает противоречащие. Документация переводит знание из приватного — в его голове — в общее, принадлежащее команде. Это не подарок будущим коллегам, а страховка проекта от потери человека.
Простой тест качества документации: может ли новый человек в команде, не разговаривая с автором модели, правильно ответить на вопрос «что такое выручка в этом DWH» и «можно ли складывать duration_minutes по разным дням»? Если да — документация выполнила работу. Если для ответа нужно искать автора — документация неполна.
Code review модели
Модель данных проходит code review, как и обычный код (это практика из модуля про современное моделирование — software engineering применяется к данным). На что смотрит ревьюер схемы:
| Что проверяет ревьюер | Конкретный вопрос к модели проката |
|---|---|
| Корректность grain | Все ли строки fct_rental одного зерна? |
| Целостность | Стоят ли FK-constraints на всех связях OLTP? |
| Нормализация (OLTP) | Нет ли продублированных чужих атрибутов? |
| Аддитивность measures | Правильно ли классифицированы measures? |
| Решения по SCD | Те ли атрибуты ведутся по SCD2? |
| Naming | Соблюдены ли соглашения об именах? |
| Анти-паттерны | Прошёл ли чек-лист выше? |
Code review модели ловит ошибки до того, как они попадут в наполненное данными хранилище. Свежий взгляд видит то, что замылилось у автора. Это не формальность и не «контроль» — это дешёвая страховка: ревью схемы занимает час, переделка схемы в проде — недели.
Защита проектного решения
Финальный навык — защита решения. На ревью, перед заказчиком, на собеседовании вас спросят «почему так, а не иначе». Профессионал не отвечает «так принято» — он объясняет компромисс. Защита решения — это всегда формула «выбрал X, потому что приоритет Y, ценой Z».
Пройдёмся по ключевым решениям модели проката — это и есть её защита:
- «Почему две схемы — OLTP и star schema, а не одна?» OLTP оптимизирована под запись и целостность (короткие транзакции проката), star schema — под аналитическое чтение. Одна схема не может быть оптимальна для обеих нагрузок: нормализованная медленна для аналитики, денормализованная даёт аномалии при записи. Цена двух схем — нужен ETL между ними; выигрыш — каждая нагрузка обслуживается оптимально.
- «Почему surrogate keys, а не natural?» Узкий ключ ускоряет JOIN и изолирует схему от изменений бизнес-данных (сменился email — FK не задеты). Цена — ключ не несёт смысла, нужен JOIN, чтобы увидеть бизнес-идентификатор; выигрыш — стабильность и скорость.
- «Почему атомарный grain?» Из атомарного зерна выводится любая агрегация; из агрегата детализацию не вернуть. Цена — fact-таблица большая (строка на каждую аренду); выигрыш — максимальная гибкость аналитики, а большой объём вывозит колоночное хранение.
- «Почему SCD2 для города клиента?» Бизнесу нужны исторически корректные отчёты «как было». Цена — dimension растёт (строка на версию), запросы чуть сложнее; выигрыш — мартовская выручка не «переезжает» задним числом.
Заметьте: каждое решение — это компромисс, и хороший моделировщик знает обе стороны. Это и есть главный итог курса. Моделирование данных — не набор «правильных» рецептов, а непрерывная цепочка осознанных компромиссов: нормализация против скорости чтения, surrogate против natural, embedding против referencing, consistency против availability. Вы прошли весь курс — от ER-диаграмм до lakehouse и NoSQL — и теперь у вас есть и инструменты, и, что важнее, понимание, какой инструмент какой ценой решает какую задачу. Защитить решение — значит честно назвать этот компромисс. На этом курс завершён.
Попробуй сам
Завершите сквозной кейс онлайн-библиотеки — проведите финальную проверку своей модели.
- Прогоните свою схему библиотеки по чек-листу из шести анти-паттернов. По каждому отметьте: избежали или нет. Если нашли проблему — исправьте.
- Проверьте naming: единый ли стиль, есть ли префиксы
dim_/fct_, осмысленны ли суффиксы. - Напишите документацию: grain fact-таблицы выдач одним предложением, бизнес-определение метрики «количество просрочек», решения по SCD.
- Сыграйте ревьюера: задайте своей модели 5 вопросов из таблицы code review и ответьте на них.
- Защитите три решения своей модели по формуле «выбрал X, потому что приоритет Y, ценой Z».