Что такое 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 кормит и финансовую витрину, и продуктовую, и маркетинговую. Логику очистки написали один раз, и все считают выручку от одного источника. Это убивает классический бардак, когда у финансов одна цифра, у продукта другая, и оба «правы», потому что по-разному фильтровали статусы.
Что ещё это даёт на практике:
- Понятный дебаг. Разошлась цифра — спускаешься по слоям и находишь, где именно.
- Дешёвый перепрогон. Silver или gold можно пересобрать из bronze, не дёргая источник.
- Разделение ответственности. Инженеры отвечают за bronze/silver, аналитики уверенно работают с gold.
- Аудит и воспроизводимость. Bronze хранит исходник, так что любой результат можно доказать.
Чтобы почувствовать эту цепочку руками, полезно порешать практические задания на очистку и агрегацию и разобрать реальные кейсы, где как раз показывают путь от сырья до витрины. На собеседованиях, кстати, всё чаще спрашивают именно про слои и трансформации — подборка есть в разделе вопросов с интервью.
С чего начать и каких ошибок избегать
Не нужно строить все три слоя идеально с первого дня. Возьмите один поток данных — например, заказы — и проведите его сквозь bronze → silver → gold целиком. Так вы поймёте логику на маленьком примере, а потом растиражируете её на остальные источники.
Типичные грабли, на которые я наступал сам:
- Делать очистку прямо в bronze. Тогда теряется страховка, и перепрогнать историю уже не из чего.
- Тащить агрегации в silver. Слой раздувается, а витрины начинают дублировать друг друга неявно.
- Строить gold прямо из bronze «чтобы быстрее». Через месяц никто не понимает, где живёт логика дедупа.
- Забыть про тесты на ключи silver. Один дубль по
order_id— и выручка в gold удвоится незаметно.
Если базовый SQL под трансформации пока шатается, имеет смысл сначала подтянуть фундамент — курс по SQL с нуля и Python-справочник закрывают ровно те приёмы (оконки, джойны, дедуп, приведение типов), из которых собирается silver и gold.
Medallion не магия и не новая технология — это привычка раскладывать данные по ответственности. Сырьё отдельно, чистое отдельно, витрины отдельно. Как только вы один раз проживёте полный путь от bronze до gold на своём проекте, обратно к «одной куче staging» уже не захочется.
Хотите отработать SQL и Python на реальных задачах, из которых собираются такие слои, — на тренажёре открыто по несколько бесплатных задач в каждом разделе, а полный доступ ко всем задачам, кейсам и AI-разбору даёт Pro. Начните с бесплатных — этого хватит, чтобы понять, ваш ли это формат.