Data EngineeringDWHmedallionархитектура данных

Medallion-архитектура: слои bronze, silver, gold на практике

2026-07-11 11 мин

Что такое medallion-архитектура простыми словами?

Medallion — это способ разложить данные в хранилище по трём слоям качества: bronze (сырьё ровно как пришло из источника), silver (очищенное, типизированное, без дублей) и gold (готовые витрины под конкретные бизнес-вопросы). Данные текут в одну сторону, bronze → silver → gold, и на каждом шаге становятся чище и ближе к тому, что реально нужно аналитику или дашборду. Название — от медалей: бронза, серебро, золото, где золото самое обработанное и ценное.

Я несколько лет собирал пайплайны, где всё валилось в одну кучу под названием staging, а потом никто не мог объяснить, откуда в отчёте взялась цифра. Medallion лечит ровно эту боль: он физически разделяет «что нам прислали» и «что мы из этого сделали». Когда кто-то спрашивает «почему выручка за вчера просела», ты не гадаешь — ты идёшь по слоям сверху вниз и смотришь, на каком шаге данные разошлись с реальностью.

Важно понимать, что это не про конкретную технологию. Medallion одинаково хорошо ложится и на классический DWH на Postgres, и на lakehouse с Parquet-файлами, и на dbt-проект. Это дисциплина организации данных, а не продукт, который можно купить.

Что попадает в bronze и почему сырьё не трогают?

Bronze — это приёмник. Сюда падает всё, что приехало из источника, максимально близко к оригиналу: выгрузки из продовой базы, события с фронтенда, ответы внешних API, файлы от партнёров. Правило простое — на этом слое мы почти ничего не переделываем. Никаких «поправим кривую дату» или «выкинем строки без email». Мы фиксируем факт: вот что нам прислали, вот когда мы это получили.

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

-- bronze_orders: как есть, плюс метаданные загрузки
create table bronze_orders (
    order_id        text,
    user_id         text,
    amount          text,          -- да, текст: вдруг источник пришлёт "12,50"
    status          text,
    created_at      text,
    _source_file    text,
    _ingested_at    timestamp default now()
);

Зачем такая аскеза? Потому что сырьё — это твоя страховка. Если очистка на следующем слое оказалась с багом, ты не теряешь исходные данные и просто перепрогоняешь трансформацию. Источник уже мог удалить старое, а у тебя в bronze лежит нетронутая копия. Плюс появляется честный аудит: можно доказать, что «наш дашборд не врёт, врал источник, вот доказательство в bronze».

Как silver превращает сырьё в чистые данные?

Silver — это слой, где сырьё становится пригодным для работы. Здесь происходит основная грязная работа: типизация, парсинг дат, приведение к единым справочникам, удаление дублей и отбрасывание мусорных строк. На выходе получаются аккуратные, атомарные таблицы, которым можно доверять, — по одной сущности на таблицу: заказы, пользователи, платежи, события.

Разберём типичные трансформации на примере заказов. Из bronze приходят строки с дублями (источник любит присылать одну запись дважды), с датами в виде текста и с null-ами там, где их быть не должно.

-- silver_orders: типы, дедуп, фильтр мусора
with typed as (
    select
        cast(order_id as bigint)              as order_id,
        cast(user_id as bigint)               as user_id,
        cast(replace(amount, ',', '.') as numeric(12,2)) as amount,
        lower(status)                         as status,
        cast(created_at as timestamp)         as created_at,
        _ingested_at,
        row_number() over (
            partition by order_id
            order by _ingested_at desc
        ) as rn
    from bronze_orders
    where order_id is not null
)
select order_id, user_id, amount, status, created_at
from typed
where rn = 1                     -- берём самую свежую версию строки
  and amount >= 0                -- отбрасываем явный мусор

Ключевой приём для дедупа — оконная функция row_number() с партицией по ключу и сортировкой по времени загрузки: оставляем одну самую свежую версию каждого order_id. Если оконные функции пока даются тяжело, потренируйтесь на живых данных в SQL-тренажёре или загляните в SQL-справочник — без них silver не построить.

Тот же дедуп в lakehouse без SQL, на pandas, выглядит так:

import pandas as pd

raw = pd.read_parquet("bronze/events/date=2026-07-10/")

silver = (
    raw
    .assign(
        user_id=lambda d: pd.to_numeric(d["user_id"], errors="coerce"),
        event_ts=lambda d: pd.to_datetime(d["event_ts"], errors="coerce", utc=True),
    )
    .dropna(subset=["user_id", "event_ts"])
    .sort_values("_ingested_at")
    .drop_duplicates(subset=["event_id"], keep="last")
)
errors="coerce" превращает битые значения в NaN, а dropna их выкидывает — это ровно та фильтрация мусора, что и в SQL-версии. Отработать такие преобразования удобно в Python-тренажёре.

Ещё одна вещь, которую многие забывают: silver — правильное место для бизнес-логики нормализации, но не для агрегаций. Здесь мы приводим строки в порядок, но не считаем «выручку по дням». Агрегации — это уже gold.

Зачем нужен gold-слой и кто им пользуется?

Gold — это витрины, заточенные под конкретное потребление. Здесь данные из чистых silver-таблиц собираются в денормализованные, агрегированные наборы, которые напрямую питают дашборды, отчёты и ML-фичи. Если silver отвечает на вопрос «какие у нас заказы», то gold отвечает «сколько выручки и активных пользователей было по дням».

-- gold_daily_revenue: витрина под дашборд
select
    date_trunc('day', o.created_at) as order_day,
    count(distinct o.user_id)       as paying_users,
    sum(o.amount)                   as revenue,
    round(sum(o.amount) / nullif(count(distinct o.user_id), 0), 2) as arpu
