← К содержанию навыка

Моделирование данных

20 минут
Раздел 6 из 8

6. Моделирование данных

Это самый сложный, самый важный и самый контр-интуитивный раздел. Здесь новички, пришедшие из SQL, могут наделать ошибок.

6.1. Single Table Design (STD)

  • SQL-подход: Вы создаете 4 таблицы: Users, Orders, Products, OrderItems.
  • Проблема:
    1. Производительность: Чтобы собрать страницу заказа, вашему приложению нужно сделать 4-5 запросов (Get User, Get Order, Get OrderItems, Get Products). Это медленно (5 x 5мс = 25мс).
    2. Экономика: У вас 4 таблицы. Users - read-heavy. Orders - write-heavy. Вы должны настраивать RCU/WCU для каждой из них. Это сложно и дорого.

Single Table Design - это паттерн, при котором вы храните разнородные сущности (Users, Orders) в одной физической таблице, чтобы получать их одним запросом.

Это реализуется через "перегрузку" ключей PK и SK.

  • Таблица: AppTable
  • Ключи: PK (string), SK (string)

Пример схемы (E-commerce)

PKSKtype (атрибут)data (атрибут)
USER#aliceUSER#aliceUser{ "name": "Alice", "email": ... }
USER#aliceADDR#homeAddress{ "city": "Astana", ... }
USER#aliceORDER#1001Order{ "status": "PENDING", ... }
USER#aliceORDER#1002Order{ "status": "SHIPPED", ... }
ORDER#1001ITEM#SKU123OrderItem{ "product_name": "Book", ... }
ORDER#1001ITEM#SKU456OrderItem{ "product_name": "Pen", ... }

Что это дает? Вы делаете один запрос Query(PK='USER#alice') и получаете сразу и профиль, и адреса, и историю заказов. Это невероятно быстро.

6.2. Почему STD - не серебряная пуля

Долгое время STD продавали как единственный правильный способ. Но у этого подхода есть огромная цена, о которой часто молчат.

  1. Высокий порог входа и сложность (Complexity). В SQL вы открываете таблицу Orders и видите заказы. Всё понятно. В STD вы открываете таблицу и видите "кашу" из всех данных сразу. Читать такую таблицу глазами в консоли AWS невозможно. Новому разработчику в команде (особенно джуниору) очень сложно объяснить, почему "чтобы найти пользователя, надо искать в поле PK строку, начинающуюся на ORG#, а в поле SK - дату регистрации".

  2. Жесткость схемы (Inflexibility). Это главная проблема. STD требует абсолютного подхода "Access Patterns First". Вы проектируете таблицу под конкретные экраны вашего UI.

    • Сценарий: Вы спроектировали ключи так, чтобы идеально показывать "Заказы пользователя".
    • Изменение: Бизнес приходит через полгода и говорит: "Мы меняем логику, теперь заказы привязаны не к пользователю, а к отделу компании".
    • Боль: В SQL вы бы просто поменяли Foreign Key. В STD вам, возможно, придется переписывать всю таблицу (ETL-миграция), потому что ваши Primary Keys больше не подходят под новый запрос. Single Table Design оптимизирован под скорость чтения, но он хрупок к изменениям бизнес-требований.

Почему это больнее, чем в обычном подходе?

