Разработчик и сотрудник проекта CouldBoost.io Наваз Дандала (Nawaz Dhandala) написал материал о том, почему в некоторых случаях не стоит использовать MongoDB. Мы в «Латере» развиваем биллинг для операторов связи «Гидра» и уже много лет работаем с этой СУБД, поэтому решили представить и свое мнение по данному вопросу.

Дандала сразу оговаривается, что работал со многими СУБД (как SQL, так и NoSQL) и считает MongoDB отличным инструментом, однако существуют сценарии, в которых его применение нецелесообразно.

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

Если MongoDB такая классная, почему ее не используют все и всегда?


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

При создании приложения, концепция которого подразумевает работу с документами, MongoDB будет хорошим выбором. К такому типу приложений можно отнести, к примеру, движок блог-платформы, где каждый автор сможет иметь по несколько блогов, и каждый из них будет содержать множество комментариев. База данных для обслуживания такого приложения должна быть легко расширяемой, и здесь MongoDB подойдет как нельзя лучше.

Однако, необходимо отметить, что у MongoDB нет связей между документами и “коллекциями” (частично это компенсируется Database Reference — ссылками в СУБД, но это не полностью решает проблему). В итоге, возникает ситуация, при которой имеется некий набор данных, который никак не связан с другой информацией в базе, и не существует никакого способа объединить данные из различных документов. В SQL-системах это было бы элементарной задачей.

Здесь возникает другой вопрос — если в MongoDB нет связей и возможностей по объединению двух таблиц, то зачем ее тогда вообще использовать? Ответ — потому что эта СУБД отлично масштабируется, и по сравнению с традиционными SQL-системами, гораздо быстрее осуществляет чтение и запись.MongoDB прекрасно подходит для приложений, в которых практически не используются данные с зависимостями и необходима масштабируемость базы данных.

Многие разработчики применяют MongoDB и для хранения связанных данных, реализуя объединения вручную в коде — этого достаточно в сценариях «одноуровневого» объединения или малого количества связей. То есть данный метод далеко не универсален.

Так какую СУБД выбрать?


Существует огромное количество различных СУБД, и каждая из них соответствует определённому набору требований, которые разработчики предъявляют к своему приложению:

  • Документоориентированные СУБД (к примеру, MongoDB): Как уже сказано выше, документоориентированные СУБД используются для хранения JSON-документов в “коллекциях” и осуществления запросов по нужным полям. Можно использовать эту базу данных для создания приложений, в которых не будет содержаться слишком большого количества связей. Хорошим примером такого приложения является движок для блог-платформы или хранения каталога продуктов.
  • Графовые СУБД (например Neo4j): Графовая СУБД используется для хранения между субъектами, где узлы являются субъектами, а грани — связями. Например, если разработчики создают социальную сеть, и один пользователь подписывается на другого, то пользователи являются узлами, а их “подписка” — связью. Такие СУБД прекрасно справляются с образованием связей, даже если глубина таких связей более ста уровней. Этот инструмент столь эффективен, что может даже позволяет выявлять мошенничество в сфере электронной коммерции.
  • Кэш (например Redis): Такие СУБД используются, когда требуется крайне быстрый доступ к данным. Если создается приложение для интернет-торговли, в котором есть подгружаемые на каждой страницы категории, то вместо обращения к базе данных при каждом чтении, что крайне затратно, можно хранить данные в кэше. Он позволяет быстро осуществлять операции чтения/записи. Дандала советует применять СУБД, использующие кэш, в качестве оболочки для обработки часто запрашиваемых данных, избавляющей от необходимости совершения частых запросов к самой базе.
  • Поисковые СУБД (например ElasticSearch): В случае необходимости осуществления полнотекстового поиска по базе данных (например поиск продукции в ecommerce-приложении), то хорошей идее будет использование поисковой СУБД вроде ElasticSearch. Эта система способна искать по огромному массиву данных и обладает обширной функциональностью — например, СУБД умеет осуществлять поиск по именованным категориям.
  • Строковые СУБД (например Cassandra): СУБД Cassandra используется для хранения последовательных данных, логов, или огромного объема информации, который может генерироваться автоматически — к примеру, каким-нибудь датчиками. Если разработчики собираются использовать СУБД для записи больших массивов данных и при этом планируется, что будет намного меньше обращений для чтения и данные не будут иметь связи и объединения, тогда Cassandra будет хорошим выбором, уверен Дандала.

Использование комбинации баз данных




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

