Почему вы никогда не должны говорить «никогда»

в 8:28, , рубрики: mongodb, never say never again, nosql, разработка

Эта моя публикация чуть более чем полностью является ответом на перевод статьи «Почему вы никогда не должны использовать MongoDB». Статья, которая, по сути, рекомендует держаться подальше от MongoDB, является самой заплюсованной в хабе. И это звучит как приговор. Поэтому логично либо хаб закрыть и больше никогда не читать, либо написать ещё более рейтинговое опровержение. Конечно же, я выбрал второй вариант, рискуя своим рейтингом и кармой (ввиду крайней холиварности в комментах).

image

Картинка самоиронии

Выбор базы данных

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

Но какие есть альтернативы? Некоторые утверждаю что графовые базы данных подходят лучше всего, но я не буду их рассматривать, так как они слишком нишевые для массовых проектов. Другие говорят что документарные идеально подходят для социальных данных, и они достаточно мейнстримные для реального применения. Давайте рассмотрим почему люди считают, что для социальных данных гораздо лучше подходит MongoDB, а не PostgreSQL.

Утверждение о том, что графовые базы слишком нишевые (???) для массовых проектов (???), работающих с социальными графами, очень сложно обосновать даже самым авторитетным источником. По своему опыту могу сказать, что графовые базы, несмотря на их название, отлично работают с графами (в том числе, с социальными графами). Конечно, не стоит брать графовые базы в качестве основного хранилища данных. Для этих целей незаменимыми остаются такие инструменты, как MySQL, MongoDB, Postgres, etc. Точно также, когда нам нужен, например, полнотекстовый поиск, мы используем Sphinx в связке с основным хранилищем данных, а не делаем мучительный выбор между ним, Redis и MariaDB.

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

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

Мы могли бы также моделировать эти данные в виде набора вложенных объектов (набор пар ключ-значение). Множество информации о конкретном сериале это одна большая структура вложенных наборов ключ-значение. Внутри сериала, есть множество сезонов, каждый из которых также объект (набор пар ключ-значение). В пределах каждого сезона, массив эпизодов, каждый из которых представляет собой объект, и так далее. Так в MongoDB моделируют данные. Каждый сериал является документом, который содержит всю информацию, об одном сериале.

У сериала есть название и массив сезонов. Каждый сезон — объект с метаданными и массивом эпизодов. В свою очередь каждый эпизод имеет метаданные и массивы отзывов и актеров.

Если бы Сара прочитала руководство по моделированию данных в MongoDB, то она бы знала, что в этой базе можно работать чуть больше чем с одной моделью данных. Но на самом деле у меня есть один очень простой вопрос: автор в руках калькулятор когда-нибудь держала? Про Google и Wikipedia спрашивать даже не буду. К чему это я? Давайте загуглим самый длинный сериал. Этот шедевр, под названием Направляющий свет, содержит 18.262 серии разнообразия и философии. На его фоне, Санта-Барбара с её 2.137 сериями — белый карлик на звёздном небе киноискусства. Но это всё лирика. Теперь я снова становлюсь занудой и утверждаю, что перед тем, как авторитетно сделать выбор базы данных, надо не только ознакомиться с рекомендуемыми моделями хранения данных, но и с ограничениями этой базы. Одно из таких ограничений — это максимальный размер документа, который не может превышать 16 Мб. А теперь берём в руки калькулятор, держимнероняем и производим магический расчёт, который я вычитал в книге заклинаний DBA Architect:

16 Мб / 18.262 серии = 876 // Байт на серию

Восемьсот семьдесят шесть байт на хранение каждой серии. Да этого не хватит даже на краткое описание того, что было в предыдущей серии! А нам же ещё в этот самый документ надо вместить список актёров, рецензии (!!!) и ещё бог весть что. Нет, серьёзно, автор об этом хоть на минуту задумывалась? В принципе, одного этого просчёта уже достаточно, чтобы понять уровень критикуемой мною статьи.

C позволения читателя, я покритикую ещё пару-тройку утверждений автора.

После того, как мы начали делать уродливые джоины вручную в коде Diaspora, мы поняли что это только первая ласточка проблем. Это был сигнал что наши данные на самом деле реляционные, что существует ценность в этой структуре связей и мы двигаемся против базовой идеи документарных СУБД.

На самом деле для меня загадка, как автору статьи раз за разом удаётся идти по наихудшему из всех возможных сценариев. То все данные пытается уместить в одном документе, не удосужившись просчитать элементарные ограничения. То берёт «best practice» из мутной Diaspora, печально известной лишь самоубийством её сооснователя, а теперь вот ещё и уродливыми джоинами. Почему бы просто не прочитать, что думают об использовании джоинов в официальной документации? Ведь там в самом первом предложении чёрным по белому написано, что джоины не поддерживаются, точка. Попытка навернуть свои джоины оживляют в моей голове яркий образ дорогих коллег с берегов великой реки Ганг. Они смотрят на меня из Skype своими добрыми глазами и говорят, что реализовали часть функционала корзины прямо в ядре фреймворка, потому что так было быстрее и проще. Святой Шива! Но ведь это не повод винить фреймворк, не так ли?