Может показаться, что в Multi-Table подходе при смене требований тоже нужно менять схему. Но разница фундаментальна:

  • В Multi-Table (связь через атрибут): У заказа есть свой независимый ключ ORDER#123, а связь с пользователем - это просто поле user_id. Чтобы привязать заказ к отделу, вы делаете дешевый UpdateItem, меняя поле user_id на dept_id. Сама запись заказа физически остается на месте.
  • В STD (связь через ключ): Чтобы получить заказы пользователя одним запросом вместе с профилем, вы вынуждены "запекать" ID пользователя прямо в Partition Key заказа (например, PK=USER#alice, SK=ORDER#123).
  • Проблема: В DynamoDB нельзя изменить Primary Key. Вы не можете сделать UpdateItem и поменять USER#alice на DEPT#sales. Вам придется физически удалить старую запись и создать новую. Это превращает простое обновление ссылки в сложнейшую миграцию данных (ETL).
  1. Проблемы с аналитикой. Экспортировать данные в Data Lake сложнее. Если вы выгрузите таблицу в S3, аналитикам придется писать сложные фильтры: "Возьми строки, где SK начинается на ORDER#, распарси их одним способом, а строки USER# - другим".

Когда не использовать Single Table Design?

Современная рекомендация звучит так: Не объединяйте сущности, если вы не запрашиваете их вместе.

  1. GraphQL: Если у вас GraphQL API (например, AppSync), он сам умеет эффективно "склеивать" данные из разных источников. Выигрыш от STD теряется, а сложность остается.
  2. Новые проекты (MVP): На старте стартапа вы не знаете своих паттернов доступа. Они поменяются 10 раз. Использовать STD на старте - это преждевременная оптимизация. Создайте отдельные таблицы (Users, Orders). Это проще рефакторить.
  3. Несвязанные данные: Нет смысла хранить "Логи аудита" и "Профили пользователей" в одной таблице. Они никогда не запрашиваются вместе.

Прагматичный подход (Multi-Table Design)

Вам можно использовать несколько таблиц в DynamoDB. Это не грех.

  • Таблица Users (Профили).
  • Таблица Orders (Заказы и позиции заказов - здесь STD оправдан, так как заказ и его товары - это единое целое).
  • Таблица Products.

Итог: Используйте Single Table Design только там, где у вас есть доказанная потребность в атомарном получении связанных данных (Parent + Children) за один запрос, и вы готовы платить за это сложностью поддержки. Во всех остальных случаях отдельные таблицы - это нормальный выбор.

6.3. Денормализация

В университете нас учили: "Никогда не дублируй данные! Третья нормальная форма!". В мире DynamoDB это правило меняется на: "Дублируй все, что нужно, чтобы чтение было быстрым".

  • Проблема: В схеме выше у OrderItem есть product_name. Но ведь product_name может измениться!
  • Ответ: Это нормально. Заказ - это "слепок" (snapshot) на момент времени. Когда заказ ORDER#1001 создан, мы копируем product_name из PRODUCT#SKU123 в ORDER#1001. Если имя продукта изменится, в старом заказе останется старое имя. Это и есть правильное поведение.

Управление обновлениями дублированных данных

  • Проблема: У USER#alice есть name: 'Alice'. Это имя скопировано в 100 ее заказов. Алиса меняет имя на 'Alice Smith'.
  • Решение 1 (Ничего не делать): Оставить 'Alice' в старых заказах. Это часто приемлемо.
  • Решение 2 (Event-Driven):
    1. Обновление PK: USER#alice генерирует событие в DynamoDB Streams.
    2. Lambda-триггер ловит это событие.
    3. Lambda находит все связанные сущности и обновляет user_name в них. Это дорого и сложно. Используйте, только если критично необходимо.

6.4. Сначала - паттерны запросов

По-английски это называется Access Patterns First Approach. Это главное правило DynamoDB.

SQL-подход: Вы рисуете ER-диаграмму (сущности). Вы нормализуете ее. Потом вы пишете SELECT-запросы.

DynamoDB-подход:

  1. Вы садитесь с бизнес-аналитиком и сначала выписываете все запросы (Access Patterns), которые нужны вашему приложению.
  2. Нам нужно:
    • A1: Найти юзера по ID.
    • A2: Найти юзера по Email.
    • A3: Найти все заказы юзера (последние первыми).
    • A4: Найти заказ по ID.
    • A5: Найти все товары в заказе.
  3. Только теперь вы проектируете таблицу (и ее GSI), которые удовлетворяют этим 5-ти запросам.

Вы не можете "просто построить" таблицу, а потом думать, как ее запрашивать. Вы проектируете таблицу под ваши запросы.

Single Table Design и моделирование данных

Вопросов: 6

6.5. Практика

Моделирование данных и Single Table Design для конкурента Twitter

Вы продолжаете работу как CTO стартапа-конкурента Twitter. Вместе с Назаром разберите Single Table Design (преимущества и недостатки), денормализацию данных, подход "Access Patterns First", и когда лучше использовать несколько таблиц вместо одной.

Назар - ваш персональный ИИ-наставник. Он поможет закрепить материал через практику и ответит на ваши вопросы.

💡 Все обсуждения с ИИ могут быть прочитаны администратором для улучшения качества обучения.