Деление данных по коллекциям в MongoDB
Опубликовано 21 December 2015 в Разное
Не надо лезть в MongoDB с реляционным подходом. Этот тезис вроде бы очевиден, но когда дело доходит до реализации, то возникает множество вопросов. И 100% одним из них будет "как делать join?" Правильный ответ: никак. Да, в 3.2 появился $lookup в агрегациях. Отчасти это замена классическому join из реляционного мира. Но в целом агрегации не самый быстрый в монге инструмент. Лучше когда запрос идет к одной коллекции и это обычный find.
Стоит отметить, что использование NoSQL не обходится бесплатно. К примеру, обеспечение консистентности данных встроено в реляционные базы по умолчанию. При использовании MongoDB, задача обеспечить консистентность ложится на программу-клиент. Будет лучше посмотреть как это происходит на примере с постом в блог. Рассмотрим сначала как его представить в обычной реляционной БД в нормализованном виде, а потом разные варианты представления постов в MondoDB.
Предположим, что нам нужно решить следующие задачи:
- выводить список постов с заголовками, категорией и тегами
- выводить отдельный пост
- выводить список тегов
- выводить список категорий
- редактировать имена тегов и категорий
В реляционной базе, скорее всего, структура БД будет примерно такой:
- posts
- id
- title
- body
- author_id
- category_id
- slug
- authors
- id
- name
- categories
- id
- title
- slug
- tags
- id
- name
- slug
- posts_tags
- post_id
- tag_id
Есть ряд вариантов как организовать документы с постами в монге.
Можно хранить данные в одной коллекции:
- posts
- _id
- title
- body
- author
- category
- title
- slug
- tags
- [
- name
- slug
- ]
- slug
Такая структура хороша тем, что список постов и вывод отдельного поста решается одним запросом. Мы сразу получим все необходимы данные. Список тегов и категорий придется строить с помощью агрегаций. Если их количество не велико, то можно результаты агрегаций просто хранить в памяти или кэшировать. Редактировать имена тегов и категорий можно достаточно простыми апдейтами. Что при наличии индексов сделает эту операцию относительно быстрой.
Такой подход подойдет для относительно небольшой коллекции. На большом количестве документов операции обновления начнут заметно тормозить. К тому же, хоть в данном примере это и маловероятно, если документы у вас сложные и содержат много данных, то можно упереться в предельный размер документа монги. На "живой" базе разрывать документы по отдельным коллекциям весьма сомнительное удовольствие.
Второй вариант - вынести данные тегов и категорию в отдельные коллекции, посты хранят только id тегов. По сути сделать обычную нормализацию немного в монговском стиле: для связи many-to-many тегов и постов не делаем отдельную коллекцию, а храним в постах список id тегов.
Коллекции в этом случае буду выглядеть так:
- categories
- _id
- title
- slug
- tags
- _id
- name
- slug
- posts
- _id
- title
- body
- author
- category_id
- tags
- [tag_id]
- slug
Отлично. Проблему с редактированием категорий и тегов решили. Теперь эта операция занимает константное время. Построить список тегов и категорий не проблема. Но бесплатного ничего не бывает. Теперь для построения поста нам надо сделать 3 запроса: получить сам пост, получить категорию, получить теги. Аналогичные проблемы придется решать при построении списка постов.
Этот способ подойдет тогда, когда имена категорий и тегов меняются часто, но их количество не очень велико. При этом нет жестких требований к скорости отдачи постов на чтение.
Можно построить еще такую гибридную схему:
- categories
- _id
- title
- slug
- tags
- _id
- name
- slug
- posts
- _id
- title
- body
- author
- category
- category_id
- title
- slug
- tags
- [
- tag_id
- name
- slug
- ]
- slug
По сути, мы добавляем поля, которых нам не хватало во втором варианте, что бы отдавать пост за один запрос. При этом возникают проблемы с редактированием: нужно не только поменять тег в коллекции тегов, но и пройтись по постам и выправить тег там. Но в отличие от первого варианта, в этом легче написать код, который будет выправлять посты асинхронно в фоновом режиме.
Пример с блогом очень искусственный (я бы вообще 10 раз подумал прежде, чем делать это не на реляционной базе), но даже на нем видно, что все подходы к разделению данных по коллекциям требуют написания какого-то количества дополнительного кода. И правильно оценив свою предметную область и объемы данных, можно прикинуть где кода будет меньше, он будет проще и не будет провалов по производительности.
PS Понимаю, что я не то что Капитан Очевидность, а даже адмирал Ясен Пень. Но подобные вопросы с MongoDB (да и в целом с NoSQL) возникают с завидной регулярностью.
Возник вопрос? Мне всегда можно написать в Twitter: avkorablev