В то же время, отсутствие джоинов не лишает разработчика возможности работать с несколькими документами. Если документы хранятся в одной коллекции, то вы можете делать очень функциональные и выразительные запросы, используя Aggregation Framework. Можно, например, легко получить даже такую весьма экзотическую метрику, как среднее число фильмов, в которых снялись актёры с именем Sisi:

db.actors.aggregate( [
   { $match : { name: "Sisi" } }
   { $group : { _id : "$movieId", count: { $sum : 1 } } },
   { $group : { _id : null, avgMovieCount : { $avg : "$count" } } }
] )

Если вам необходимо подтянуть связанные данные, то это делается отдельным запросом. И этот подход не уступает в эффективности сложному запросу с джоинами и используется во всех официальных MongoDB ORM и ODM. Главное преимущество отдельных запросов в том, что связанные данные запрашиваются только по мере необходимости (lazy loading). Если же вам нужно делать совершенно хардкорные запросы, в том числе между разными коллекциями, то на помощь приходит MapReduce. Но лично в моей практике, таких запросов на всё приложение могло насобираться максимум несколько штук.

После трех месяцев в разработке все прекрасно работало с MongoDB. Но однажды в понедельник на планерке клиент сказал, что один из инвесторов хочет новую фичу. Он хочет иметь возможность кликнуть на на имя актера и посмотреть его карьеру в телесериалах. Он хочет список всех эпизодов во всех сериалах в хронологическом порядке, в которых этот актер снимался.

На дворе Web 3.0, но Сара на полном серьёзе проектировала систему таким образом, что желание клиента кликнуть на имя автора и увидеть список сериалов повергло её в натуральный шок. И на самом деле, это абсолютно закономерная расплата за все те просчёты, которые были допущены ранее. Расплата за нежелание читать официальную документацию и рекомендации.

Хорошо. Но как насчет социальных данных?

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

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

Из комментариев

Для полноты картины, я также решил включить в свою статью ответы на пару самых рейтингов комментариев с конструктивной критикой в адрес MongoDB.

Комментарий от cleg:

и потом банальная задача «удалить сериал» при отсутствии поддержки целостности превращается в многоступенчатый процесс:
— удаляем все связанные записи из коллекции с сериями
— удаляем все связанные записи из коллекции с отзывами
— удаляем запись из коллекции сериалов

а если записей много, и где-то этот процесс навернулся — у нас остается неконсистентность

Удалить сериал? Это как? Все его копии сожгли, а актёров отправили на Альфа Центавра? Какой реальный кейс удаления сериала? Даже если таковой и найдётся, то удалять ничего не надо, достаточно пометить сериал как «удалённый». И такой подход применяется в любой крупной системе. Более того, на некоторых проектах, в разработке которых я принимал участие, было требование не только не удалять данные физически, но и фиксировать все изменения. Если и разрешать удаление чего-либо в сервисе сериалов, то разве что комментариев к обзорам. Да и то, наш всеми любимый Хабр не имеет даже такой возможности и позиционирует это как фичу. Объяснить такую ситуацию с удалениями можно, во-первых, ценностью данных, а во-вторых тем, что в том же MySQL, на больших объёмах, с шардированием и реплицированием, каскадное удаление работает далеко не так гладко, как может показаться.

Комментарий от gandjustas:

Это призрачное преимущество. В программе все равно будет схема (типы), в живом приложении взять и поменять схему не выйдет. Прямо в статье есть пример — были сериалы, где актеры включены в документы сериалов. Потом понадобилось поменять схему — вытащить сущность актеров в отдельные документы.

Как отсутствие схемы помогло? Похоже что никак.

Совершенно согласен. Отсутствие схемы в базе не избавляет от необходимости продумывать модели хранения данных на этапе проектирования приложения. Это действительно абсолютно неверное понимание schemaless, к сожалению, весьма распространённое. И печальный опыт Сары — очередное тому подтверждение. Единственное, что даёт отсутствие схемы в базе — это избавление от необходимости дублирования это самой схемы в коде вашего приложения, в моделях данных. И лично мне это очень по вкусу. Например, зачем указывать и в базе и в правилах валидации модели пользователя, что имя — это строка с максимальной длиной 100 символов? MongoDB позволяет описывать модели данных только в приложении, не заставляя переносить часть этих правил в схему базы. И это всё, что даёт schemaless. MongoDB не умеет магическим образом нивелировать ошибки разработчика при проектировании моделей данных. Увы.

Заключение

В завершение хочу ещё раз подчеркнуть, что нет смысла винить в отбитом пальце молоток, а в протекающем компоте вилку. Вместо этого лучше правильно использовать правильные инструменты. А для этого надо читать документацию и изучать официальные best practice. На этом у меня всё. Всем добра и шардинга из коробки.

Автор: uaoleg

Источник

* - обязательные к заполнению поля


https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js