Почему вы никогда не должны использовать MongoDB

http://www.sarahmei.com/blog/2013/11/11/why-you-should-never-use-mongodb/
  • Перевод
Дисклеймер от автора (автор — девушка): Я не разрабатываю движки баз данных. Я создаю веб-приложения. Я участвую в 4-6 разных проектах каждый год, то есть создаю много веб-приложений. Я вижу много приложений с различными требованиями и различными потребностями хранения данных. Я разворачивала большинство хранилищ, о которых вы слышали, и несколько, о которых даже не подозреваете.

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

Проект


Diaspora — это распределенная социальная сеть с долгой историей. Давным давно, в 2010 году, четыре студента Нью-Йоркского университета опубликовали на Kickstarter видео с просьбой пожертвовать $10,000 для того разработать распределенную альтернативу Facebook. Они отправили ссылку друзьям, семье и надеялись на лучшее.

Но они попали в самую точку. Только что отгремел очередной скандал из-за приватности на Facebook, и когда пыль улеглась они получили $200,000 инвестиций от 6400 человек для проекта, в котором еще не было написано ни одной строки кода.

Diaspora была одним из первых проектов на Kickstarter, которым удалось значительно превысить цель. Как результат, о них написали в газете New York Times, что обернулось скандалом, потому что на доске на фоне фотографии команды была написана неприличная шутка, но никто этого не заметил, пока фотографию не напечатали… в New York Times. Так я и узнала об этом проекте.

В результате успеха на Kickstarter парни бросили учиться и переехали в Сан-Франциско, чтобы начать писать код. Так они оказались в моем офисе. В то время я работала в Pivotal Labs и один из старших братьев разработчиков Diaspora тоже работал в этой компании, поэтому Pivotal предложили парням рабочие места, интернет, и, конечно, доступ к холодильнику с пивом. Я работала с официальными клиентами компании, а по вечерам зависала с парнями и писала код по выходным.

Закончилось тем, что они оставались в Pivotal более двух лет. Тем не менее, к концу первого лета у них был минимальная, но работающая (в некотором смысле) реализация распределенной социальной сети на Ruby on Rails, использующая MongoDB для хранения данных.

Довольно много баззвородов — давайте разберемся.

Распределенная социальная сеть


Если вы видели Facebook, то вы знаете все, что вам надо знать о Facebook. Это веб-приложение, оно существует в единственном экземпляре и позволяет вам общаться с людьми. Интерфейс Diaspora выглядит почти также, как Facebook.
image
Лента сообщений посередине показывает посты всех ваших друзей, а вокруг нее куча мусора, на который никто не обращает внимания. Основное отличие Diaspora от Facebook невидимо для пользователей, это «распределенная» часть.

Инфраструктура Disapora не располагается за одним веб-адресом. Есть сотни независимых экземпляров Diaspora. Код открыт, поэтому вы можете развернуть свои серверы. Каждый экземпляр, называется Стручок. Он имеет свою базу данных и свой набор пользователей. Каждый Стручок взаимодействует с другими Стручками, у которых так же своя база и свои пользователи.
image

Стручки общаются с помощью API на основе протокола HTTP (сейчас это модно называть REST API — прим. пер.). Когда вы развернули свой Стручок, он будет довольно скучным, пока вы не добавите друзей. Вы можете добавлять в друзья пользователей в вашем Стручке, или в других Стручках. Когда кто-либо что-либо опубликует произойдет вот что:
  1. Сообщение сохранится в базе данных автора.
  2. Ваш Стручок будет оповещен через API.
  3. Сообщение сохранится в базе данных вашего Стручка.
  4. В вашей ленте вы увидите сообщение вместе с сообщениями от других друзей.

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

Кого это волнует?


Есть технические и юридические преимущества этой архитектуры. Основным техническим преимуществом является отказоустойчивость.
image
(такую отказоустойчивую систему надо иметь в каждом офисе)

Если один из Стручков падает, то все остальные продолжают работать. Система вызывает, и даже ожидает, разделение сети. Политические следствия этого — напрмиер если в вашей стране закрывают доступ к Facebook или Twitter, ваш локальный Стручок будет доступен другим людям в вашей стране, даже если все остальные будут недоступны.

Основное юридическое преимущество — это независимость серверов. Каждый Стручок- юридически независимая сущность, управляемая законами той страны, где расположен Стручок. Каждый Стручок также может устанавливать свои условия, на большинстве вы не отдаете права на весь контент, как например на Facebook или Twitter. Diaspora — свободное программное обеспечение, как в смысле «даром», так и в смысле «независимо». Большинство тех, кто запускает свои Стручки, это очень заботит.

Такова архитектура системы, давайте рассмотрим архитектуру отдельного Стручка.

Это Rails приложение.


Каждый Стручок это Ruby on Rails приложение со своей базой на MongoDB. В некотором смысле это «типичное» Rails приложение — оно имеет пользовательский интерфейс, программный API, логику на Ruby и базу данных. Но во всех остальных смыслах оно совсем не типичное.
image
API используется для мобильных клиентов и для «федерации», то есть для взаимодействия между Стручками. Распределенность добавляет несколько слоев кода, которые отсутствуют в типичном приложении.

И, конечно, MongoDB — далеко не типичный выбор для веб-приложений. Большинство Rails приложений используют PostgreSQL или MySQL. (по состоянию на 2010 год — прим. пер.)

Так вот код. Рассмотрим, какие данные мы храним.

Я не думаю, что это слово означает, что вы думаете, что это означает


