Документы и коллекции

Что это такое

Документ — это вложенная структура из полей и значений, адресованная ключом и хранимая в коллекции рядом с другими документами примерно такой же формы. Запрос отбирает документы по совпадению значений полей, при необходимости используя индекс по этому полю. Запись атомарна в пределах одного документа, поэтому изменение нескольких полей одной записи происходит целиком.

Почему это важно

Модель убирает две вещи, которые код приложения обычно делает вручную: чтение вложенной записи вместе с её строками, адресами и тегами укладывается в одну операцию, а не в несколько с соединением или несколькими обращениями к базе. А изменение полей одной записи атомарно без явной транзакции — обычный случай в приложении, которое обновляет одну сущность за раз. Оба свойства следуют из того, что единицей является документ, и понимание этого как раз и подсказывает, когда модель перестаёт помогать.

Ключевые идеи

Документ — единица атомарности

Всё внутри одного документа меняется вместе или не меняется вовсе, без транзакции; однако всё за его пределами — нет. Этот единственный факт решает большинство вопросов проектирования: данные, которые должны меняться вместе, принадлежат одному документу, а данные, которые не должны, можно разнести по нескольким.

один документ: заказ с его строками         -> одно атомарное обновление
два документа: заказ и его журнал аудита    -> две записи, вместе не атомарны

Встраивание сохраняет запись целиком ценой ограниченного размера

Вложение связанной структуры внутрь её родителя означает, что одно чтение возвращает всё сразу, — поэтому адрес или строки заказа уместны там. Цена: родитель растёт, существует максимальный размер документа, а запрашивать вложенные данные сами по себе неудобно. Правило — встраивать то, что читается вместе с родителем и ограничено по количеству.

Ссылки переносят связь в код приложения

Хранение идентификатора другого документа вместо его содержимого держит документы небольшими и избегает дублирования, но чтение связанных данных теперь требует второго запроса, а поддержание ссылки обеспечивает приложение; это то самое соединение, которое раньше выполнял реляционный движок, и оно переехало. Ссылки уместны, когда связанные данные велики, разделяются или обновляются независимо.

Индексы и запросы — собственный язык хранилища

Запрос сопоставляет поля и может использовать индекс, если он есть для нужных полей и порядка; без индекса же коллекция сканируется — та же цена, что и сканирование таблицы. Интерфейс запросов здесь не стандартный, поэтому привычки читать план запроса и проверять, какой индекс использован, остаются, но инструменты и словарь другие.

Проекция управляет и полезной нагрузкой, и стоимостью

Запрос может вернуть только выбранные поля, что уменьшает передаваемые байты и работу по десериализации. Для документа с несколькими большими встроенными структурами вернуть всё ради одного поля — реальная цена, а запрашивать только нужное вызывающему — та же дисциплина, что и выбор столбцов.

Обновление вложенных полей требует пути и оператора

Запись может заменить документ, изменить конкретное вложенное поле или добавить и убрать элемент массива. Операторы существуют для того, чтобы два параллельных обновления разных полей одного документа не перезаписывали друг друга целиком; замена документа вместо этого — распространённый источник потерянных обновлений.

Практика

Решите, встраивать или ссылаться

Возьмите три связи — строки заказа, адрес пользователя и организацию пользователя — и для каждой решите, встраивать её или ссылаться, объяснив причину.

Готово, когда: каждое решение говорит, ограничены ли связанные данные по количеству, разделяются ли они и обновляются ли независимо.

Покажите, почему вложенные обновления требуют операторов

Прочитайте документ в свою программу, измените одно вложенное поле и запишите документ целиком обратно, пока другая сессия меняет другое поле.

Готово, когда: вы можете описать потерянное обновление и оператор, который его предотвратил бы.

Частые ошибки

  • Встраивание неограниченного списка — документ растёт без предела и упирается в потолок размера; исправить: ссылайтесь на связанные документы, как только их количество перестанет быть ограниченным.
  • Замена всего документа ради изменения одного поля — параллельное обновление другого поля теряется; исправить: используйте оператор, меняющий только этот путь.
  • Предположение, что запись атомарна между документами — без транзакции это не так; исправить: храните данные, которые должны меняться вместе, в одном документе.
  • Запрос к полю без индекса — коллекция сканируется; исправить: добавьте индекс и проверьте план.
  • Возврат всего документа ради одного поля — нагрузка и работа растут вместе с встроенными данными; исправить: проецируйте только нужные поля.

Что читать дальше