One Big Table и wide tables
В прошлом уроке мы сказали: на gold-слое medallion живут либо star schema, либо One Big Table (OBT). Star schema вы уже знаете из модулей про размерное моделирование. OBT — подход, который стал популярен именно с приходом колоночных cloud-warehouse, и он напрямую бросает вызов привычке всегда разносить данные по таблицам. Этот урок разбирает, что такое OBT, в чём её компромисс и где её место.
One Big Table — это одна широкая денормализованная таблица, в которой собраны все факты и все описательные атрибуты сразу. Никаких отдельных dimension-таблиц, никаких JOIN на чтении. Всё, что аналитику может понадобиться для запроса, лежит в одной таблице — отсюда и название «одна большая таблица». Синоним — wide table, «широкая таблица».
Как выглядит OBT рядом со star schema
Возьмём знакомую картину. В star schema есть fct_orders с foreign keys и несколько dimensions: dim_customer, dim_product, dim_date. Чтобы получить «выручку по городам клиентов и категориям товаров», аналитик соединяет fact с тремя dimensions.
OBT берёт ту же информацию и разворачивает её в одну таблицу: атрибуты клиента, атрибуты товара, атрибуты даты — всё дописано прямо в строку рядом с метриками.
-- Star schema: fact + JOIN к dimensions на каждый запрос
SELECT c.city, p.category, SUM(f.amount)
FROM fct_orders f
JOIN dim_customer c ON f.customer_key = c.customer_key
JOIN dim_product p ON f.product_key = p.product_key
GROUP BY c.city, p.category;
-- One Big Table: всё в одной таблице, ноль JOIN
SELECT customer_city, product_category, SUM(amount)
FROM obt_orders
GROUP BY customer_city, product_category;
Структурно obt_orders — это fct_orders, в которую «влиты» все нужные столбцы из всех dimensions:
CREATE TABLE obt_orders (
order_id BIGINT,
order_date DATE,
-- метрики (бывшая fact-таблица):
amount DECIMAL(12,2),
quantity INTEGER,
-- атрибуты клиента (бывший dim_customer):
customer_id BIGINT,
customer_name VARCHAR,
customer_city VARCHAR,
customer_segment VARCHAR,
-- атрибуты товара (бывший dim_product):
product_name VARCHAR,
product_category VARCHAR,
product_brand VARCHAR,
-- атрибуты даты (бывший dim_date):
order_year INTEGER,
order_month INTEGER,
order_weekday VARCHAR
);
Почему OBT стала возможна: колоночное хранение
OBT — не новая идея сама по себе (это просто крайняя денормализация), но практичной её сделали именно колоночные cloud-warehouse: Snowflake, BigQuery, Redshift, Databricks. Чтобы понять почему, вспомните физику колоночного хранения из модуля про OLTP и OLAP.
В колоночном хранилище каждый столбец лежит на диске отдельно. Когда запрос обращается к трём столбцам из ста — warehouse читает ровно эти три, остальные 97 не трогает вообще. Поэтому «широта» таблицы почти не вредит: лишние столбцы не замедляют запрос, который их не использует. В строковом хранилище было бы иначе — там читается строка целиком.
Именно это и переворачивает старую интуицию. В мире строкового хранения (классический OLTP) широкая таблица — это плохо: каждое чтение тащит с диска все сто столбцов, даже когда нужны три. Поэтому реляционная школа учила разносить данные по узким таблицам. Колоночное хранение убирает этот штраф: в нём ширина таблицы и стоимость запроса развязаны. OBT — это идея, которая была бы вредной на строковом движке и стала разумной на колоночном. Это хороший пример общего принципа курса: «правильность» модели не абсолютна, она зависит от физики хранилища под ней. Сменилась физика — сменилось и то, какая модель хороша.
Это снимает главное возражение против OBT. «Таблица со ста столбцами» на колоночном движке — нормально: платите чтением только за то, что реально читаете. А выигрыш — отсутствие JOIN — реален: JOIN это работа (хеш-таблицы, сопоставление ключей), и убрать его означает ускорить запрос.
Замеры это подтверждают. По бенчмаркам Fivetran, на Redshift, Snowflake и BigQuery переход со star schema на OBT даёт прирост скорости запросов примерно на 25-50% (на BigQuery — около 49%). Цифры зависят от движка и нагрузки, но направление устойчивое: на колоночных warehouse OBT обычно быстрее star schema.
Компромисс: storage против compute
Раз OBT быстрее — почему не делать всегда только OBT? Потому что у скорости есть цена. OBT — это классический компромисс storage против compute.
OBT экономит compute: запрос не тратит вычисления на JOIN. Но платит storage: данные дублируются. Атрибут customer_city в star schema хранится один раз — в dim_customer, в одной строке на клиента. В OBT он повторяется в каждой строке заказа этого клиента. Если у клиента 500 заказов, его город записан 500 раз.
На колоночных warehouse этот компромисс часто выгоден для OBT по двум причинам. Первая: storage дёшев, а колоночное сжатие отлично справляется с дублированием. Столбец customer_city, где одно значение повторяется тысячи раз подряд, сжимается почти до нуля (повторы — лучший случай для сжатия). Вторая: compute дорог и его экономия — это деньги и время на каждый запрос. Платить дешёвым storage, чтобы сэкономить дорогой compute, — разумная сделка. Но «часто выгоден» не значит «всегда»: на очень больших объёмах или редко запрашиваемых данных арифметика может склониться обратно.
| Аспект | Star schema | One Big Table |
|---|---|---|
| JOIN на чтении | Нужен | Не нужен |
| Дублирование атрибутов | Нет | Есть (в каждой строке) |
| Storage | Меньше | Больше (но хорошо сжимается) |
| Compute на запрос | Больше (JOIN) | Меньше |
| Скорость на колоночном warehouse | Базовая | Обычно +25-50% |
Слабые места OBT: историчность и backfill
У OBT есть проблемы серьёзнее, чем дублирование, и о них надо знать.
Историчность dimension. Вспомните SCD2: атрибуты dimension меняются во времени, и нужно хранить версии. В star schema это решено элегантно — SCD2 в dim_customer, fact ссылается на нужную версию. В OBT отдельной dimension нет. Атрибуты «вшиты» в строку факта, и момент записи факта фиксирует значение атрибута на тот момент. Это иногда удобно (видно состояние «как было»), но управлять историей сложнее: нет одного места, где живёт «текущая версия» клиента, и нет лёгкого способа спросить «как выглядит клиент сейчас» отдельно от заказов.
Backfill при добавлении атрибута. Это, пожалуй, главная боль OBT. В star schema добавить новый атрибут клиента — это ALTER TABLE dim_customer ADD COLUMN и заполнение одной dimension. В OBT тот же атрибут нужно дописать в каждую строку таблицы заказов — а это могут быть миллиарды строк. Такой пересчёт всей таблицы называется backfill, и он дорогой: добавление одного столбца превращается в перезапись всего OBT.
Эти два минуса — историчность и backfill — причина, по которой OBT редко используют как источник истины. Если витрина живёт долго и часто эволюционирует (новые атрибуты, меняющиеся dimensions), цена backfill и сложность с историей накапливаются. Star schema или нормализованная модель переживают эволюцию заметно легче.
Где место OBT: производная, не источник истины
Отсюда — современная позиция индустрии. OBT — это не замена размерному моделированию и не источник истины. OBT — это downstream-производная для потребления.
Правильный паттерн такой: источник истины — нормализованный или размерный слой (3NF/Data Vault на silver, star schema на gold). А OBT строится из этого слоя как финальная, удобная для чтения «материализация» под конкретные паттерны запросов или под конкретный BI-инструмент.
-- OBT не источник истины: она СОБИРАЕТСЯ из star schema
CREATE TABLE obt_orders AS
SELECT
f.order_id, f.order_date, f.amount, f.quantity,
c.customer_name, c.customer_city, c.customer_segment,
p.product_name, p.product_category, p.product_brand
FROM fct_orders f
JOIN dim_customer c ON f.customer_key = c.customer_key
JOIN dim_product p ON f.product_key = p.product_key;
JOIN выполняется один раз при сборке OBT, а не на каждый пользовательский запрос. Star schema остаётся источником истины — там удобно управлять историей и добавлять атрибуты. OBT — её быстрая проекция для чтения. Если OBT испортилась или паттерн запросов изменился — её пересобирают из star schema. Это та же логика, что у PIT/bridge в Data Vault: предвычисленный кэш поверх честного источника истины.
Так разрешается мнимое противоречие «OBT против star schema». Это не «или-или»: star schema — источник истины, OBT — производная витрина потребления поверх неё. В следующем уроке посмотрим, как dbt — стандартный инструмент трансформаций — организует эти слои.
Попробуй сам
- Возьмите star schema из модулей про размерное моделирование (
fct_orders+dim_customer+dim_product+dim_date). Спроектируйте на бумаге соответствующуюobt_orders: выпишите все столбцы. - Посчитайте дублирование: если у клиента 500 заказов, сколько раз его
customer_cityхранится в star schema и сколько — в OBT? Почему колоночное сжатие делает это не страшным? - Представьте, что нужно добавить атрибут
customer_loyalty_tier. Опишите, что это за операция в star schema и что — в OBT (вспомните слово backfill). - Сформулируйте в двух предложениях, почему OBT строят ИЗ star schema, а не наоборот.