«Социальные данные» — это информация о нашей сети друзей, их друзей и их деятельности. Концептуально мы думаем об этом как о сети — неориентированном графе, в котором мы находимся в центре, и наши друзья находятся вокруг нас.
image
(Фотографии с rubyfriends.com. Благодаря Мэтт Роджерс, Стив Klabnik, Нелл Shamrell, Катрина Оуэн, Сэм Ливингстон-серый, Джош Сассер, Акшай Khole, Прадьюмна Dandwate и Хефзиба Watharkar за вклад в # rubyfriends!)

Когда мы храним социальные данные, мы сохраняем как топологию, так и действия.

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

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

Как MongoDB хранит данные


MongoDB — это документарная база данных. Вместо хранения данных в таблицах, состоящих из отдельных строк, как в реляционных базах, MongoDB сохраняет данные в коллекциях, состоящих из документов. Документ — это большой JSON объект без заранее определенного формата и схемы.

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

Эти данные можно смоделировать несколькими способами. В типичном реляционном хранилище, каждый из прямоугольников будет таблицей. У вас будет таблица tv_shows, таблица seasons с внешним ключом в tv_shows, таблица episodes с внешним ключом в seasons, reviews и cast_members таблицы с внешними ключами в episodes. Таким образом, чтобы получить всю информацию о сериале нужно выполнить соединение пяти таблиц.

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

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

Похоже на огромную фрактальную структуру данных.
image
(Множество множеств множеств множеств. Вкусные фракталы.)

Все данные нужные для сериала хранятся одним документом, так что можно очень быстро получить всю информацию сразу, даже если документ очень большой. Есть сериал, называемый «General Hospital», который насчитывает уже 12000 эпизодов в течение 50+ сезонов. На моем ноутбуке, PostgreSQL работает около минуты, чтобы получить денормализованные данные для 12000 эпизодов, в то время как извлечение документа по ID в MongoDB занимает доли секунды.

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

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


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

Вложенная структура ленты активности выглядит очень похоже на сериалы.
image

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

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

В 2010 году команда Diaspora приняла такое решение, статьи Esty об использовании документарных СУБД оказались весьма убедительными, даже несмотря на то, что они публично отказались от MongoDB в последствии. Кроме того, в это время, использование Cassandra в Facebook породило много разговоров об отказе от реляционных СУБД. Выбор MongoDB для Disapora был в духе того времени. Это не было неразумным выбором на тот момент, учитывая знания, которые они имели.

Что могло пойти не так?


Существует очень важное различие между социальным данным Diaspora и Mongo-идеальных данных о сериалах, которое никто не заметил на первый взгляд.

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

Но с социальными данными, некоторые из прямоугольников в диаграмме отношений имеют один и тот же тип. На самом деле, все эти зеленые прямоугольники одного типа — они все пользователи диаспоры.
image

Пользователь имеет друзей, и каждый друг может сами быть пользователем. А может и не быть, потому что это распределенная система. (Это целый пласт сложности, который я пропущу на сегодняшний день.) Таким же образом, комментаторы и лайкеры также могут быть пользователями.

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

Дублирование данных дублирование данных


Мы можем по-разному смоделировать это в MongoDB. Самый простой способ — дублирование данных. Вся информация о пользователе копируется сохраняется в лайке к первому посту, а затем отдельная копия сохраняется в комментарии ко второму посту.Преимущество в том, что все данные присутствует везде, где вам это нужно, и вы все еще можете вытащить весь поток активности еще в одном документе.

Примерно так выглядит плотностью денормализованная лента активности.
image

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

Вы можете понять, почему это привлекательно: все данные вам нужно уже находится там, где вам это нужно.

Вы также можете видеть, почему это опасно. Обновление данных пользователя означает обход всех лент активности, чтобы изменить данные во всех местах, где они хранятся. Это очень сильно подвержено ошибкам, и часто приводит к несогласованности данных и загадочным ошибкам, особенно при работе с удалениями.

Неужели нет надежды?


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

При этом подходе вместо встраивания данных там, где они нужны, вы даете каждому пользователю ID. После этого вместо встраивания данных пользователя вы сохраняете только ссылки на пользователей. На картинке ID выделены зеленым:
image
(MongoDB фактически использует идентификаторы BSON — строки, похожие на GUID. На картинке числа, чтобы легче было читать.)

Это исключает нашу проблему дублирования. При изменении данных пользователей, есть только один документ, который нужно изменить. Тем не менее, мы создали новую проблему для себя. Потому что мы больше не можем построить ленту активности из одного документа. Это менее эффективное и более сложное решение. Построение ленты активности в настоящее время требует, чтобы мы 1) получить документ ленты активности, а затем 2) получили все документы пользователей, чтобы заполнить имена и аватары.

Чего не хватает MongoDB — это операции соединения как в SQL, которая позволяет написать один запрос, объединяющий вместе ленту активности и всех пользователей, на которых есть ссылки из ленты. В конечном итоге приходится вручную делать джоины в коде приложения.

Простые денормализованные данные


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

В социальной сети нет самодостаточных сущностей. Каждый раз когда вы видите имя пользователя или аватар вы ожидаете что можно кликнуть и увидеть профиль пользователя, его посты. Сериалы не работают таким образом. Если вы находитесь на эпизоде 1 сезон 1 сериала Вавилона 5, вы не ожидаете, что будет возможность возможность перейти на эпизод 1 сезон 1 General Hospital.

Не. Надо. Ссылаться. На. Документы.


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

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

Если ваши данные выглядят как набор бумажных документов — поздравляю! Это хороший кейс для Mongo. Но если у вас есть ссылки между документами, то у вас на самом деле нет документов. MongoDB в этом случае — плохое решение. Для социальных данных это действительно плохое решение, так как самая важная часть — связи между документами.

Таким образом социальные данные не являются документарными. Это означает что на самом деле социальные данные… реляционны?

Опять это слово


Когда люди говорят «социальные данные не реляционны», это означает не то, что они имеют ввиду. Они имеют ввиду одну из двух вещей:

1. «Концептуально, социальные данные более граф, чем набор таблиц.»

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

2. «Гораздо быстрее получить все социальные данные когда они денормализованы в один документ»

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

На конференции All Your Base Conf в Оксфоре, где я сделала доклад по теме этого поста, Neha Narula представила замечательный доклад о кешировании, который я рекомендую посмотреть. Вкратце, кеширование нормализованных данных — это сложная, но вполне изученная проблема. Я видела проекты, в которых лента активности была денормализована в документарной СУБД, как MongoDB, что позволяло получать данные гораздо быстрее. Единственная проблема — инвалидация кеша.

«Есть только две трудные задачи в области информатики: инвалидация кеша и придумывание названий.»


Фил Карлтон

Оказывается, инвалидировать кеш на самом деле довольно трудно. Фил Карлтон написал большую часть SSL версии 3, X11 и OpenGL, так что он знает кое-что о компьютерной науке.

инвалидация кеша как сервис


Но что такое инвалидация кеша, и почему это так сложно?

Инвалидация кеша это знание когда ваши данные в кеше устарели и требуется их обновить или заменить. Вот типичный пример, который я каждый день вижу в веб-приложениях. У нас есть долговременное хранилище, обычно PostgreSQL или MySQL, и перед ними мы имеем слой кеширования, на основе Memcached или Redis. Запрос на чтение ленты активности пользователя обрабатывается из кеша, а не напрямую из базы данных, что делает выполнение запроса очень быстрым.
image

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

Это очень распространенный паттерн. Твитер держит в in-memory кеше ленты последних активных пользователей, в которые добавляются посты когда кто-то из фолловеров создает новый пост. Даже небольшие приложения, которые используют нечто, вроде лент активности, так делают (см: соединение семи таблиц).

Вернемся к нашему примеру. Когда автор меняет существующий пост, обновление обрабатывается также, как и создание, за исключением того что элемент в кеше обновляется, а не добавляется к существующему.

Что произойдет, если фоновый поток, обновляющий кеш, поток прервется посередине? Может упасть сервер, отключатся сетевые кабели, приложение перезапустится. Нестабильность является единственным стабильным фактом в нашей работе. Когда такое случается данные в кеше становятся несогласованными. Некоторые копии постов имеют старое название, а другие — новое. Это нелегкая задача, но с кэшем, всегда есть ядерный вариант.
image

Всегда можно полностью удалить элемент из кеша и пересобрать его из согласованного долговременного хранилища.

Но что если нет долговременного хранилища? Что если кеш — единственное что у вас есть?

В случае MongoDB это именно так. Это кеш, без долговременного согласованного хранилища. И он обязательно станет несогласованным. Не «согласованным в конечном счете» (eventually consistent), а просто несеогласованным все время (Этого не так сложно добиться, достаточно чтобы обновления происходили чаще, чем, чем среднее время достижения согласованного состояния — прим. пер.). В этом случае у вас нет вариантов, даже «ядерного». У вас нет способа пересобрать кеш в согласованном состоянии.

Когда в Diaspora решили использовать MongoDB, то объединили базу с кешем. База данных и кеш — очень разные вещи. Они основаны на разном представлении о стабильности, скорости, дублировании, связях и целостности данных.

Преобразование


Как только мы поняли, что случайно выбрали кеш для базы данных, что мы могли сделать?

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

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

Делать было нечего, кроме извлечения данных из MongoDB и помещения в реляционную СУБД, на ходу решая проблемы несогласованности данных. Сам процесс извлечения данных из MongoDB и помещения в MySQL был прямолинейным. Более подробно в докладе на All Your Base Conf.

Ущерб


