Базы данных

Data Modeling: от концепции до физики

2015 год: стартап добавил EAV таблицу для 'гибкого' хранения атрибутов продуктов. Через 3 года: 50 миллионов строк в product_attributes, запрос списка товаров занимает 8 секунд, команда боится менять схему. Это называется схемный долг. Правильное data modeling в начале стоит дня работы, неправильное - месяцев рефакторинга.

  • **GitHub**: переход с UUID на BIGINT PK для ключевых таблиц - 30-50% улучшение insert performance
  • **Twitter**: денормализованные followers_count/friends_count в user profile - мгновенное чтение без COUNT JOIN
  • **Instagram**: JSONB для гибких метаданных медиа - schema flexibility без EAV антипаттерна

Концептуальная модель данных

Концептуальная модель - высокоуровневое представление бизнес-сущностей и их связей без технических деталей. Используется для коммуникации с бизнесом, не для реализации. Основные инструменты: ER-диаграмма (Entity-Relationship), Domain Model. На этом уровне: только сущности, атрибуты и кардинальность связей (1:1, 1:N, M:N).

Uber Domain Model разбит на поддомены: Trip (поездка), Driver (водитель), Rider (пассажир), Payment (оплата), Map (маршруты). Каждый поддомен моделируется независимо, потом связи между ними - это Bounded Context из Domain-Driven Design (DDD).

Что НЕ входит в концептуальную модель данных?

Логическая модель: нормализация

Логическая модель - детализация концептуальной: добавляются primary keys, foreign keys, нормализация (устранение дублирования), но без привязки к конкретной СУБД. M:N связи превращаются в junction tables. Опциональные атрибуты могут выноситься в отдельные таблицы.

Обратите внимание на unit_price в ORDER_ITEMS: нельзя хранить только ссылку на PRODUCTS.price. Цены меняются, а исторический заказ должен хранить цену на момент покупки. Это денормализация по бизнес-причине, не техническая.

Зачем хранить unit_price в ORDER_ITEMS, если цена есть в PRODUCTS.price?

Физическая модель: оптимизация для СУБД

Физическая модель - логическая модель плюс специфика конкретной СУБД: типы данных, индексы, партиционирование, tablespaces, storage parameters. На этом уровне принимаются решения которые влияют на производительность: UUID vs BIGSERIAL, JSONB vs отдельные колонки, индексы на foreign keys, partial indexes.

GitHub перешёл с UUID PK на BIGINT (auto-increment) для основных таблиц, сохранив UUID как публичный идентификатор. Причина: UUID v4 random - плохой clustered index ключ (фрагментация). BIGSERIAL монотонно возрастает - лучше для B-Tree insert performance.

Почему UUID v4 как PRIMARY KEY в PostgreSQL хуже BIGSERIAL для производительности?

Денормализация: осознанный компромисс

Денормализация - намеренное добавление дублирования данных для ускорения чтения. После нормализации данные консистентны но запросы требуют join. После денормализации - быстрые reads но сложнее поддерживать консистентность при обновлениях. Правило: нормализовать по умолчанию, денормализовать только когда профайлер показывает проблему.

Twitter хранит followers_count и friends_count как денормализованные счётчики в таблице users - читается при каждом запросе профиля. Instagram делает то же для likes_count на постах. Это acceptable денормализация: счётчики обновляются атомарно при каждом follow/like.

Денормализация добавила post_count в таблицу users. Какой риск появился?

Антипаттерны проектирования схемы

Антипаттерны data modeling - повторяющиеся ошибки в проектировании схемы, которые создают проблемы при масштабировании. Наиболее опасны: EAV (Entity-Attribute-Value), Polymorphic Association, хранение списков в строке, God Table с сотнями колонок.

WordPress использует EAV паттерн (wp_postmeta таблица) для кастомных полей - это исторически известная точка боли при масштабировании. Magento (e-commerce) тоже использует EAV для атрибутов продуктов и столкнулся с serious performance issues при тысячах продуктов. Обе системы предлагают способы обойти EAV через специальные индексные таблицы.

EAV (Entity-Attribute-Value) - гибкое и удобное решение для хранения произвольных атрибутов

EAV жертвует type safety, индексируемостью и производительностью ради flexibility; лучшие альтернативы: JSONB, отдельные таблицы по типу сущности, или специализированные document databases

EAV запросы требуют complex JOIN и CAST - медленны и трудночитаемы. Нет проверки типов: can store 'abc' в numeric поле. PostgreSQL JSONB даёт ту же flexibility с GIN индексами, schema validation через CHECK constraints, и нативными операторами.

Почему хранить теги как 'python,javascript,database' в одной VARCHAR колонке - антипаттерн?

Итоги

  • **Три уровня** - концептуальный (бизнес-сущности), логический (нормализация + FK), физический (типы, индексы, партиции)
  • **Денормализация** - намеренное дублирование для ускорения чтения; всегда с триггером или application logic для поддержания консистентности
  • **Антипаттерны** - EAV, списки в строке, polymorphic FK без ссылочной целостности; предпочитать JSONB или нормализованные таблицы

Связанные темы

Data modeling - фундамент для всех остальных тем курса:

  • Нормализация — Логическая модель строится на нормальных формах - 1NF, 2NF, 3NF устраняют аномалии данных
  • Индексы B-Tree — Физическая модель определяет какие индексы создавать - compound index порядок определяется паттернами запросов
  • Партиционирование — Физическая модель включает решение о партиционировании по времени или другому ключу для больших таблиц

Вопросы для размышления

  • Интернет-магазин продаёт электронику и одежду - у них очень разные атрибуты. Как смоделировать без EAV антипаттерна?
  • Денормализованный счётчик post_count расходится с реальностью из-за бага. Как написать reconciliation query который находит расхождения?
  • UUID v7 (IETF стандарт 2024) содержит timestamp в начале и монотонно возрастает. Решает ли это проблему фрагментации UUID v4 при использовании как PK?

Связанные уроки

  • stat-09-regression
Data Modeling: от концепции до физики

0

1

Войти