from silver_orders o
join silver_payments p using (order_id)
where p.status = 'succeeded'
group by 1

Смысл gold в том, что бизнес-пользователь или BI-инструмент не должен джойнить пять таблиц и помнить, что «удачные платежи — это статус succeeded, а не paid». Вся эта логика уже зашита в витрину. Открыл gold_daily_revenue — и там сразу выручка, платящие, ARPU. Метрики вроде DAU, retention или конверсии обычно живут именно на этом слое: их считают один раз, консистентно, и все берут из одного источника.

Обычно gold-таблиц много, и они пересекаются: одна под финансовый отчёт, другая под продуктовую аналитику, третья под маркетинг. Это нормально — золото затачивается под конкретного потребителя, и дублирование данных здесь осознанная плата за скорость и удобство чтения.

Как слои ложатся на dbt-модели?

dbt буквально создан под medallion, поэтому связка стала почти стандартом. Каждый слой — это папка с моделями, а зависимости между ними выражаются через ref(). Структура проекта обычно повторяет слои:

models/
  bronze/   -- source-таблицы, минимум логики
  silver/   -- очистка, типизация, дедуп
  gold/     -- витрины, агрегации, метрики

Модель silver ссылается на bronze, gold — на silver, и dbt сам выстраивает граф зависимостей и порядок выполнения. Тебе не нужно вручную помнить, что сначала пересобрать: dbt строит DAG и прогоняет модели в правильной последовательности.

-- models/gold/gold_daily_revenue.sql
select
    date_trunc('day', created_at) as order_day,
    sum(amount)                   as revenue
from {{ ref('silver_orders') }}
where status = 'completed'
group by 1

Материализацию тоже удобно раскладывать по слоям: bronze и silver часто делают инкрементальными (дописываем только новые данные), а лёгкие gold-витрины — обычными view или полными пересборками. Тесты dbt (unique, not_null, accepted_values) вешают в первую очередь на silver-ключи — это дешёвый способ ловить проблему до того, как она доедет до gold и дашборда.

Причём здесь lakehouse?

Lakehouse — это архитектура, где дешёвое объектное хранилище с файлами (Parquet, ORC) получает поверх себя транзакционный слой формата вроде Delta или Iceberg, и на нём же работает SQL-движок. Medallion — естественный способ навести порядок в таком хранилище, потому что иначе data lake быстро превращается в «болото», где лежат тысячи файлов без понятной структуры.

На практике каждый слой становится набором таблиц в отдельной зоне: bronze/, silver/, gold/. Bronze хранит сырые файлы почти без обработки, silver — очищенные Parquet-таблицы с эволюцией схемы, gold — компактные витрины. Транзакционные форматы дают то, чего в голом data lake не было: атомарные апдейты, time travel к прошлым версиям и upsert для дедупа. Именно поэтому термин «medallion» популяризировали вокруг lakehouse — он превращает свалку файлов в управляемое хранилище со слоями ответственности.

При этом ничего lakehouse-специфичного в самой идее нет. Те же три слоя прекрасно живут в схемах обычного DWH: bronze.orders, silver.orders, gold.daily_revenue. Разница только в движке и хранении, а дисциплина одна и та же.

Что medallion даёт для качества и переиспользования?

Главный выигрыш — предсказуемое качество данных, потому что оно проверяется на границах слоёв, а не постфактум в готовом отчёте. Ошибку типизации ловим на входе в silver, дубли — там же, бизнес-несостыковки — на входе в gold. Проблема останавливается на ближайшем слое и не расползается по десяткам витрин.

Второй выигрыш — переиспользование. Одна чистая silver-таблица silver_orders кормит и финансовую витрину, и продуктовую, и маркетинговую. Логику очистки написали один раз, и все считают выручку от одного источника. Это убивает классический бардак, когда у финансов одна цифра, у продукта другая, и оба «правы», потому что по-разному фильтровали статусы.

Что ещё это даёт на практике:

Чтобы почувствовать эту цепочку руками, полезно порешать практические задания на очистку и агрегацию и разобрать реальные кейсы, где как раз показывают путь от сырья до витрины. На собеседованиях, кстати, всё чаще спрашивают именно про слои и трансформации — подборка есть в разделе вопросов с интервью.

С чего начать и каких ошибок избегать

Не нужно строить все три слоя идеально с первого дня. Возьмите один поток данных — например, заказы — и проведите его сквозь bronze → silver → gold целиком. Так вы поймёте логику на маленьком примере, а потом растиражируете её на остальные источники.

Типичные грабли, на которые я наступал сам:

Если базовый SQL под трансформации пока шатается, имеет смысл сначала подтянуть фундамент — курс по SQL с нуля и Python-справочник закрывают ровно те приёмы (оконки, джойны, дедуп, приведение типов), из которых собирается silver и gold.

Medallion не магия и не новая технология — это привычка раскладывать данные по ответственности. Сырьё отдельно, чистое отдельно, витрины отдельно. Как только вы один раз проживёте полный путь от bronze до gold на своём проекте, обратно к «одной куче staging» уже не захочется.

Хотите отработать SQL и Python на реальных задачах, из которых собираются такие слои, — на тренажёре открыто по несколько бесплатных задач в каждом разделе, а полный доступ ко всем задачам, кейсам и AI-разбору даёт Pro. Начните с бесплатных — этого хватит, чтобы понять, ваш ли это формат.

Отработай SQL на практике
545 SQL-задач с автопроверкой в браузере — первые открыты без регистрации.
SQL-тренажёр →