У нас были данные за 8 месяцев работы, которые превратились в 1.2 миллиона строк в MySQL. Мы провели восемь недель разрабатывая код для миграции и когда запустили процесс основной сайт ушел в даунтайм на 2 часа. Это было более чем приемлемым результатом для проекта в стадии pre-alpha. Мы бы могли уменьшить даунтайм, но мы закладывали 8 часов, так что два часа выглядело фантастикой.
image
(NOT BAD)

Эпилог


Помните приложение для телесериалов? Это был идеальный вариант использования для MongoDB. Каждый сериал был одним самодостаточным документом. Нет ссылок на другие документы, нет дублирования, и нет способа сделать данные несогласованными.

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

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

Видите что происходит?


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

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

Это было в конечном счете, проблема коммуникации, а не техническая проблема. Если эти разговоры, что произошли раньше, если бы мы взяли время, чтобы действительно понять, как клиент хочет видеть данные и что хочет делать с ним, то мы, вероятно, перешли бы PostgreSQL ранее, когда было меньше данных, и было легче.

Учиться, учиться и еще раз учиться


Из опыта я узнала кое-что: идеальный кейс MongoDB еще уже, чем наши данные о сериалах. Единственное, что удобно хранить в MongoDB — произвольные JSON фрагменты. "Произвольные" в этом контексте означает, что вам абсолютно все равно что внутри JSON. Вы даже не смотрите туда. там нет схемы, даже неявной схемы, как было в наших данных о сериалах. Каждый документ — набор байт, и вы не делаете никаких предположений о том, что внутри.

На RubyConf я столкнулась с Conrad Irwin, который предложил этот сценарий. Он сохранял произвольные данные, пришедшие от клиентов, в виде JSON. Это разумно. CAP теорема не имеет значения, когда в ваших данных нет смысла. Но в любом интересном приложении данные имеют смысл.

От многих людей я слышала, что MongoDB используется как замена PostgreSQL или MySQL.Нет обстоятельств при которых это может быть хорошей идеей. Гибкость схемы (по факту отсутствие схемы — прим. пер.) выглядит как хорошая идея, но на самом деле это полезно только тогда, когда ваш данные не несут ценности. Если у вас есть неявная схема, то есть вы ожидаете некоторую структуру в JSON, то MongoDB — неверный выбор. Я предлагаю взглянуть на hstore в PostgreSQL (в любом случае быстрее, чем MongoDB), и изучить как делать изменения схемы. Они действительно не так сложны, даже в больших таблицах.

Найдите ценность


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

Делайте ценные вещи простыми.

Конец