Например, если в приложении есть функция поиска, то его можно реализовать с помощью ElasticSearch, а уже для хранения данных без связей лучше подойдет MongoDB. Если речь иет о проекте в сфере «интернета вещей», где огромное количество всевозможных устройств и датчиков генерируют гигантские объёмы данных, вполне разумно будет использовать Cassandra.

Принцип, при котором используются несколько СУБД для работы в одном приложении, называется “Polyglot Persistence”. В этой статье можно почитать о плюсах и минусах такого подхода.

Наш опыт


Наша биллинговая система «Гидра» использует для учета первичных данных и хранения финансовой информации реляционную СУБД. Она идеально подходит для этих целей. Но некоторые модули Гидры, например, RADIUS-сервер, работают под высокой нагрузкой и могут получать тысячи запросов в секунду с жесткими ограничениями на время обработки запроса. Кроме того, в БД нашего автономного RADIUS-сервера данные хранятся в виде набора AVP (attribute/value pair). В таком сценарии реляционная СУБД уже не выглядит лучшим решением, и тут на помощь приходит MongoDB с ее хранилищем документов произвольной структуры, быстрой выдачей ответа и горизонтальной масштабируемостью.

При эксплуатации более чем на 100 инсталляциях Гидры на протяжении последних 5 лет серьезных проблем с Mongo мы не обнаружили. Но пара нюансов все же есть. Во-первых, после внезапного отключения сервера БД хоть и восстанавливается благодаря журналу, но происходит это медленно. К счастью, необходимость в этом возникает нечасто. Во-вторых, даже при небольшом размере БД редко используемые данные сбрасываются на диск и когда запрос к ним все же приходит, их извлечение занимает много времени. В результате нарушаются ограничения на время выполнения запроса.

Все это относится к движку MMAPv1, который применяется в Mongo по умолчанию. С другими (WiredTiger и InMemory) мы пока не экспериментировали — проблемы не настолько серьезны.

