Тема: Часть 9. Raw и Bronze-приземление
Уровень сложности: Средний
Расчётное время изучения: 2-3 часа (теория + практика)
Предварительные требования: Базовое понимание архитектуры Data Lakehouse (слои raw, bronze, silver, gold)
Опыт работы с dbt или аналогичными инструментами трансформации данных
Знание SQL и понятий о схемах данных
Понимание принципов lineage и метаданных
Базовое знакомство с Python (для генерации синтетических данных)
Цели обучения: Различать зоны ответственности слоёв Raw и Bronze и формулировать, какие преобразования допустимы на каждом из них
Объяснять, почему нельзя модифицировать, удалять или скрывать данные в raw-слое без явного бизнес-обоснования
Составлять приёмочные факты (acceptance facts) для проверки воспроизводимости входа в хранилище
Описывать семантику пустых значений и null через манифест источника, а не через ручные правки
Строить цепочку доказательств raw → манифест → спецификация → staging → тест для защиты продуктовых решений
Обзор: Глава 9 посвящена первым двум слоям Lakehouse-архитектуры — Raw и Bronze — которые отвечают за воспроизводимость входа данных. В учебном примере raw представлен CSV-файлами, генерируемыми скриптом scripts/generate_data.py; в продакшн-среде это может быть зона посадки в объектном хранилище, топик Kafka, CDC-таблица или внешний API. Несмотря на разницу физической реализации, принцип един: на первом шаге нельзя менять смысл данных. Raw отвечает на вопрос «что пришло» и сохраняет вход как есть — включая пустые значения, PII и противоречия. Bronze добавляет минимальную техническую нормализацию (формат файла, типизация, метаданные загрузки, lineage), но не принимает продуктовых решений. Цель главы — научить читателя отличать описание данных от их исправления, понимать, что «скучный и честный» raw — это страховка от будущих споров о происхождении данных (особенно критичная в банковском контексте), и строить короткую цепочку доказательств, которая позволяет любому ревьюеру проследить путь от источника до витрины.
Ключевые концепции: Raw-слой: Зона посадки, отвечающая на вопрос «что пришло». Сохраняет данные в их исходном виде: те же имена полей, тот же порядок, те же пустые значения и null-маркеры. Запрещено переименовывать бизнес-поля, удалять строки, скрывать PII, исправлять значения без доказательств и выполнять любые агрегации. Raw-слой намеренно «скучный»: его задача — быть честным зеркалом источника, а не фильтром или нормализатором.
Bronze-слой: Промежуточный технический слой, добавляющий минимальную нормализацию без продуктовой логики: формат файла (Parquet, Delta), базовую типизацию полей, метаданные загрузки (время, источник, batch_id), контроль наличия колонок и базовый lineage. Bronze не должен принимать бизнес-решений — например, решать, что пустой revoked_at означает активное согласие. Это решение переносится в staging-спецификацию.
Воспроизводимость входа: Ключевое свойство Lakehouse: возможность повторно сгенерировать raw-данные и пройти по цепочке трансформаций до витрины с тем же результатом. Достигается через версионирование скриптов генерации, манифесты источников, контроль заголовков и фиксацию синтетических данных.
Приёмочные факты raw/bronze: Минимальный набор проверок, подтверждающих, что вход корректен: исходные файлы существуют и доступны, заголовки соответствуют ожидаемой схеме источника из доменной карты, генерация синтетических данных воспроизводима, семантика пустых значений и null описана в манифесте, PII не потеряно до утверждения политики обработки.
Семантика пустых значений: Правило интерпретации пустых ячеек и NULL-значений, которое фиксируется в манифесте источника, а не в коде или данных. Например, пустой revoked_at может означать «согласие активно» или «согласие отозвано до внедрения поля» — выбор делается бизнесом и записывается явно, а не подразумевается из удобства прохождения теста.
Lineage и метаданные загрузки: Техническая информация, добавляемая на уровне Bronze: имя источника, путь к файлу или топику, временная метка загрузки, идентификатор batch-а, версия схемы. Позволяет проследить происхождение любой строки в витрине вплоть до исходного события.
Манифест источника: Документ, фиксирующий ожидаемую структуру и семантику raw-данных: перечень колонок с типами, описание пустых значений и null-маркеров, бизнес-владелец, политика обновления, открытые вопросы по спорным полям. Связывает источник данных с остальной архитектурой.
Sdd data (specification-driven data): Подход, при котором решения о данных фиксируются в спецификациях и манифестах, а не в коде трансформации или, тем более, в самих данных. Raw/Bronze — это место, где SDD защищает будущий разговор с ревьюером или регулятором.
Типичная ошибка «зелёного теста»: Соблазн исправить исходный CSV, чтобы dbt build прошёл. Внешне ошибка исчезает, но вместе с ней исчезает доказательство того, что источник прислал неоднозначное значение. Правильный путь: зафиксировать вход, описать интерпретацию в staging-контракте, добавить тест.
Доменная карта: Карта предметной области, описывающая сущности, связи между ними, владельцев данных и ожидаемые источники. Используется как эталон при сверке заголовков raw-файлов.
Важные даты: Этап эволюции lakehouse (примерно 2019-2020): Появление терминологии medallion architecture (Bronze/Silver/Gold) в Databricks и закрепление идеи послойной обработки данных с явным разделением технической нормализации и бизнес-логики.
Внедрение dbt в практику sdd (с 2021): Рост популярности dbt как инструмента, который сделал staging- и mart-слои «общающимися на SQL», усилил внимание к спецификациям моделей и тестам, что повысило значимость честного raw-слоя.
Регуляторные изменения по pii (gdpr с 2018, обновления фз-152 в рф): Ужесточение требований к обработке персональных данных, из-за которых правило «не скрывать PII до утверждения политики» стало особенно критичным в банковском и финансовом секторах.
Практические упражнения: Название: Поиск поля с неоднозначной семантикой пустоты
Проблема: Откройте каталог book3/examples/bank-lakehouse/raw и изучите все CSV-файлы. Найдите одно поле, в котором встречаются и пустые строки, и явные null-маркеры (или значение, которое выглядит как 'unknown', 'n/a' и т. п.). Для найденного поля опишите: (1) как оно должно быть сохранено как raw-факт; (2) где и как будет выполнена техническая типизация в Bronze; (3) в какой staging-спецификации появится бизнес-правило интерпретации; (4) какой тест в dbt должен подтверждать корректность интерпретации. Подсказка: начните с файла consents.csv и поля revoked_at.
Решение: Шаг 1. Откройте raw/consents.csv и найдите строки, где revoked_at пуст. Шаг 2. Зафиксируйте raw-факт: пустая ячейка сохраняется как пустая (или как NULL после чтения Pandas/duckdb, в зависимости от парсера), без подстановки значений по умолчанию. Шаг 3. В Bronze-модели stg_consents (или эквивалентной) выполните приведение типа к DATE или TIMESTAMP, не интерпретируя пустоту. Шаг 4. В staging-спецификации (например, в schema.yml или в _models/_docs) добавьте правило: пустой revoked_at в staging-логике трактуется как «согласие активно на момент выгрузки». Шаг 5. Напишите dbt-тест, проверяющий, что после трансформации для всех активных согласий значение is_active = true соответствует ожидаемой доле (например, not_null или accepted_values). Шаг 6. Зафиксируйте решение в заметке к тесту, объясняющей, почему именно так.
Сложность: intermediate
Название: Сверка заголовков с доменной картой
Проблема: Используя Qwen (или вручную), выполните запрос из раздела «Qwen-запрос»: проверьте raw-источники в book3/examples/bank-lakehouse/raw, сравните заголовки с ожидаемыми источниками из доменной карты и составьте список фактов источника и открытых вопросов. dbt-модели не меняйте. Цель — зафиксировать расхождения до того, как они приведут к ошибкам в staging.
Решение: Шаг 1. Откройте каждый CSV в каталоге raw и выпишите фактические заголовки. Шаг 2. Откройте доменную карту (например, docs/domain_map.md или аналогичный файл в репозитории) и выпишите ожидаемые заголовки для каждого источника. Шаг 3. Составьте таблицу расхождений: какое поле отсутствует, какое переименовано, какое имеет неожиданный тип. Шаг 4. Для каждого расхождения классифицируйте: (а) реальная ошибка источника → открытый вопрос к владельцу; (б) расхождение в терминологии → уточнить манифест; (в) изменилась доменная модель → обновить карту. Шаг 5. Составьте markdown-документ «Факты источников и открытые вопросы», который станет входной точкой для ревьюера.
Сложность: intermediate
Название: Анти-паттерн: починка CSV вместо починки контракта
Проблема: Симулируйте ситуацию: вы запускаете dbt build, и модель stg_consents падает с ошибкой «NULL value not allowed for column revoked_at_iso». В raw-файле действительно есть строки с пустым revoked_at. Смоделируйте два пути решения — неправильный и правильный — и опишите последствия каждого через 1, 6 и 12 месяцев.
Решение: Неправильный путь: открыть CSV, заменить пустые строки на placeholder-дату (например, 1900-01-01), пересохранить файл, перезапустить dbt build — он зелёный. Через 1 месяц никто не помнит о подмене. Через 6 месяцев приходит аудитор и спрашивает, почему в системе 12% согласий с датой отзыва в 1900 году. Через 12 месяцев вы не можете восстановить, какие клиенты действительно отозвали согласие, а какие нет. Правильный путь: зафиксировать raw как есть; в staging-модели использовать safe_cast или try_cast, который превращает пустоту в NULL; в манифесте описать, что пустой revoked_at интерпретируется как «активное согласие»; добавить dbt-тест not_null на производное поле is_active и тест на согласованность is_active ↔ revoked_at. Через 1, 6 и 12 месяцев цепочка доказательств остаётся целой: raw → манифест → staging → тест.
Сложность: intermediate
Название: Проектирование приёмочных фактов для нового источника
Проблема: Команда подключает новый источник — выгрузку операций по картам из внешнего процессинга. Источник приходит как parquet-файл в S3 ежедневно. Спроектируйте набор приёмочных фактов для raw- и bronze-слоя, которые будут проверяться автоматически (например, Great Expectations, dbt-source-freshness или собственный Python-скрипт).
Решение: Raw-приёмочные факты: (1) файл существует по ожидаемому пути s3://bucket/raw/card_ops/yyyy/mm/dd/; (2) размер файла > 0; (3) заголовки соответствуют манифесту (card_id, txn_id, amount, currency, txn_ts, mcc); (4) контрольная сумма совпадает с манифестом (защита от подмены); (5) нет неожиданных колонок; (6) PII-поля (card_id) присутствуют и не замаскированы. Bronze-приёмочные факты: (1) схема приведена к ожидаемым типам (amount → DECIMAL, txn_ts → TIMESTAMP); (2) добавлены метаданные загрузки (loaded_at, source_batch_id, source_file_path); (3) количество строк raw и bronze совпадает (защита от потерь при конвертации); (4) freshness: данные не старше N часов; (5) lineage записан в meta-таблицу. Все проверки запускаются до старта dbt build и блокируют его при провале.
Сложность: advanced
Кейсы: Название: Спор о согласии клиента через полгода после выгрузки
Сценарий: Розничный банк получает жалобу от клиента: «Я отзывал согласие на обработку персональных данных ещё в марте, а мне продолжают звонить с рекламными предложениями в августе». Юридический отдел запрашивает у команды данных подтверждение статуса согласия на каждый день с марта по август. Данные поступают из внешней CRM в виде ежедневных CSV-выгрузок, которые складываются в raw-слой и далее трансформируются через dbt в витрину согласий.
Задача: При первичной загрузке CSV-файла за март поле revoked_at было частично пустым (источник прислал данные в переходный период, когда часть систем ещё не передавала дату отзыва). Команда, чтобы зелёный dbt build прошёл, открыла файл и заменила пустые ячейки на дату 1900-01-01. Технически данные выглядят «чистыми», но клиент, который действительно отозвал согласие в середине марта, теперь числится как активный до августа — потому что «отзыв» по системе произошёл в 1900 году.
Решение: Аудит команды данных восстанавливает цепочку событий: находит в git-истории коммит, в котором CSV-файл был пересохранён с подставленной датой; проверяет staging-модель и видит, что интерпретация revoked_at = 1900-01-01 → is_active = false не была задокументирована. Команда переделывает процесс: (1) восстанавливает оригинальный CSV из резервной копии S3 с версионированием; (2) в манифесте источника фиксирует, что пустой revoked_at трактуется как «нет данных», а не как «активно»; (3) в staging-логике добавляет явное правило: is_active = (revoked_at IS NULL AND consent_given_at IS NOT NULL); (4) добавляет dbt-тест, запрещающий появление даты 1900-01-01 в production-данных; (5) перевыпускает витрину согласий и предоставляет юристам корректную историю.
Результат: Юридический отдел получает корректный таймлайн: клиент действительно отозвал согласие 14 марта, и начиная с этой даты он не должен был получать маркетинговые коммуникации. Банк урегулирует спор без штрафа. Внутренний процесс пересматривается: запрещается любое ручное редактирование raw-файлов, в CI добавляется проверка, что git-история CSV пуста после начального коммита, а все интерпретации пустых значений проходят через review манифеста.
Извлечённые уроки: «Зелёный тест» — это не доказательство корректности; правка исходных данных подменяет сигнал и убивает воспроизводимость
Семантика пустых значений должна жить в спецификации, а не в коде или в самих данных
В банковском контексте спор возникает не сегодня, а через месяцы — поэтому цепочка доказательств raw → манифест → staging → тест должна быть восстановимой в любой момент
Версионирование raw-данных (S3 versioning, DVC, git-lfs) — страховка, которая позволяет откатить «ручную починку» и увидеть, что источник прислал на самом деле
Связанные концепции: Raw-слой
Семантика пустых значений
Манифест источника
SDD Data
Типичная ошибка «зелёного теста»
Воспроизводимость входа
Название: Подключение Kafka-топика как нового raw-источника
Сценарий: Команда переходит от пакетных CSV-выгрузок к потоковому приёму событий платежей через Kafka. Топик payments.events пишется в объектное хранилище через Kafka Connect в формате Avro. Команда должна решить, где проходит граница между raw и bronze, и кто отвечает за схему Avro.
Задача: Появляются новые вопросы: (1) считать ли parquet-файлы в S3, сгенерированные Kafka Connect, уже bronze-слоем (потому что там есть схема и метаданные), или это всё ещё raw? (2) нужно ли сохранять сырой Avro-байт-поток для аудита? (3) как описать семантику пустых значений в потоковых данных, где «отсутствие события» — это тоже факт?
Решение: Команда принимает следующее решение: сырой Avro-снапшот каждого батча сохраняется в S3 в каталоге raw/payments_avro/ для целей аудита — это настоящий raw. Паркет-файлы, сгенерированные Kafka Connect, помещаются в каталог raw/payments/ и считаются raw-фактом: в них есть технические метаданные Kafka (offset, partition, timestamp), но нет бизнес-интерпретации. Bronze-модель payments_bronze выполняет только: (1) приведение Avro-схемы к dbt-типам; (2) добавление loaded_at, source_partition; (3) проверку наличия обязательных полей (event_id, amount, currency); (4) контрольную проверку, что количество записей в Avro-снапшоте и в parquet совпадает. Семантика пустых значений фиксируется в манифесте: пустой merchant_category означает «MCC не определён системой-источником», а не «категория отсутствует по решению бизнеса».
Результат: Команда получает два независимых слоя доказательств: Avro-снапшоты для глубокого аудита и parquet для ежедневной трансформации. При любом расхождении между витриной и источником можно за часы восстановить, что именно пришло из Kafka. Расходы на хранение Avro-снапшотов оправдываются снижением регуляторного риска.
Извлечённые уроки: Физический формат файла (Parquet, Avro, CSV) не определяет автоматически слой — важна роль данных в конвейере
Raw должен сохраняться даже тогда, когда downstream-инструменты (Kafka Connect, Fivetran) уже добавляют технические метаданные
В потоковых данных «отсутствие события» — это такой же факт, как и само событие, и должен быть описан в манифесте
Bronze не должен брать на себя ответственность за интерпретацию схемы источника — это задача staging-спецификации
Связанные концепции: Bronze-слой
Lineage и метаданные загрузки
Приёмочные факты Raw/Bronze
Воспроизводимость входа
Доменная карта
Название: CDC-таблица как raw: граница между техническим и продуктовым
Сценарий: Команда переходит на CDC (Change Data Capture) из PostgreSQL: Debezium пишет изменения в топик, оттуда данные попадают в Iceberg-таблицу raw.customers_cdc. Таблица содержит все операции (INSERT, UPDATE, DELETE) с метаданными __op, __ts_ms, __source_ts_ms. Аналитикам нужна только актуальная версия клиента.
Задача: Где должно жить правило «оставить только последнюю версию клиента» — в bronze или в staging? Если в bronze, то bronze начинает принимать продуктовое решение. Если в staging, то staging получает поток операций и должен разбираться в CDC-семантике. Кроме того, нужно решить, что делать с операциями DELETE — считать ли их «удалением клиента» или «операцией в системе-источнике».
Решение: Команда фиксирует: raw.customers_cdc хранит все операции как они пришли, без фильтрации. Это значит, что в таблице остаются и INSERT, и UPDATE, и DELETE, и даже TOAST-обновления. Bronze-модель customers_cdc_bronze выполняет только: (1) явное приведение типов (особенно для JSON-полей в PostgreSQL); (2) переименование технических CDC-полей в lineage-поля (cdc_op, cdc_ts_ms); (3) добавление loaded_at, source_db, source_table. Все решения о «последней версии», «активном клиенте», «применять ли DELETE» переезжают в staging-спецификацию stg_customers, где они явно описаны и покрыты тестами. Например, тест проверяет, что после применения CDC-операций количество активных клиентов совпадает с оперативной системой.
Результат: Команда получает «скучный» raw, в котором видны все нюансы источника: например, оказывается, что UPDATE-операции иногда приходят раньше, чем INSERT (из-за порядка партиций Kafka), и это требует специальной логики в staging. Без raw-факта эта аномалия была бы скрыта «чистым» bronze-слоем и проявилась бы только при спорах с регулятором о корректности пересчёта остатков.
Извлечённые уроки: CDC-данные — это всегда raw, потому что отражают внутреннюю кухню источника, а не продуктовое представление
«Последняя версия сущности» — это staging-решение, которое должно быть задокументировано и покрыто тестом
Технические метаданные CDC (__op, __ts_ms) важно сохранять как lineage, а не выбрасывать на этапе bronze
Raw-слой защищает от потери контекста при смене staging-логики: если завтра бизнес изменит правило «что считать активным клиентом», можно пересчитать витрину без потери исходных операций
Связанные концепции: Raw-слой
Bronze-слой
Lineage и метаданные загрузки
SDD Data
Воспроизводимость входа
Советы по изучению: Перед чтением главы откройте каталог book3/examples/bank-lakehouse/raw и бегло просмотрите все CSV-файлы — это поможет увидеть реальные примеры пустых значений и противоречий, которые обсуждаются в тексте.
Ведите отдельный блокнот «Открытые вопросы по raw», куда записывайте все спорные поля по мере чтения. Этот блокнот станет основой для манифеста источников в вашем собственном проекте.
Проговорите вслух правило: «Raw отвечает на вопрос ЧТО пришло, staging — на вопрос ЧТО ЭТО ОЗНАЧАЕТ». Если на занятии вы забыли границу — вернитесь к этой фразе.
При изучении кейсов сначала попробуйте сами предложить «неправильный путь», а потом сверьте с авторским — так активнее формируется анти-паттерн, и в продакшне рука не потянется править CSV.
Используйте технику «обратного lineage»: от любого поля в витрине поднимитесь до raw-файла и проверьте, что каждое преобразование задокументировано. Если где-то цепочка обрывается — это сигнал, что есть скрытая продуктовая логика в bronze или raw.
Сформулируйте личное правило: «Прежде чем править файл, спроси себя — это описание данных или исправление данных?» Если исправление — остановитесь и пересмотрите staging-контракт.
Сделайте себе чек-лист из 5 приёмочных фактов raw и повесьте рядом с монитором — это превращает абстрактные требования в ежедневную привычку при приёме новых источников.
Для запоминания разницы raw/bronze используйте мнемонику: «Raw — это зеркало, Bronze — это рамка вокруг зеркала». Рамка может улучшить вид, но не должна менять отражение.
Дополнительные ресурсы: Книга 'fundamentals of data engineering' (joe reis, matt housley, o'reilly 2022): Глава о storage и ingestion подробно разбирает, почему первый слой должен быть «скучным» и как связаны raw, bronze и принцип immutability.
Документация dbt: 'about dbt project structure' и 'source freshness': Описание того, как в dbt правильно декларировать sources и проверять freshness — это формализация приёмочных фактов raw-слоя.
Статья 'the medallion architecture: a guide to bronze, silver, and gold layers' (databricks): Оригинальное описание архитектуры medallion с акцентом на границы между слоями и запрет на бизнес-логику в bronze.
Материалы 'great expectations' (или替代итель soda core): Инструменты для формализации приёмочных фактов raw/bronze как кода, чтобы проверки были версионируемыми и воспроизводимыми.
Книга 'designing data-intensive applications' (martin kleppmann, глава о stream processing): Глубокое объяснение того, что такое «факт поступления события» и почему важно отделять его от интерпретации.
Стандарт фз-152 (рф) и gdpr (ес) по обработке pii: Регуляторная база, объясняющая, почему в raw нельзя скрывать PII до утверждения политики — это юридическая, а не только инженерная норма.
Dbt-utils пакет: source freshness и accepted values тесты: Готовые тесты для формализации части приёмочных фактов raw/bronze в стандартном dbt-проекте.
Qwen (или аналогичный llm-ассистент): Инструмент для автоматизации рутинной проверки заголовков raw-файлов против доменной карты — пример запроса приведён в главе.
Резюме: Глава 9 «Raw и Bronze-приземление» формулирует три ключевых принципа. Во-первых, raw — это «скучный и честный» слой, который сохраняет вход в его исходном виде: не переименовывает поля, не удаляет строки, не скрывает PII, не исправляет значения и не агрегирует. Его задача — быть зеркалом источника, а не фильтром. Во-вторых, bronze добавляет минимальную техническую нормализацию (формат файла, типизация, метаданные загрузки, lineage), но не должен принимать продуктовых решений — интерпретация спорных значений переносится в staging-спецификацию. В-третьих, воспроизводимость входа проверяется через приёмочные факты: существование файлов, корректность заголовков, воспроизводимость генерации, описанная семантика пустых значений и сохранность PII до утверждения политики. Главная типичная ошибка — правка raw-данных ради «зелёного теста», которая внешне убирает проблему, но разрушает цепочку доказательств и делает невозможным восстановление истины через месяцы (что критично в банковском контексте). После изучения главы читатель должен уметь отличать описание данных от их исправления, проектировать приёмочные факты для новых источников и строить короткую восстановимую цепочку raw → манифест → спецификация → staging → тест.