Документы и коллекции
Что это такое
Документ — это вложенная структура из полей и значений, адресованная ключом и хранимая в коллекции рядом с другими документами примерно такой же формы. Запрос отбирает документы по совпадению значений полей, при необходимости используя индекс по этому полю. Запись атомарна в пределах одного документа, поэтому изменение нескольких полей одной записи происходит целиком.
Почему это важно
Модель убирает две вещи, которые код приложения обычно делает вручную: чтение вложенной записи вместе с её строками, адресами и тегами укладывается в одну операцию, а не в несколько с соединением или несколькими обращениями к базе. А изменение полей одной записи атомарно без явной транзакции — обычный случай в приложении, которое обновляет одну сущность за раз. Оба свойства следуют из того, что единицей является документ, и понимание этого как раз и подсказывает, когда модель перестаёт помогать.
Ключевые идеи
Документ — единица атомарности
Всё внутри одного документа меняется вместе или не меняется вовсе, без транзакции; однако всё за его пределами — нет. Этот единственный факт решает большинство вопросов проектирования: данные, которые должны меняться вместе, принадлежат одному документу, а данные, которые не должны, можно разнести по нескольким.
один документ: заказ с его строками -> одно атомарное обновление
два документа: заказ и его журнал аудита -> две записи, вместе не атомарны
Встраивание сохраняет запись целиком ценой ограниченного размера
Вложение связанной структуры внутрь её родителя означает, что одно чтение возвращает всё сразу, — поэтому адрес или строки заказа уместны там. Цена: родитель растёт, существует максимальный размер документа, а запрашивать вложенные данные сами по себе неудобно. Правило — встраивать то, что читается вместе с родителем и ограничено по количеству.
Ссылки переносят связь в код приложения
Хранение идентификатора другого документа вместо его содержимого держит документы небольшими и избегает дублирования, но чтение связанных данных теперь требует второго запроса, а поддержание ссылки обеспечивает приложение; это то самое соединение, которое раньше выполнял реляционный движок, и оно переехало. Ссылки уместны, когда связанные данные велики, разделяются или обновляются независимо.
Индексы и запросы — собственный язык хранилища
Запрос сопоставляет поля и может использовать индекс, если он есть для нужных полей и порядка; без индекса же коллекция сканируется — та же цена, что и сканирование таблицы. Интерфейс запросов здесь не стандартный, поэтому привычки читать план запроса и проверять, какой индекс использован, остаются, но инструменты и словарь другие.
Проекция управляет и полезной нагрузкой, и стоимостью
Запрос может вернуть только выбранные поля, что уменьшает передаваемые байты и работу по десериализации. Для документа с несколькими большими встроенными структурами вернуть всё ради одного поля — реальная цена, а запрашивать только нужное вызывающему — та же дисциплина, что и выбор столбцов.
Обновление вложенных полей требует пути и оператора
Запись может заменить документ, изменить конкретное вложенное поле или добавить и убрать элемент массива. Операторы существуют для того, чтобы два параллельных обновления разных полей одного документа не перезаписывали друг друга целиком; замена документа вместо этого — распространённый источник потерянных обновлений.
Практика
Решите, встраивать или ссылаться
Возьмите три связи — строки заказа, адрес пользователя и организацию пользователя — и для каждой решите, встраивать её или ссылаться, объяснив причину.
Готово, когда: каждое решение говорит, ограничены ли связанные данные по количеству, разделяются ли они и обновляются ли независимо.
Покажите, почему вложенные обновления требуют операторов
Прочитайте документ в свою программу, измените одно вложенное поле и запишите документ целиком обратно, пока другая сессия меняет другое поле.
Готово, когда: вы можете описать потерянное обновление и оператор, который его предотвратил бы.
Частые ошибки
- Встраивание неограниченного списка — документ растёт без предела и упирается в потолок размера; исправить: ссылайтесь на связанные документы, как только их количество перестанет быть ограниченным.
- Замена всего документа ради изменения одного поля — параллельное обновление другого поля теряется; исправить: используйте оператор, меняющий только этот путь.
- Предположение, что запись атомарна между документами — без транзакции это не так; исправить: храните данные, которые должны меняться вместе, в одном документе.
- Запрос к полю без индекса — коллекция сканируется; исправить: добавьте индекс и проверьте план.
- Возврат всего документа ради одного поля — нагрузка и работа растут вместе с встроенными данными; исправить: проецируйте только нужные поля.
Что читать дальше
- MongoDB (05-backend-data/18-mongodb) — хранилище, которому принадлежит эта модель, поэтому читайте его первым.
- Проектирование схемы в хранилище без схемы (05-backend-data/18-mongodb/02-schema-design-in-a-schemaless-store) — как эти решения о моделировании принимаются для всей коллекции.
- Документы и коллекции (05-backend-data/18-mongodb/01-documents-and-collections) — этот дочерний урок, а следующая запись строится на нём.