Комментарии (89)


  1. Labutin
    26.03.2016 20:39
    +3

    Хватит уже "раскапывать стюардессу". Какой MMAPv1 в 2016-м году?
    Вот любят же про монгу писать статьи о старых версиях.
    Лучше бы перешли на WiredTiger и написали — стало лучше или хуже. Было бы куда полезней, чем читать материал о практически устаревшем MMAPv1.


  1. AlexLeonov
    26.03.2016 21:35
    +7

    Главное, что нужно понимать, это то, что не стоит использовать принципиально нереляционную БД в ситуации, когда у вас "реляционные" данные. А это, afaik, процентов так под 90 реальных кейсов в этом жестоком мире.
    А то сначала они делают CRM на монге, а потом удивляются, почему для запроса "А покажи тех, кто зарегистрировался от полугода до года назад, живет в Москве, в последний месяц не делал заказов, а раньше делал в среднем на 1000 рублей в месяц или больше" требуется всё выкинуть, всех уволить и переписать заново...
    Зато коллекции, json и schemaless, да.


    1. gandjustas
      26.03.2016 22:40
      +5

      Данные не бывают реляционными, графовыми или еще какими-то. Реляционные они или нет — зависит от операций, об этом многие забывают. То, что сегодня укладывается в key-value запросы, завтра может потребовать джоинов, подзапросов, группировок.
      Поэтому я бы перефразировал. Стоит использовать принципиально не реляционную СУБД, только если все сценарии работы с данными укладываются и будут укладываться в будущем в выборки по ключу и получение всей коллекции. Если такой уверенности нет, то лучше сразу взять РСУБД.


      1. grossws
        27.03.2016 02:20

        Стоит использовать принципиально не реляционную СУБД, только если все сценарии работы с данными укладываются и будут укладываться в будущем в выборки по ключу и получение всей коллекции.

        В этом случае стоит использовать kv storage. Но это далеко не все кейсы, когда необходимо использовать нереляционные СУБД.
        Вопрос в подборе подходящей базы к задаче или её части. Где-то и графовые или rdf-движки нужны. А кому-то плевать на транзакции и strong consistency, но нужна хорошая горизонтальная масштабируемость и они берут кассандру, риак или ещё что-нибудь такое.


        1. gandjustas
          27.03.2016 14:13
          -1

          Еще раз. Способы обработки данных со временем меняются. На старте может казаться, что хватит и банального kv и обязательно будет 100500 миллионов пользователей и понадобится масштабирование.
          Через год оказывается, что пользователей 10000, данные спокойно укладываются в sqlite, но нужны джоины для некоторых операций и транзакции.
          Если на старте выбрать sql и правильно делать схему и запросы, то даже при 100500 миллионах пользователей приложения масштабируемость может не понадобиться.
          Графовые базы нужны в одном случае — если задача решается обходом графа. Таких задач на практике немного. Но плохая новость в том, что графовые движки, зачастую, кроме обхода графа делать ничего не умеют.
          Для обхода графа данные из базы можно просто в память затянуть. Или делать обход графа при записи, а не при запросе. Необходимость применять графовый движок для обхода есть не всегда.


          1. grossws
            27.03.2016 17:34
            +1

            Если вы не поняли, я специально процитировал ложную дихотомию из вашего предыдущего сообщения. Вы делите все случаи на те, где достаточно kv и все остальные, утверждая, что для них необходимо использовать rdbms, выкидывая из рассмотрения множество других классов баз данных со своими плюсами и минусами.


      1. mirrr
        27.03.2016 10:14
        +1

        если все сценарии работы с данными укладываются и будут укладываться в будущем в выборки по ключу и получение всей коллекции

        Эм… это точно о монге, а не о memcached или редисе? Потому как это покрывает максимум процент от возможностей mongoDB.


        1. gandjustas
          27.03.2016 13:54
          -3

          Memcached вообще не сохраняет на диск. Redis действительно используют как замену монге. Он получается и быстрее, и имеет несколько полезных фич вроде time series.
          Несмотря на все возможности монги основная операция с ней — выборка и запись по ключу. Все остальное имеет подводные камни.


          1. mirrr
            27.03.2016 16:20
            -1

            Монга это не key-value база данных вообще.


            1. gandjustas
              27.03.2016 17:04
              -1

              А я писал что монга это key-value база данных вообще?


              1. mirrr
                27.03.2016 17:29
                -1

                основная операция с ней — выборка и запись по ключу

                Выборка по ключу подразумевает наличие ключа!

                В монге выборка идет по запросу к содержимому документа.
                В запросе могут использоваться OR,AND,<,<=,>,>=,not,exists и многое другое, включая поиск глубоко в подполях и подмассивах, полнотекстовый поиск, регулярные выражения, нахождение ближайших элементов по географическим координатам и так далее. К результатам выборки применяются сортировка, distinct, пагинация, объединение с данными из другой коллекции по LEFT JOIN, получение только выбранных полей документа, сложные группировки и агрегации, получение количества элементов в выборке и многое другое.


                1. gandjustas
                  28.03.2016 03:16
                  -2

                  Все эти запросы делают полное сканирование коллекции.


                  1. mirrr
                    28.03.2016 04:11

                    Не делают, если настроены индексы для полей. Как и в SQL. Вы уделите время, ознакомьтесь с вопросом, монга это совсем не Redis.


                    1. gandjustas
                      28.03.2016 05:20
                      -3

                      Я в курсе как оно работает. Индексы в монге на порядок слабее, того что есть в любой рсубд.


                      1. mirrr
                        28.03.2016 05:46

                        Диалог в ветке шел о сравнении mongo с k/v хранилищами типа Redis, теперь вы зачем-то начинаете сравнивать монгу с реляционными базами, хотя я с ними не сравнивал. Ну допустим. В чем конкретно индексы на порядок слабее?


                        1. gandjustas
                          29.03.2016 16:47
                          -1

                          1) Не оценивается селективность индекса, используется первый подходящий, возможно не самый эффективный или даже бесполезный
                          2) Индексы должны влезать в память, чтобы быть эффективными
                          3) Нельзя сделать покрывающие multikey индексы
                          4) Про индексирование вычисляемых полей или индексированные представления молчу вообще


                          1. mirrr
                            29.03.2016 19:10

                            1) Если по полям выборки существует несколько индексов, а проверить какой используется при конкретном запросе через explain лень, то достаточно легко в самом запросе указать, какой использовать, чтобы уж точно.
                            2) Здесь, пожалуй, соглашусь. Хотя оперативка сейчас довольно дешевый ресурс, можно и всю базу зачастую вытащить, не то, что индексы. Интересен механизм работы SQL, когда индексы находятся на диске и не влезают в оперативную память, на сколько понимаю, там производительность не проседает?
                            По остальным пунктам — решение задач реляционных баз данных. Не будем же мы считать отсутствие индексов представлений недостатком, если самого понятия представлений в mongo нет.


                            1. gandjustas
                              29.03.2016 19:25
                              -1

                              1) Я видел в продакшене MongoDB на пяти разных проектах. Как ни странно, только в одном из них использовались индексы. Вообще есть тенденция, что на Mongo и другие NoSQL переходят те, кто не освоил SQL. Поэтому плотность правильного использования индексов в Монге в разы ниже, чем в РСУБД. А рассчитывать на то, что кто-то будет лезть с explain в монгу и выяснять какой там индекс, я бы вообще не стал.

                              2) Все РСУБД проектируются с рассчетом, что данные не влезают в ОП. Поэтому более-менее терпимо работают, когда индексы или данные не влезают в память. NoSQL базы обычно исходят из другого предположения — что данные и индексы будут находится в памяти в момент запроса. Если это так, то ответ отдается быстро, если нет, то давай до свидания. Монга в случае недостатка памяти тормозит очень сильно. Индексы съедают эту самую память, вытесняя данные.


                              1. mirrr
                                29.03.2016 20:18
                                +2

                                Я видел в продакшене MongoDB на пяти разных проектах. Как ни странно, только в одном из них использовались индексы.

                                Может быть они воспринимают ее только как кейс для "выборки и запись по ключу" или считают, что "все запросы делают полное сканирование коллекции"?

                                В свое время я ознакомился с большим количеством чужих проектов, в основном php+mysql. Так вот индексы присутствовали немногим чаще, чем никогда. А вроде бы не mongo. Так-что вопрос квалификации разрабов наверное не связан с кокретной бд. Хотя монга действительно значительно проще в использовании, особенно если проект на node.js, и это оправдывает многие недостатки, если учесть их в процессе проектирования.


  1. ilukyanov
    27.03.2016 00:00
    -3

    MongoDB использовать не стоит хотя бы по той причине, что до недавнего времени она не гарантировала strong consistency. База данных, которая возвращает клиенту ACK на запись, но при этом не гарантирует то, что эта запись на деле персистентна — это мусор, о ней нельзя говорить всерьез.


    1. ilukyanov
      27.03.2016 19:54
      +3

      Как качественно бомбит у сторонников Mongo. Господа, не стесняйтесь, расскажите что у вас за кейсы где вы можете потерять часть данных и это не повлечет последствий.


      1. umputun
        27.03.2016 23:38
        +4

        меня вовсе не "бомбит", однако я подозреваю, что определенная неграмотность замечания и вызвала то, что ты видишь. Совсем непонятно о чем это ты говорил, если про write concern который в какой-то древней версии несколько лет назад был по умолчанию самый низкий — ну это вполне понятно удивляет некоторой… неактуальностью. Кроме того "не гарантировала strong consistency" видимо подразумевает что сейчас оно это гарантирует? Тут тоже много удивлений может быть вызвано у читателя, т.к. во первых там все не так прямо, а во вторых речь идет о продукте, который eventually consistent с разной степенью этой "eventually".


        1. ilukyanov
          28.03.2016 11:56

          Я говорю о всем множестве «особенностей» mongo.

          1. Про write concern, когда терялись записанные и ACK'нутые данные в самом строгом режиме записи. В середине 2013 (меньше 3 лет назад) он, насколько я вижу, был еще актуален. База к тому времени существовала 4 года (и видимо эти 4 года не гарантировала совсем ничего в смысле целостности данных). 3 года это вообще не срок, у иных традиционных БД аптайм больше.

          2. Проблема eventual consistency в том, что это самое eventual может не наступить никогда. Я не зря спрашиваю про кейсы использования. По большому счету сценариев где можно взять и потерять часть данных — не так уж много. Я практически уверен, что существенная часть пользователей mongo плохо понимает этот ньюанс.

          3. Отсутствие вменяемого, доказанного алгоритма согласования данных при передаче лидерства. Даже несмотря на то, что есть определенные проблески в виде мажорити и выбора лидера. Почему написал «не гарантировала» — потому что сейчас там Tokutek то ли пишет, то ли уже написал имплементацию движка, основанную на Raft, и дела пожалуй станут несколько лучше.

          Вот хорошая ссылка по делу: https://aphyr.com/posts/284-jepsen-mongodb


          1. Scratch
            28.03.2016 13:32
            +1

            Рафт в 3.2 впилили


          1. mirrr
            28.03.2016 18:26
            +1

            3 года это вообще не срок, у иных традиционных БД аптайм больше.

            Да уж совсем не срок, за это время в mysql 15'000 баг закрыто. А сколько срок, когда уже можно использовать без write concern?


  1. burdin
    27.03.2016 00:20
    -4

    Мне кажется, всю свою историю, человечество старалось систематизировать данные которые надо хранить и обрабатывать в дальнейшем. То есть связать их и упорядочить. Реляционные СУБД подходят для этого хорошо. К тому же сейчас уже, в них, для особых случаев, добавляют элементы документоориентированных БД (поля-массивы, поля-JSON).
    Плюс, постоянно растут вычислительные и коммуникационные возможности, размеры и скорости хранилищ. Конечно, и объемы данных растут. Но кому они нужны если будут представлять собой, грубо, кучу, единственный плюс которой это возможность сравнительно задешево раскидать ее по нескольким свалкам :)


  1. stoune
    28.03.2016 11:35
    +1

    Для логов лучше исспользовать time-series db.
    Cassandra хороша когда нужна очень большая горизонтальная маштабируемость и гарантированое время записи.
    Redis — если данные хорошо ложатся в парадигму ключ-значение, по факту это кеш в памяти с дублированием на диск. Возможно к вашему решению RADIUS были дополнительные требования не указанные в заметке, но согласно описания он вам подошёл бы лучше.
    Для поднятия редко исспользуемых данных, можно реализировать сервис "прогрева", который поднимает данные с некоторой периодичностью.


  1. daneyeah
    28.03.2016 18:28
    +1

    А как можно совместить реляционные СУБД и документированные?


    1. gandjustas
      29.03.2016 16:49
      +1

      Поля типа JSON и XML в базе. Не надо думать, что документарность — какая-то супер-фича.


  1. SilverFerrum
    28.03.2016 18:31
    +1

    Я считаю, что пример с блогом не очень удачный. Если я хочу посмотреть все комментарии одного пользователя, как мне делать выборку и насколько это будет быстрым?
    Кто-то может назвать более подходящий пример использования MongoDB?
    Я сразу оговорюсь, я пользуюсь только SQL, но очень интересны случаи использования NoSQL и где это может реально пригодится. Спасибо


    1. mirrr
      30.03.2016 17:11
      -1

      Если я хочу посмотреть все комментарии одного пользователя

      db.comments.find({userID: 100500}).sort({date: -1})


      1. SilverFerrum
        30.03.2016 17:20

        Для комментариев отдельная коллекция что ли?
        Ведь подразумевается, следующая структура: User -> Posts -> Comments


        1. mirrr
          30.03.2016 19:08

          Сначала неверно истолковал вопрос, в таком случае требуется агрегация с группировкой:


          db.posts.aggregate([{
              $project: {
                  items: {
                      $filter: {
                          input: "$comments",
                          as: "comm",
                          cond: {
                              $eq: ["$$comm.userID", 10]
                          }
                      }
                  }
              }
          }, {
              $group: {
                  _id: "all",
                  messages: {
                      "$push": "$items"
                  }
              }
          }]);

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


        1. mirrr
          30.03.2016 19:57

          А вот-так даже красивее гораздо:

          db.posts.aggregate([{$unwind : "$comments"}, {$match: {"comments.userID": 10}}]);


      1. gandjustas
        30.03.2016 18:49
        -1

        1) Как вытащить пост с комментами и их авторами для страницы поста?
        2) Как вытащить пост с количеством комментов для главной?


        1. mirrr
          30.03.2016 20:18

          1)

          db.posts.aggregate([
              {$lookup: {"from": "comments", "localField": "_id", "foreignField": "postID", "as": "comments"}},  {$unwind : "$comments"},
              {$lookup: {"from": "users", "localField": "comments.userID", "foreignField": "_id", "as": "comments.user"}}, 
              {$group : { _id: "$_id", comments: {$push: "$comments"}}}
          ]);

          2)
          db.comments.aggregate([
              {$group: {_id: "$postID", count: { $sum: 1}}},
              {$lookup: {"from": "posts", "localField": "_id", "foreignField": "_id", "as": "post"}},  
          ]);


          1. gandjustas
            30.03.2016 23:13
            -2

            Мама дорогая…
            Это индексами оптимизируется?
            Насколько дружит с шардингом?
            Ссылочная целостность поддерживается?


            1. mirrr
              31.03.2016 08:00

              Оптимизируется, а средствами node.js еще и прекрасно кешируется.
              Но вообще я не специалист по монге и использую ее в более простых вещах. А в подобных случаях предпочел бы сделать два запроса вместо одного. Потому не стоит продолжать троллить и ума выведывать, не по адресу.