Два проекта на Pylons и MongoDB готово. Теперь вполне можно поделиться впечатлениями от нереляционных БД и монго в частности. Статей по тому, что скрывается под страшными словами нереляционная база данных в интернетах уже хоть попой ешь, поэтому не хочу вдаваться в терминологию. Объясню на реальных примерах и, так сказать, чтобы даже моя жена поняла.
В "обычном" приложении в реляционных БД данные представлены в виде таблицы, которая состоит из столбцов и строк. Думаю, прайс-лист в магазине все видели - типичный пример. Пойдем дальше - у нас есть много таблиц. Например, новости и их категории. Настоящие поцаны, прочитавшие первые 100 страниц книжки по реляционным БД знают, что новости и названия их категорий нужно хранить в разных таблицах. В таблице новости хранить, собственно, ID новости, название, саму новость и ID категории, а в таблице категорий хранить ID категории и название категории. Опять же в простейшем случае. В запросе выбирать из двух таблиц, чтобы получить и новость и название категории. Это называется JOIN. Все удобно, красиво и очевидно. Другие крутые поцаны придумали язык SQL, который позволяет легко это делать.
А теперь представим, что к новости нужны теги. Это еще одна таблица тегов. У новости может быть много тегов. Это еще одна таблица соотношений новость-тег. А к новости нужны комментарии. Еще таблица комментариев. Один запрос обрастает 10 JOIN'ами с другими таблицами. А каждый JOIN на низком уровне - это довольно дорогостоящий как по памяти так и по процессору цикл foreach. А затем заказчик сказал, что категория может быть не одна, а много. Нам придется перекраивать отношения между двумя таблицами, изменять все запросы. Если мы используем ORM, придется переписывать и перекомпилировать схемы БД. Тут начинается настоящий SQL'ный ад. ORM, конечно, немного спасает от этого, потому что не приходится искать все запросы к этой таблице по всему приложению.
От этого никуда не деться при написании больших систем. Там просто постоянно нужны отношения между таблицами. Но, например, когда мы пишем простой сайт наши запросы обычно не сложнее одного-двух JOIN'ов, из которых половина вообще вынужденная. Просто потому что "так правильно". Мы все так привыкли, но есть совершенно другой подход...
Есть такой формат как JSON. Пошел он от языка JavaScript, потому что там каждый объект является массивом своих методов и данных. Соответственно, в простейшем случае, JSON-данные можно (но лучше не надо) прогнать через eval() и получить JS-объект (массив). Выглядит это просто:
{
name: "V@s3K",
sex: "male",
hands: {
left: True,
right: True,
},
penis: "8===>",
}
То есть у нас есть массив типа ключ-значение, в котором значения тоже могут быть такими же массивами, либо строками, числами, объектами. Собственно таблица в MongoDB представляет из себя массив таких массивов и называется коллекция. Всем известно как получить, например, имя третьего человека в такой коллекции. collection[3]["name"]. Ну вот... это всё :D Это и есть нереляционные БД. Один большой массив. Для меня самым приятным в этом было то, что у записи нет определенной схемы. То есть я могу к одному юзеру добавить поле address, не добавляя его всем остальным. В SQL так нельзя, все-таки таблица.
Теперь о самом MongoDB. Почему именно он? Для меня тут все просто:
CouchDB написан на Erlang'е. Это снижает мой интерес к ней до нуля.
Cassandra слишком document-oriented. Отлично подходит только под узкий круг задач.
MongoDB же написан на С++, что внушает мне доверие, а так же имеет куда более больший круг задач.
Главным тут является язык запросов. Для тех, кто знаком с SQL очень полезной будет ссылка о соответствии запросов SQL и MongoDB: http://memo.undr.su/2010/01/27/sootvetstvie-mysql-i-mongodb-zaprosov/
Мне, если честно, чтобы освоить синтаксис запросов полностью хватило этой странички.
В простейшем случае запрос выглядит как: db.news.find()
Соответственно, news - название коллекции, a find() вернет все записи из коллекции.
Если нам нужно условие (что чаще всего и нужно): db.news.find({ "name": "V@s3K" })
Выберет всех юзеров с именем V@s3K
Сортировка? Проще простого: db.news.find({ "name": "V@s3K" }).sort({ "penis": -1 })
Отсортирует юзеров с именем V@s3K по пенису по убыванию (-1 - убывание, 1 - возрастание).
Ограничить? Тогда db.news.find({ "name": "V@s3K" }).sort({ "penis": -1 }).limit(5)
Очень нравится то, что если у нас есть поле с массивом типа categories: [ "Одна", "Вторая" ], можно легко выбрать запросом: db.news.find({ "categories": "Одна" }) все записи, содержащие эту категорию в массиве.
Вернет 5 записей.
Хотим любимый и быстрый SELECT COUNT()? Тогда: db.news.find().count({ "name": 1 }). Посчитает все записи, где name - не пустой.
Все остальное - в документации: http://www.mongodb.org/display/DOCS/Developer+Zone
Философия mongo диктует нам использовать вложенные массивы, когда мы бы использовали JOIN в SQL. То есть список категорий надо делать не отдельной таблицей, а вставлять массивом в поле categories у каждой новости. Если привыкнуть к этому, становится очевидно и странно, почему раньше так не делал. Можно создавать целые огромные коллекции, например, коллекция пользователей, у каждого из которых массив его новостей, у каждой новости массив комментариев к ней (или у пользователя, в зависимости от наших нужд). Вместо минимум 3 таблиц на SQL, у нас 1 коллекция mongo. Не агитирую так делать всегда, например мне в блоге было удобнее все-таки разделить коллекцию постов и комментариев для удобства редактирования-удаления последних. Тут как и везде: возможности хороши только когда они обоснованы.
О производительности. При сборке из исходников вы бы заметили, что MongoDB написан на С++ и активно использует библиотеки boost. Уже хорошо. БД хранятся в отдельных файлах в указанной в настройках директории. Формат файлов - BSON (бинарный JSON). Размер всегда - ровно степень двойки. Например, под мой блог монго выделила 64 мегабайта, для магазина увеличила до 128. Вот такой вот формат. На 32-битной машине поддерживается максимально только 2-гигабайтную коллекцию. Разработчики рекомендуют 64-битные ОС для больших баз, чтобы не упереться в это ограничение. Храние всей базы в одном бинарном файле очень облегчает бекап и переносимость. В состоянии ожидания демоны серверов mongod и mysqld жрут одинаково, при запросах активно тредятся и форкаются, но по личным наблюдениям процессы mysql в топе я вижу чаще, чем mongo. То есть CPU при штатном использовании монго жрет меньше. Довольно странный и очень субъективный обзор-сравнения есть на хабре: http://habrahabr.ru/blogs/mysql/87620/
Видим, что mongo у поцана не дает больше на выборках (~20K/сек у mysql против ~15K/сек у монго), но всегда обходит на вставках (разгоняется аж до 60К записей в секунду). Видим, что границы разгона MongoDB ограничены так же нечетко, как всех остальных СУБД, там даже 160К в секунду получилось случайно. Подробности в обзоре, но нужно здраво оценивать, что все статьи, что монго "в 100 раз быстрее" - не более, чем рекламный ход и синтетические тесты. В других тестах я замечал, что и показатель отказов у монго выше (99% против 94%) и скорость в два раза больше. У третьих получалось наоборот, монго была в четыре раза медленнее. Все равно любой адекватный человек знает, что сравнивать базы - дело очень многогранное и сложное. Зависит не только от настроек, но и от конфигурации сервера, расположении баз на диске, от тысячи факторов. Кому интересно, наберите в гугле "MongoDB vs MySQL" и полюбуйтесь как у разных людей получались совершенно разные и совершенно синтетические результаты. У первого стояли чистые серверы без настроек. У последнего явно распараллеленный MySQL-сервер с хорошими настройками и чистый Mongo. Да еще и в конце автор сознается, что использовал кеш для MySQL. Короче - бред.
Из личных (то есть чисто субъективных и необоснованных) заметок: монго любит параллелиться из коробки и лучше будет использовать мои 4 ядра по 5%, чем одно на 20%. Монго любит хранить часто используемые данные в памяти, чтоб не читать каждый раз. Мы получаем настоящий memcache из коробки. А так же я ни разу не наблюдал как какой-то запрос по непонятным причинам может выполняться десяток секунд. Или я один замечал, что некоторые запросы в phpmyadmin ВНЕЗАПНО выдают "время выполнения: 16,434 сек"?
В общем я не разочарован в MongDB, хотя по началу очень старался избегать моды на NoSQL, считая это чем-то вроде холивара. После использования NoSQL я могу сказать, что у него несколько другая область применения. Теперь я не чувствую, что колю орехи ядерным взрывом, когда пишу SELECT * FROM `news`. Какая-то легкость в руках что-ли. Вот монго помогает именно в таких "простых" случаях. И нет, я не собираюсь обсуждать что лучше, а что хуже. Я пользуюсь и тем и тем.
А из замеченых минусов:
- ID объектов хранятся как закодированное мясо (набор из штук 15 символов). Сделано это для удобства кластеризации (не нужно следить за индексацией на разных серверах, очень ускоряет запись), но у этого подхода главный минус - привычный каждому auto increment делается через неприятное место. В реляционных БД внутри то же самое, только там это уже сделано и через то же место (таблицу индексов).
- Встроенный механизм DBRef, позволяющий (да-да) выполнять самые настоящие JOIN'ы между коллекциями, довольно не очевиден для такой простой БД. Точнее даже не он сам, а его реализация в библиотеках. Именно поэтому я никогда не использовал его в приложениях. Раз уж отказываемся от отношений, то до победного конца. Без костылей. Чаще всего я просто заменяю отношение на включение документа как значение.
- Херовая интеграция. Для 90% читающих будет как выстрел в голову. Связана с молодостью монго. В тех же pylons или django при использовании встроенного ORM или SQLAlchemy мы получаем так же плюшки в виде автоматической генерации и валидации форм, админок и другого безобразия. Тут же пока даже популярные библиотеки (я использую mongokit для питона) грешат костылями, что уж говорить об автогенерации чего-либо. Приходится как-то выкручиваться и учить старые форм-валидаторы новым танцам. А самые ленивые пользователи django, так привыкшие, что пол сайта генерируется за них самостоятельно, вообще начинают плеваться какашками, когда им говоришь, что на админку придется потратить больше, чем 0 строк кода. Я для таких даже название придумал: дитя фреймворков. Мне так жалко, когда человек теряется в гугле на пол дня со словами "а как я буду писать авторизацию, за меня всегда это джанга делала". Мой РНР-шный опыт все-таки иногда пригождается :)
- Ну и последний и очевидный недостаток, который не относится монго, но важен, так это то, что с ним нет хостингов. Если вы или ваш клиент нищеброд без VDS или своего сервера - использовать в продакшене не получится. А так: "бесплатный хостинг РНР + MySQL скачать без регистрации".
Summary
Итак. Для чего лучше подходит MongoDB:
+ Когда нам нужен простой дизайн
+ Когда число записей в разы превышает число чтений
+ Когда нам нужно активное распараллеливание
+ Когда нам уже не хватает распараллеливания и нужна кластеризация
А для чего SQL:
+ Огромные запросы на выборку разных наборов данных с их аналитикой
+ Когда нам нужно хранить изначально табличные данные
+ Когда нам нужна высокая стабильность и защищенность от потери данных, если уборщица выключит компьютер из розетки. В монго над этим только начали работать. (Говоря стабильность, я не имею ввиду обиду от потери БД пользователей или последних двух комментариев вашего бложека, я говорю о настоящих больших вещах).
+ Чтобы похапешники могли зарабатывать на говносайтах
P.S.: Никого не агитирую резко меняться, куда-то бежать, что-то ставить. Просто поделился тем, что для меня MongoDB был как глоток свежего воздуха. Как какая-то новая философия, другой подход к базам данных, который я мог увидеть не только на бумаге в учебниках в главе "типы баз данных". Примерно как кататься на велосипеде под веселую музыку в толпе. Кто это делал, тот меня поймет. И нет, это не замена SQL, и нет, он не готов к продакшену, а пока активно растет. Можем сослать это на юношескую безбашенность и манию к чему-то новому. Быть может. Но пока мне это нравится :)