Нормализация против денормализации
Урок противопоставляет два стиля проектирования таблиц и связывает их с типом нагрузки.
Нормализация — разбиение данных на таблицы без дублей. Денормализация — сознательное дублирование данных ради скорости чтения.
Два мира: OLTP и OLAP
В транзакционных системах (OLTP — заказы, платежи) важна целостность: каждый факт хранится один раз, чтобы не было противоречий. Это нормализация. В аналитических системах (OLAP — отчёты) важна скорость чтения больших объёмов, и ради неё данные денормализуют — дублируют контекст прямо в строках.
| Свойство | Нормализация (OLTP) | Денормализация (OLAP) |
| Цель | целостность, нет дублей | скорость чтения |
| Запись | частая, быстрая | пакетная |
| Чтение | точечное | массовые агрегаты |
| JOIN-ов | много | мало |
Как работает под капотом
Покажем разницу на одном примере. Нормализованный вариант: заказы и клиенты в отдельных таблицах, имя клиента — только в customers.
CREATE TABLE customers (id INTEGER PRIMARY KEY, name TEXT, city TEXT);
CREATE TABLE orders (id INTEGER PRIMARY KEY, customer_id INTEGER, amount INTEGER);
INSERT INTO customers VALUES (1,'Анна','Москва'), (2,'Борис','Казань');
INSERT INTO orders VALUES (1,1,300), (2,1,500), (3,2,700);
-- чтобы получить город по заказам, нужен JOIN
SELECT c.city, SUM(o.amount) AS revenue
FROM orders o
JOIN customers c ON c.id = o.customer_id
GROUP BY c.city;Денормализованный вариант для аналитики: город уже лежит в строке заказа, JOIN не нужен.
CREATE TABLE orders_flat (id INTEGER PRIMARY KEY, city TEXT, amount INTEGER);
INSERT INTO orders_flat VALUES (1,'Москва',300), (2,'Москва',500), (3,'Казань',700);
SELECT city, SUM(amount) AS revenue
FROM orders_flat
GROUP BY city;Оба запроса дают одинаковый ответ, но второй проще: нет JOIN, движку не нужно сопоставлять таблицы по ключу. На трёх строках разница незаметна, а на сотнях миллионов JOIN превращается в дорогую операцию, и денормализация экономит секунды, а то и минуты на каждом запросе аналитика. Поэтому витрины для дашбордов почти всегда денормализованы — их строит конвейер один раз за ночь, а читают потом тысячи раз за день.
Чем платим за денормализацию
Дублирование означает, что при изменении (Анна переехала) нужно обновить много строк. Поэтому денормализованные таблицы строит конвейер — он пересобирает их из нормализованных источников по расписанию, и проблема обновления решается перестроением витрины.
Ключевая мысль: нормализация и денормализация — не «правильно» и «неправильно», а инструменты под разные задачи. Транзакционная база приложения (где Анна реально меняет город в профиле) должна быть нормализованной, чтобы не было противоречий. Аналитическая витрина (где мы считаем выручку по городам) выигрывает от денормализации. Дата-инженер живёт на стыке: он читает нормализованный источник и строит из него денормализованную витрину — этим и занимается шаг Transform в ETL.
Частые ошибки
- Денормализовать OLTP-базу приложения. Дубли в транзакционной базе ведут к рассинхрону и багам; там нужна нормализация.
- Гнаться за «третьей нормальной формой» в аналитике. Идеально нормализованная аналитическая модель медленна из-за JOIN-ов.
- Обновлять денормализованную витрину точечно. Её проще целиком пересобрать конвейером, чем латать отдельные строки.
Итог
- Нормализация (OLTP) хранит факт один раз ради целостности.
- Денормализация (OLAP) дублирует контекст ради скорости чтения и меньшего числа JOIN-ов.
- Денормализованные витрины пересобирает конвейер по расписанию.