Спасибо что дочитали досюда.
Метки:
Поделиться публикацией
Комментарии 245
  • –11
    Просто кто-то не умеет готовить mongo и взялся за проект, не прочитав про него ни строчки.
    • +44
      Может вы укажете на явные ошибки в доводах автора для тех, кто не является специалистом по базам данных?
      • +5
        > Документ — это большой JSON объект
        Это не обязательно большой обьект

        >Вот пример документа одного сериала, Вавилон 5
        Почему вы решили что это оптимальная модель данных?
        Почему например документ не может из себя представлять описание эпизода?
        А другой документ описание сериала. Причем эти документы можно разнести по разным коллекциям.

        Мне кажется вы даже не пытались думать над моделью данных, все доводы это одна большая явная ошибка.
        • +13
          Почему например документ не может из себя представлять описание эпизода?
          А другой документ описание сериала. Причем эти документы можно разнести по разным коллекциям.


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

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

          при этом задача «удалить актеров, на которых больше нет ссылок из сериалов» — становится достаточно нетривиальной
          • +1
            удалено
            • +7
              так про это и статья: если нужна реляционность (а она чаще всего нужна) — Mongo не очень хороший выбор, так как реляционность придется делать на уровне кода, а это чревато массой проблем
              • +4
                Ох, зря выходит удалил. Ну да ладно. В целом я согласен, просто довольно глупо выглядит выбор инструмента, который не подходит, а потом выставление этого инструмента виноватым в том, что он не подходит.
                • +4
                  ну вот люди сделали ошибку, написали статью о том как ошиблись (я думаю статья не новая явно)
                  если бы 5 лет назад эта статья попалась бы нам на глаза — мы бы тоже не выбрали монгу в качестве основной БД

                  а 5 лет назад в монге помимо ограничений «by design» было еще очень-очень много недоработок и хреновостей, которые приходилось героически преодолевать. сейчас с этим уже намного-намного лучше
                  • +1
                    А в статье про конкретные баги ни слова. Две технические проблемы, описанные в статье: отсутствие ACID транзакций и отсутствие джоинов. Они и по сей день никуда не делись. Статья описывает опыт полученный в 2010-2012 годах.
                    • 0
                      ну про старые баги (часть из которых можно было считать фичами) — это уже история, вспомнил в порядке ностальгии :)
                    • +6
                      люди рассказали, как они ошиблись с выбором инструмента, и пишут в заголовке «никогда не используйте этот инструмент».

                      Разумно, да.
                      • +5
                        люди описали свою ошибку, потом описали в каких случаях не использовать этот инструмент. заголовок немного не соответствует статье, но это не отменяет полезности самой статьи
              • +3
                Кто вам сказал что удаление это тривиальная задача? И кстати, удаление гораздо более не тривиально в реляционных данных.
                • 0
                  ну вот в реляционной СУБД пример с сериалами — решается сильно проще. большая часть остального — тоже.
                  • 0
                    Вы путаете проще и привычнее. А кроме того забываете про связанность данных и как следствие переиндексирование реляционных таблиц. При этом когда вы сравниваете реляционные базы с документарными вы не учитываете возможность разных полей описания для разных сериалов и серий. Например если сериалы у вас делятся на типы (боевики, семейные, для взрослых и т.д.) то в некоторых из типов будут присутствовать дополнительные поля описания (например количество трупов) а в других нет.
                    • +7
                      Я не путаю ничего. Проще — это когда все делается 1 запросом, который гарантированно или выполнится, или нет, не сломавшись на пол-дороги и не оставив базу в неопределенном состоянии.
                      А дополнительные поля описания — вполне можно вместить и в реляционную модель. Это будет не так красиво как в Mongo, но 5 лет использования Mongo на серьезных наборах данных четко убедили меня в мысли, что целостность — все-таки более приятный бонус.
                      • –7
                        Если у вас запрос с кучей JOIN'ов на удаление данных, которые из-за этих самых JOIN'ов, выполняется, скажем, 2 минуты, и сервер, например, зависнет/упадёт/выключится — то у вас тоже будет падение на полпути и нецелостность данных. Побъются таблицы. Так в чём же разница?
                        • 0
                          любой нормальный сервер с поддержкой транзакций даже после сбоя операцию выполнит. монга кстати тоже выполнит операцию если сбой был на сервере БД

                          проблема монги в том, что те же каскадные удаления надо делать на программном уровне, и сбойнуть все может (с куда большей вероятностью чем падение сервера) именно на уровне приложения, и тут уже журнал операций сервера БД не поможет
                          • 0
                            Mongo выполнит, но в определенных кейсах.
                            В 32 битной версии по дефолту не выполнит.
                          • +4
                            Разница в том, что не побьются.
                            • +5
                              1) При правильном проектировании реляционной базы в запросе скорее всего не будет ни одного джойна.
                              2) Не смотря на 1) есть вероятность что он будет выполняться 2 минуты, но при таком количестве зависимостей у вас в NoSQL проблем будет никак не меньше(как и описано в статье, вам придется или массово дублировать данные, или реализовывать контроль целостности данных в коде)
                              3)Ваша фраза о том что «побъются таблицы» говорит о полном непонимании того как работают реляционные базы. И сводит ценность вашего комментария к абсолютному нулю.
                              • –2
                                Расскажите ка мне, как это вы будите писать запрос по вытаскиваю данных из НОРМАЛИЗОВАННОЙ БАЗЫ без join-в?
                                • +1
                                  По вытаскиванию — скорее всего никак. Речь шла об удалении. Если внешние ключи и индексы расставлены с умом, то удаление не потребует джойнов.
                                  • +2
                                    С удалением я полностью согласен. А вот с select-ом…

                                    P.s. I'm so sorry, зачитался комментариями и забыл что parent-comment был об удалении.
                                  • 0
                                    Вьюхи :)
                        • 0
                          В реляционных структурах я бы вообще не вспоминал слово «удаление» (если только речь не идет о переносе данных в архивные таблицы). Методов скрывать данные от приложения/пользователя достаточно много
                      • +4
                        Покажите как правильно сделать тот же пример с сериалами.
                        • 0
                          В комментарии выше я дал направление. Если хотите вот вам схемы для документов:
                          СЕРИИ (все поля не обязательны)

                          {
                          «название серии»:
                          «название сериала»:
                          «id сериала»:
                          «название сезона»:
                          «id сезона»:
                          «актеры сериала»:< array[,...] >
                          «актеры серии»:< array[,...] >
                          «описание»:…
                          }

                          — КОММЕНТАРИЙ (все поля не обязательны)

                          {
                          «название серии»:
                          «название сериала»:
                          «id сериала»:
                          «название сезона»:
                          «id сезона»:
                          «актеры сериала»:< array[,...] >
                          «актеры серии»:< array[,...] >
                          «Комментарий»:…
                          }

                          Хотите помощь в реальной задаче — пишите в личку.
                          • +8
                            А вы прочитали статью до конца? Там как раз описан кейс про список эпизодов и сериалов для всех актеров. Как эта структура решает проблему?
                            • +1
                              Простой выборкой по полю «актеры сериала»
                              • 0
                                и тут вдруг нам понадобилось сменить название сериала (с ошибкой было, например)…
                                • 0
                                  И что? Меняйте. Имя сериала это описательное поле, это не индекс. А что если в реляционной базе понадобится внести дополнительное поле описания? Это же надо будет изменить структуру таблицы для всего что уже записано и переиндексировать потом все с начала. Да везде есть нетривиальные задачи, но документарные базы позволяют их решать проще если у вас большое количество данных. С небольшим количеством данных удобнее работать в реляционной модели, но если данных много и приходится учитывать операции индексирования, то документарная модель лучше справляется.
                                  • +2
                                    И что? Меняйте. Имя сериала это описательное поле, это не индекс

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

                                    А что если в реляционной базе понадобится внести дополнительное поле описания? Это же надо будет изменить структуру таблицы для всего что уже записано и переиндексировать потом все с начала.

                                    зачем? обычно это можно решить без такого размаха

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

                                    очень многие вещи без того же JOIN выливаются в огромный баттхерт. а уж от потери целостности данных — так и совсем грустно становится
                                • +3
                                  Простите, не понимаю. Какой именно простой выборкой?

                                  Допускаю, что мой комментарий очень глуп — я не специалист в MongoDB совершенно. Поэтому, возможно, спрашиваю очевидные вещи. Рассчитываю на снисхождение :).

                                  Что вообще хранится в поле «актёры сериала» — имена актёров, id или и то, и другое?

                                  Если имена — то все проблемы, описанные в статье, ваши. Вы уверены, что распознаете, что «Одри Хепбёрн» и «Одри Хепберн» — одна и та же актриса, причём та же самая, что и «Хепбёрн, Одри», и «О. Хепберн», и «Хёпберн О.», но не та же самая, что «Хёпберн К.»?

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

                                  Если и то, и другое — не совсем вижу, получится ли преодолеть проблемы вариантов «только одно» или «только другое».
                                  • 0
                                    «актеры сериала»: array('actor_id (from actor document)' => 'Actor Name (for caching reason),.....)

                                    .find({ «актеры сериала.actor_id»: {$exists:true} )
                                • +1
                                  Подозреваю, что актёры это некий документ с ид на отдельную коллекцию актёров, что то в виде:
                                  {
                                  «имя»:
                                  «фамилия»:
                                  «id актёра»:
                                  «id фото»:
                                  }
                          • +5
                            Ошибка автора в том, что не нужно делать из нереляционной СУБД реляционную. Если уж вас так пугает дублирование данных, то нужно создавать отдельный документ и использовать dbref на него. Они поспешили переходить на mysql просто не подумав. Увы но так делаются многие проекты.
                            • +2
                              Кстати, а что произойдет с производительностью в случае использования большого количества dbref в документе?
                            • +10
                              Этот вопрос в статье рассмотрен. Как только появляются DBRef_ы сразу появляются джоины, которые работают медленно. То есть теряется собственно смысл использования NoSQL, так как единственное неоспоримое преимущество перед реляционными СУБД — скорость.
                              • 0
                                Вы забываете про преимущество в виде отсутствия схемы. При этом NoSQL не всегда быстрее хорошо организованной реляционной СУБД.
                                • +20
                                  Это призрачное преимущество. В программе все равно будет схема (типы), в живом приложении взять и поменять схему не выйдет. Прямо в статье есть пример — были сериалы, где актеры включены в документы сериалов. Потом понадобилось поменять схему — вытащить сущность актеров в отдельные документы.

                                  Как отсутствие схемы помогло? Похоже что никак.
                                  • +10
                                    Чаще всего отсутствие схемы — миф. Схема присутствует, но рассыпана по коду, изменение схемы — изменение в коде (причем код становится запутанным и раздутым из-за необходимости поддерживать старую схему для уже существующих данных и т.д.). А такое неявное присутствие схемы делает сложным понимание вообще структуры и взаимосвязей данных — нет никакой мета информации, как на уровне БД о колонках и их типах, ссылочности, ограничениях и т.д. А это, следовательно, увеличивает вероятность багов со стороны разработчиков, которые, могут по коду упустить какие-либо изменения схемы, сделанные другими разработчиками и т.д.
                                    Отсутствие схемы — это произвольные json документы, но никак не бизнес-сущности в приложении. Любые бизнес-сущности, засунутые в один JSON — это полноценная, но жутко неудобная схема в неявном виде
                                    • –1
                                      Вы можете в своем приложении создать программно схему, создать entity и ей следовать.
                                      Речь о том, что никто не заставляет это делать.
                                      • –3
                                        Простой пример.

                                        Есть база ресторанов и гостиниц.

                                        1. Реляционная БД. У ресторана свои поля, у гостиницы свои поля. Значит 2 таблицы. Если надо выбрать и то, и то, например, по метро, то это минимум 1 JOIN. Если это пользовательский выбор, то нужны стейтменты.
                                        И вот, чтобы добавить новое поле для, например, ресторанов, то мы меняем структуру таблицы, а затем ищем в коде где испльзуются стейтменты и добавляем это поле туда, иначе наши «SELECT * FROM table WHERE metro=?» и им подобные уже не работают. Помимо формы редактирования, например.

                                        2. Документоориентированная БД. Все сущности можно хранить в 1 БД (коллекции), с готовым индексом по метро. Добавили в рестораны новое поле — просто в форму (метод) редактирования его внесли. Всё. Всё остальное как работало, так и работает.
                                        • 0
                                          Открой для себя ef code first в c#. Добавляешь пол в класс и автоматом добавляется в базу.
                                          • 0
                                            в случае с ресторанами и гостиницами можно использовать подход Core Entities & Hiers — 1 таблица — «гео-объект» (название, адрес, тип,… ) +1 таблица на каждый из типов сущностей: рестораны, отели, чебуречные,…
                                            • 0
                                              А вы хоть раз такой проект реализовывали, чтобы такое предлагать? И какое количество пользователей в сутки это выдерживало? и был ли там поиск по нескольким параметрам?
                                              • 0
                                                Использую такой подход в самописной LCMS для создания и хранения учебных объектов с учетом вложенности и типов. Отдельно таблица «узел контета» с названием, вложенностью и другими общими параметрами. И отдельно по таблице на каждый из видов узлов (их с десяток). Сейчас больше 200k объектов, полет нормальный. Правда задач поиска по нескольким категориям не возникало, особых нагрузок нет.
                                        • +3
                                          Отсутствие схемы нужно редко и в очень малых количествах (в большинстве случаев).
                                          Причём в большинстве случев достаточно завести поле «extra_data», куда пихать json (индексировать, правда, не выйдет).
                                          Ну а сейчас в postgresql уже есть родной json.
                                          • 0
                                            В PostgreSQL 9.4 (релиз осенью) индексировать выйдет. Там совсем родной json (тип назвали jsonb) с эффективным хранением, возможностью индексирования и всякими клёвыми операторами (не все из них, правда, войдут в стандартную поставку).
                                        • +3
                                          Я бы сказал, единственное действительно неоспоримое преимущество — шардинг из коробки. Преимущество в скорости (по крайней мере по сравнению с последними релизами PostgreSQL) под большим вопросом.
                                          • +1
                                            А шардинг зачем нужен? Надежность он не увеличивает (по сравнению с РСУБД), скорость записи тоже (вся запись идет в master), только скорость чтения. Да еще и нарваться на неконсистентное состояние с шардингом проще в разы.
                                            • +4
                                              Когда база перестанет влезать в дисковый массив — тогда очень нужен :)
                                              С минусами я не спорю, но если БД реально большая — деваться особо некуда.
                                              • –2
                                                А добавить дисков в массив не? Зачем еще один сервер ставить? Там где вопрос касается терабайтов данных никто отдельные диски в серваки не пихает.
                                                • +2
                                                  Я не вижу смысла спорить. Если вам не нужен шардинг — рад за вас :)
                                                  Если кому-то он нужен (наверное, очевидно, что у разных проектов разные потребности, и мы не сможем рассмотреть все случаи), то его наличие — это плюс.
                                                  • –2
                                                    Just because you can it doesn't mean you should.

                                                    Реальная потребность в шардинге настолько редка, что когда она возникает фичи СУБД имеют довольно слабое значение.
                                                  • +1
                                                    Дисков можно и доставить. А вот доставить процессоров в этот сервер будет более сложной задачей. Ибо вертикальное масштабирование не всегда возможно.
                                                    • –3
                                                      А процессоры зачем? Тяжелые запросы надо кешировать, тогда вся производительность упрется в скорость записи на диски. Почитайте про архитектуру StackOverflow. Там посоны живут без шардинга, и неплохо живут при их объемах. И основная нагрузка на СУБД — запись.
                                                      • +1
                                                        а вы почитайте про архитектуру Badoo, например, и вы поймёте, что не всё можно вместить на один сервер. У Stackoverflow не такая уж и большая нагрузка — всего 216 запросов в секунду, и это при их 25 серверах.
                                                        • 0
                                                          Вы не делаете Badoo, и даже SO не делаете, а он прекрасно живет на одном сервере БД. Зачем шардинг?
                                                          • +5
                                                            я делаю игры. И в них может быть и больше 216 запросов в секунду. А может и не быть. Поэтому я лучше заранее спроектирую свою систему и заложу в неё возможность шардинга, и по началу у меня просто будет один шард — пока его будет хватать. Зато когда (если) игра выстрелит, то я очень быстро отмасштабирую всю систему.
                                                            Делать шардинг только в первый раз сложно. Когда всю систему отладил один раз, второй раз уже гораздо проще, и оверинжиниринг небольшой.
                                                            • 0
                                                              А с чего ты взял что 216 это потолок? Я делал системы, где в базу приходило 800-1500 запросов в секунду. но потом прикрутил кеш и получилось 50 запросов в секунду. Вообще считать запросы к базе без конкретной архитектуры смысла не имеет.
                                                              • 0
                                                                я про потолок ничего не говорил. Я взял данные из последней статьи про StackOverflow, и из числа 560 млн запросов в месяц вычислил, сколько запросов в секунду. Понятно, что пиковое значение у них в 5-10 раз больше. Плюс, в статье написано, что сервер ы загружены на 10%. Просто комментировал ваш аргумент про StackOverflow.
                                                                • 0
                                                                  Ну то есть в пике будет успешно выдерживать 2160 запросов в секунду. Этого мало для ваших игр? При наличии хорошо продуманного кеша потребность в шардинге у вас не возникнет ИМХО.
                                                                  • +3
                                                                    напоминаю, что StackOverflow живёт на 25 серверах, из которых 4 (четыре) — серверы баз данных.
                                                                    К тому же, кэш помогает ускорить чтение, а вот запись масштабируется либо увеличением ресурсов сервера (вертикально), либо шардингом (горизонтально). Кэш в этом случае не поможет. А в играх запись — очень частая операция.
                                                                    Я не пойму, о чём мы спорим. Вы хотите сказать, что шардинг — напрасная придумка, и все базы можно впихнуть в один сервер? Или что мне (и всем читателям этих комментов) никогда не светит выйти за границы одного сервера?
                                                                    • +1
                                                                      Из 4 это кластеры. Реально все запросы SO обрабатываются одним серверов, и остального StackExchange — другим. Но сверху построен слой кеширования из-за которого до базы долетают от силы 10% запросов, большинство из которых запись.

                                                                      Типичное интерактивное веб-приложение (вроде соцсети) иримеет соотношение чтения\записи примерно 98\2. Для сайтов где контент больше потребляется, чем создается, соотношение еще больше в сторону чтения. И итоге если у вас в базу попадает 200 независимых запросов на запись в секунду, то чтений происходит примерно 9800, и это при загрузке СУБД в 10%. До таких масштабов далеко не каждый проект доживет.

                                                                      Если же у вас сценарий стриминга данных, то смысла нет сравнивать с SO. Нужно буферизировтаь данные и заливать через bulk insert или аналогичные технологии. На sql express мне удавалось 20,000 строк в секунду писать.

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

                                                                      Кстати учитывая что у монги database-level lock на запись, то она действительно без шардинга не справится, даже с 216 в секунду, особенно при обеспечении хоть какой либо-надежности.
                                                                    • +3
                                                                      Peak is more like 2600-3000 requests/sec on most weekdays.

                                                                      Как хорошо, что я не видел статью на хабре, а сразу читал оригинал.
                                                  • +2
                                                    > скорость записи тоже (вся запись идет в master)

                                                    Наверное, прежде чем вот так рассуждать, надо сначала почитать хотя бы краткую документацию, да? Master — это в репликации. Шардинг это капельку о другом. Хотя продакшн шардинг и включает в себя репликацию, но это никак не относится к тому, что вы хотели сказать.
                                                • 0
                                                  Тут я привел пример где работа с DBRef будет быстрее «джойнов».

                                                  теряется собственно смысл использования NoSQL,
                                                  А что если у вас много данных и одна таблица размазана на 10 серверов, как вы в реляционной СУБД будете делать джойны и транзакции?
                                                  • +1
                                                    Много это сколько? У меня было около 2 ТБ в одной таблице на одном сервере. Зачем надо размазывать на 10 серверов? Горизонтальное масштабирование рсубд небходимо на таких объемах, которых 99,999% разработчиков никогда не увидят. Это происки вендоров NoSQL, для которых 10ГБ уже достаточно, чтобы горизонтально масштабировать.
                                                    • +1
                                                      А это от специфики приложения сильно зависит. Приложение не обязательно должно быть таким уж и сложным. Представте, что вы пишите систему интернет-аналитики, или сбора морниторинговых данных. В таких системах бывает 100 тысяч запросов на запись в секунду и больше. Без шардинга такую задачу просто не реально решить, так как просто упретесь в скорость дискового массива.
                                                      • –2
                                                        А зачем сразу в базу писать? Обычно для потоковой записи сначала пишут в промежуточный буфер (тупо на диск), а потом вливают в СУБД. Если realtime данные нужны пользователю, то пишут в кеш в памяти. Не обращал внимания что GA и Яндекс Метрика показывают реалтайм данные очень ограничено, а полную статистику только за прошедший день?
                                                        • +1
                                                          Можно, вопрос только зачем. На cassandra таже задача решается проще. Так можно про любую базу сказать, что оно хорошая — я тут рядышком ручками допишу, что она не умеет, а потом сложу обратно :)
                                                          • –2
                                                            Нет смысла пытаться заменить кеш базой данных и наоборот. Если поставить cassandra для всего, то получится и не кеш, и не база. А если захочется согласованности данных, то все равно придется лепить РСУБД. Об этом в статье было.

                                                            А если выбирать куда писать поток событий — на диск или в кассандру, то я выберу диск. Тупо дешевле и надежнее. Просто на каждое событие создается файл, а потом раз в час\день\месяц сливается все в субд.
                                                            • +1
                                                              >Нет смысла пытаться заменить кеш базой данных и наоборот.

                                                              Еще как есть, почитайте про cassandra, ее именно так и нужно использовать.
                                                              • 0
                                                                Дайте ссылку чтоли? Если есть смысл, то почему всеподряд не используют? Кассандра, на сколько мне известно, не поддерживает ссылочную целостность, даже несмотря на транзакции, поэтому для многих сценариев она непригодна.
                                                                • 0
                                                                  lambda-architecture.net/ — вот на самом деле ссылка для наиболее общего подобного случая.

                                                                  И там Кассандра занимает место [2] (master dataset), которое Вы отводите файлам (у которых с ссылочной целостностью тоже не очень, насколько я понимаю).
                                                                  Сваливание в файлы невозможно ускорить по записи втрое, подключив ещё несколько узлов. И репликация (replication_factor у Кассандры) на несколько узлов с автошардингом — нетривиальная для файлового хранилища задача, на самом деле.
                                                                  • 0
                                                                    Ниче не понял честно говоря. С чего вы взяли что файлы — это master dataset? Master dataset_ом всегда будет РСУБД, ибо поддерживает целостность. А вот для ускорения некоторых операций могут быть и промежуточные хранилища — файлы, очереди итп.

                                                                    Зачем нужно то, что по ссылке я не понял.
                                                                    • +1
                                                                      master dataset — это место, куда валится всё-всё что на входе, до разбора и преобразований.
                                                                      (Уточню на всякий случай: мы обсуждаем систему, которая более-менее в реалтайме выдаёт аналитику для десятков тысяч событий в секунду. Логи там, отчёты всякие.)

                                                                      Использовать под мастер датасет РСУБД безумие. Потому что транзакционная природа тут ни к чему, ибо данные не мутабельны. Одна строка — одно событие, бывают только insert или select. За транзакции для этого мы заплатим скоростью вставки и большей централизацией (хотя бы для координации блокировок), что сделает всю систему хрупче.

                                                                      У нас на уровне этого самом мастер датасета ровно одна задача — проглотить и надёжно сохранить все входящие. Много входящих.

                                                                      Потом уже эти данные пересчитываются пачками (при помощи канонического Map/Reduce) и, в качестве бонус-трека, потоково (при помощи стрим процессора какого-нибудь, типа Storm).
                                                                      Производные данные как раз-таки хорошо хранятся в РСУБД. Это именно они читаются с клиентов, и именно тут транзакционность может быть критичной. Потому что производные данные — это суть свёртки, и для них на сцену выходят апдейты имеющихся рядов.
                                                                      • 0
                                                                        Теперь понял, на сайте это ни разу не очевидно. Лучше бы raw dataset назвали, ибо в bi называют master data справочные данные, которые потом используются для сведения многих систем.

                                                                        Вот только не понял чем кассандра лучше записи в файлы.
                                                                        • 0
                                                                          >чем кассандра лучше записи в файлы

                                                                          Шардинг из коробки, гибкое управление фактором репликации, устойчивость к потере узлов, автобалансировка записи без единой точки отказа, линейное масштабирование (по объёму хранилища и скорости записи) при добавлении новых узлов, возможность добавлять новые узлы «на лету» (с оговорками, правда).
                                                                          • 0
                                                                            Ах, ну да. И Хадуп по ней бегает как по родной, без предварительной выгрузки данных в HDFS.
                                                                  • +1
                                                                    Вот неплохое введение: habrahabr.ru/post/204026/

                                                                    >Если есть смысл, то почему всеподряд не используют? — да вообще часто довольно используют. Кассандра штука специфичная, но кому нужно много писать, часто выбирают ее.
                                                            • 0
                                                              Что касается кеша, я честно гвооря не вижу как это может помочь. В таких системах обычно не хранят все поступившие данные, а аггрегируют различные счетчики по каким-то интервалам (например часам) и потом выполняют запросы аггрегирующие типа типа group by по дням, неделям и так далее. Кеш в этом случае должен быть не тупым key-value, а умной штукой, с поддержкой умных апдтейтов и запросов, а это уже получается опять какая-то база нужна.

                                                              Я думаю на практике никакие кеши не используют, и работает все отстойно, как вы и написали, с задержкой в сутки.
                                                              • 0
                                                                Агрегируют что? Перед тем как агрегировать данные надо куда-то записать. При большом потоке самый лучший вариант — диск локального сервера. Быстрее и надежнее (одновременно) сложно что-то придумать. Если писать только в не-durable хранилище, то велика вероятность потерять ченить.

                                                                А что касается кеша, то тот же redis поддерживает операции типа append и pub\sub, с которыми легко получить нужный результат.
                                                                • 0
                                                                  Нет, не обязательно записывать все. Вот представьте летят показы баннера, просто инкерементируется значение счетчика, соответсвующее показам за какой-то час, получается данные аггрегируются на лету по часам и надежно сохраняются, что и позволяет добиться риалтаймовости, о не-durable хранилище речь не идет. Cassandra позвоялет укзать кворум записи, таким образом данные будут пробублированны на W серверах (W — задаем сами), это будет надежнее, чем писать на диск одного сервера. Да и сложного ничего не вижу. А вам еще придется фоновым процессом разгребать все это дело :)

                                                                  Redis — совсем не подходит для хранения временных рядов. Там нельзя будет сделать поиск по диапазону дат и аггрегацию.

                                                                  • 0
                                                                    А как потом получить значение сколько баннеров было показано в ночь с пятницы на понедельник?

                                                                    А в redis не нужно делать агрегацию, её надо в базе делать или в olap. Redis нужен только чтобы показать хиты за последние 15 минут\день.

                                                                    Я делал систему статистики для SharePoint (встроенная довольно убогая) и главное было сделать реалтайм мониторинг (бесполезная фича, но красиво показывать было). Выкрутился именно так как описал — база в MS SQL для статистики и кеш для «реалтайма», правда промежуточного хранилища не было, события агрегировались в памяти приложения и пачками заливались в MS SQL.
                                                                    • 0
                                                                      >А как потом получить значение сколько баннеров было показано в ночь с пятницы на понедельник?

                                                                      Запросить за диапазон дат, и в коде просуммировать полученные счетчики, это если ad-hoc. Но «иделогически» правильно в таком случае писать еще один счетчик, типа количество показов за каждые 12 часов. Т.е. обычно приложение в cassandra пишет сразу кучу таблиц, где данные группируются уже как надо.
                                                  • +4
                                                    Если я не ошибаюсь, то mongo умеет DBRef, который хоть и не замена join-а, но некоторыми коннекторами работающая в подобной роли.
                                                • +2
                                                  присоединяюсь к вопросу.
                                                  Как вы решаете проблемы запроса дополнительных данных по их BSON id
                                                  • 0
                                                    Делаю дополнительный find. Разве не логично?
                                                    По сути дела, за меня это делает Spring.
                                                    • +5
                                                      равносильно дополнительный SELCT в mysql
                                                      • +2
                                                        Не равносильно. В mysql два select-а можно сделать в одной транзакции.
                                                        • 0
                                                          можно, но имелся в виду именно дополнительный запрос
                                                          • +3
                                                            В mysql я могу обернуть сколько угодно запросов в транзакцию и получить гарантированно консистентные данные. В монге — не могу. Единственный способ получить консистентное состояние базы — хранить всю информацию в одном документе (либо использовать глобальные блокировки, что сведет производительность к нулю)
                                                            • 0
                                                              Более того, монга по умолчанию не гарантирует что будут прочитаны только что записанные данные, особенно при репликации.
                                                              • +1
                                                                При репликации этого и РСУБД в общем случае не гарантируют.
                                                                • 0
                                                                  Mirroroing+синхронный коммит таки гарантируют.
                                                                  • 0
                                                                    Здравствуй, CAP-теорема. :)
                                                                    • 0
                                                                      Часто на практике видели network partitioning? Я за 10 лет ни разу. А проблемы несогласованности — хоть отбавляй.
                                                                      • 0
                                                                        Был случай, когда надо было синхронизировать данные между серверами, расположенными на разных континентах, вот там-то я дерьма наелся. :)
                                                              • 0
                                                                Ты имеешь в виду мульти запрос?
                                                                • +1
                                                                  я ничего такого не имею в виду, простая транзакция.

                                                                  псевдокодом:

                                                                  db.beginTransaction();
                                                                  var a = db.prepare("select * from a where id = ?", 1).execute().fetchRow();
                                                                  var b = db.prepare("select * from b where id = ?", a.bId).execute().fetchRow();
                                                                  db.commit();
                                                                  
                                                      • 0
                                                        Это зависит от задачи, например у меня в ajax приложении я отправляю сырой документ comment, в котором есть ссылка author,
                                                        на клиенте вывожу его с пом. Angular, примерно так: <div>{{comment.text}} <a href="{{#get comment.author -> homePage}}">{{#get comment.author -> name}}</a> </div>
                                                        Т.к. этот автор в 95% случаев уже закеширован в браузере, то имя и ссылка сразу подставятся, в остальных случаях произойдет автомтическая подгрузка автора и вывод в нужные места.

                                                        А в комментарии может быть ещё много ссылок, которые мы не «джойнили» т.к. не было необходимости.
                                                        В итоге мы имеем такие плюсы: меньшая нагрузка на БД (нет джойнов, меньшее кол-во обращений в БД), меньшая нагрузка на сервер за счет того что часть (или весь) рендеринга будет происходит у клиента, меньшее кол-во передаваемых данных.
                                                        • 0
                                                          Кейс интересный, но по факту вы держите кеш на клиенте?
                                                          А при отсутствии данных идет шторм запросов в базу, или все таки собирается кумулятивный запрос (но это уже больше вопрос по ядру самого Angular)?
                                                          • 0
                                                            У меня в одном из проектов собирается кумулятивный запрос в течение 100-300 мс, после этого летит на сервер одним «пакетом», в итоге ссылки на одни и те же объекты не порождают дополнительных запросов, объектов.
                                                            • 0
                                                              А на сервере эти документы выбираются одним запросом, если они из одной коллекции.
                                                              • 0
                                                                То есть для джоина надо вытащить записи на клиент, в смысле браузер, а потом сбегать на сервер и забрать связанные записи? Это хорошая идея? Это должно быстро работать?

                                                                В протоколе OData есть возможность делать Join прямо с клиента, и он прекрасно прикручивается к РСУБД.
                                                              • 0
                                                                Предположу, что интерфейс у Вас очень долго грузится, особенно когда надо загрузить связанные данные после первого-второго запроса.
                                                                То же самое можно сделать на сервере — параллельная загрузка несвязанных данных. При этом это будет невероятно быстрее: отсутствуют ожидания на клиенте, страница рисуется разом, кеш будет действовать для всех запросов.
                                                                Ну и как то сложно мне видится составление такого кумулятивного запооса, поддержка batch на уровне апи, разбор потом такого запроса.
                                                                • 0
                                                                  Нет, грузиться быстро, хотя для SPA и медленная загрузка допустима.
                                                                  Ну и как то сложно мне видится составление такого кумулятивного запооса
                                                                  Ничего сложного, а профит ощутимый (по аналогичной причине на некоторых сайтах множество js собирают в один js файл).
                                                                  кеш будет действовать для всех запросов.
                                                                  А если данные возьмутся из кеша браузера, то это будет ещё быстрее чем запрос на сервер.
                                                            • 0
                                                              Только начал работать с angularjs, #get это функционал из коробки? Не могу найти информации, или это псевдокод?
                                                        • –2
                                                          Абсолютно с вами согласен, автор 'зная' что монго не реляционная бд, пытается хранить в ней связанные данные и негодует.
                                                          • +1
                                                            Ребят, а за что мои пост получает минусы?
                                                            • –2
                                                              Из SQL базы можно (и нужно) сделать подобие mongo, а вот обратная задача затруднительна если вообще возможна.
                                                            • +6
                                                              Вы бы сначала разобрались в моделировании данных, разделили бы коллекции, и сделали бы все правильно, а потом бы уже кричали какарул монго мне данные того сего. Ну не разобрались в нереляционной модели, привыкли реляционным базам, ну бывает, зачем сразу столько пафоса «Вы никогда не должны использовать MongoDB»?

                                                              — Я создаю веб-приложения. Я участвую в 4-6 разных проектах каждый год, то есть создаю много веб-приложений. Я вижу много приложений с различными требованиями и различными потребностями хранения данных. Я разворачивала большинство хранилищ, о которых вы слышали, и несколько, о которых даже не подозреваете-------.

                                                              Либо у вас за этой рутиной не хватило времени разобраться в моделировании данных под монго, либо… одно из двух.
                                                              • +16
                                                                Это не ко мне, а к автору. А вы можете привести пример «как надо»? На примере тех же сериалов или соцсети.
                                                                • –4
                                                                  Да уж =) Автор спроектировал корявенькую архитектуру да еще и выбрал абсолютно не тот инструмент под нее. Но ведь конечно, если коньки не едут по асфальту, то это коньки виноваты, а не одевший их индивидуум.

                                                                  P.S. Нисколько не защищаю MongoDB кстати, погоняли ее в тестах и оказалось, что практически для всех задач есть более эффективные инструменты.
                                                                • +2
                                                                  Довольно банальные вещи, на самом деле, хотя сам по себе кейс, безусловно, интересен. Спасибо!
                                                                  • +14
                                                                    Заголовок абсолютно желтый. Нет ничего специфичного для MongoDB, кроме как конкретный попробованный пример документо-ориентированной базы данных. Такой критикой можно ударить по бОльшей части NoSQL продуктов, которые, как показывает практика, могут успешно использоваться и без непосредственной связи между таблицами.
                                                                    • 0
                                                                      Для MongoDB довольно много специфичного:
                                                                      1) Отсутствие ACID транзакций
                                                                      2) Отсутствие джоинов в любом виде (как минимум на момент написания).

                                                                      Сейчас появились более продвинутые движки NoSQL которые лишены обоих недостатков и проблемы, описанные в статье, гораздо менее актуальны.
                                                                      • +3
                                                                        ACID транзакций и джоинов нет также и в Riak, Cassandra, HBase, что не делает их менее «продвинутыми».

                                                                        • –1
                                                                          Значит у них будут те же самые проблемы. Но есть например RavenDB, где есть ACID и джоины, причем с автоматическими индексами. С ним указанных проблем не будет.
                                                                          • +1
                                                                            будут те же самые «проблемы» только тогда, когда Вы попытается наложить реляционную структуру на них
                                                                            • +1
                                                                              Я пытался работать с RavenDB в таком стиле. Кое-де он просел хуже SQL Express, но особого геморроя не доставил. Транзакции в нем есть, за несогласованность боятся не надо. Джоины делаются индексами на стороне БД и описываются в C# обычными Linq запросами. Если данные индекса устаревшие, то он честно об этом говорит и можно предусмотреть UI для этого.

                                                                              Проблемы в общем есть, но совсем не страшные. Правда RavenDB денег стоит и багов еще дофига. В продакшн пока страшно, а для блогов самое то.
                                                                              • 0
                                                                                >денег стоит
                                                                                >для блогов
                                                                                Взаимоисключающие параграфы.
                                                                                • 0
                                                                                  Да, поэтому на практике и не используется. Может баги починят, тогда можно будет ченить серьезное писать, а пока — увы.
                                                                                • –2
                                                                                  Что-то мне кажется, что бд на сисярпе исключительно под .NET взлетит невысоко, пролетит недалеко.
                                                                                  • 0
                                                                                    Сделай тесты, опубликуй результаты, интересно будет глянуть.
                                                                                    • +3
                                                                                      Я не про скорость, а про масштабы прикладного использования. Мало кто из здравомыслящих людей будет крутить бд на mono под никсами в продакшене. В итоге имеем бд, претендующую на звание next-gen nosql, и функционирующую на ~30% серверов. Что-то тут не сходится.
                                                                        • +1
                                                                          Ну почему же. У Монги целая масса всякой специфики: начиная с глобалных блокировок на запись и заканчивая отсутствием какой-либо компресии хранимых данных и рекомендацией авторов Монги использовать короткие имена полей в объектах (что для меня выглядит глупо и несерьезно).
                                                                          • 0
                                                                            Абсолютно согласен. Но разве на один из этих пунктов автор указывает?..
                                                                            • 0
                                                                              Автор указывает, что у Монги хватает нюансов, о которых обычно на всяких уроках/тренингах/ознакомительной документации обычно не упоминается, но которые делают использование Монги во многих проектах неуместным. И я в этом с автором согласен. В том числе потому, что у самого был проект, где в качестве одной из БД была выбрана MongoDB и впоследствии этот выбор был признан неудачным.

                                                                              А заголовок, конечно, слишком желтый.
                                                                        • +1
                                                                          Чего не хватает MongoDB — это операции соединения как в SQL, которая позволяет написать один запрос, объединяющий вместе ленту активности и всех пользователей, на которых есть ссылки из ленты. В конечном итоге приходится вручную делать джоины в коде приложения.

                                                                          С pymongo это можно сделать подключив нужный «son_manipulator», и «джойны» будут происходить автоматический на уровне «драйвера». Под Ruby тоже не должно быть такой проблемы.

                                                                          Ещё один из вариантов: отправлять клиенту документ как есть, а «джойны» производить в браузере со списком закешированых пользователей, это особенно удобно для ajax приложений. Таким образом уменьшается нагрузка на БД и кол-во передаваемых данных клиенту.

                                                                          PS: Статья не нова, и у меня есть ощущение что её уже переводили на хабре.
                                                                          • +1
                                                                            Искал переводы, но не нашел. Подобных статей в одно время было очень много.
                                                                            • 0
                                                                              С pymongo это можно сделать подключив нужный «son_manipulator», и «джойны» будут происходить автоматический на уровне «драйвера». Под Ruby тоже не должно быть такой проблемы.


                                                                              Не в этом дело. В любом случае, pymongo или что-либо другое будет сначала получать документ на клиент, смотреть на что он ссылается и подтягивать это следующими запросами.
                                                                            • +11
                                                                              Увидев заголовок аж вздрогнул испугавшись. Почитал статью и выяснил, что заголовок имеет к ней очень отдалённое отношение. :(
                                                                              • –2
                                                                                Правильно было бы назвать «не начинайте новый проект с использованием MongoDB», но в таком виде заголовок завлекает больше читателей.
                                                                                • +16
                                                                                  Нет, корректнее было бы «Правильно проектируйте хранение данных, а то может случиться конфуз» — это никак не относится ни к MongoDB, ни к любой другой БД, независимо от её типа.

                                                                                  Приходилось видеть и на MySQL такое, что еле-еле работало и хрипело «пристрели меня…», но это ж не значит, что MySQL плохая.
                                                                                  • 0
                                                                                    Есть в статье несколько Mongo-специфичных моментов. Например неконсистентность из-за отсутствия транзакций и полное отсутствие джоинов на стороне СУБД. Без этих недостатков проблема была бы не столь велика.
                                                                                    • +17
                                                                                      Погодите, но ведь если мы, проектируя хранение данных, изначально закладываемся на эти «фичи» Монги, это не должно стать сюрпризом.
                                                                                      А если это стало сюрпризом, значит хреново проектировали, и незачем с больной головы на здоровую перекладывать — Монго делает ровно то, что она делает.
                                                                                      Странно было бы, например, обвинять Redis в отсутствии, скажем, JOIN'ов — это ведь даже в голову никому не приходит. Так почему можно обвинять MongoDB в отсутствии вещей, об отсутствии которых сказано изначально в документации?
                                                                                      • +2
                                                                                        Правильно, если все заранее знать, то проблем не будет, но, процитирую автора:

                                                                                        Я говорила об этом так, как будто вся информация была очевидна, и команда Dispora просто не в состоянии провести исследование, прежде чем выбрать.

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


                                                                                        На момент описанных событий хайп вокруг MongoDB и NoSQL вообще перекрыл здравый смысл. Даже сейчас попадаются люди, которые не понимают чем грозит отсутствие транзакций.
                                                                                        • +4
                                                                                          В документации монги до сих пор очень много «белых пятен». Например узнать про database-lock при записи очень сложно, об этом написано одно предложение. Про проблемы, которые индуцирует отсутствие acid (по сути после записи нет гарантий что оно вообще записалось) вообще ни слова. Некоторые маркетинговые материалы монги откровенно врут, выдавая желаемое за действительное. Вот тут подробнее: hackingdistributed.com/2013/01/29/mongo-ft/