

Введение
Друзья, в первую очередь хочу поблагодарить вас за высокую оценку моего труда, это приятно, и мотивирует меня продолжать. Итак, почему надо покупать наших слонов я думаю вы уже поняли из
первой статьи, кто-то уже скачал и попробовал на вкус, а кто-то только собирается. Как бы там ни было, начнем.
Сегодня мы поставим MongoDB, ниже рассмотрим свежеиспеченный
ХабраЛоггер и
пошпионим за главной страницей Хабра в реальном времени :-)
Засыпаем кофе и заливаем кипяток
Сначала, выбираем себе релиз по душе и качаем —
Downloads.
Вы можете выбрать любой релиз, я использую 1.1.1. Если не боитесь падений, ставьте последний — 1.1.2 (вовсе не обещаю что будет падать, просто это возможно).
Если для вашей ОС есть сборка, советую ставить сборку, если же нет, придётся собрать из исходников. Простейшая установка сборки хорошо дана тут —
Quickstart. При сборке из исходников придется работать со Scons, и под FreeBSD возникнет ошибка «shell/dbshell.cpp:77: error: 'sigrelse' was not declared in this scope» — просто закомментируйте эту строчку.
Итак, будем считать что поставили. Если планируется репликация, советую запускать `mongod --master`, с этим ключом MongoDB ведёт лог операций (oplog). Поиграться можно уже сейчас с помощью `mongo test` (test — название БД):
> db.habratest.save({abra: "foo", ka: true, da: 123, bra: {foo: "bar"}}); # коллекция создана и в нее вставлен ряд.
> db.habratest.find({ka: true}); # ищем ряды где ka == true, поиск будет без индекса
{"_id" : ObjectId( "4af18c86977fd21033ca67f8") , "abra" : "foo" , "ka" : true , "da" : 123 , "bra" : {"foo ar"}}
А теперь давайте создадим индекс — вернее удостоверимся в его наличии:
> db.habratest.ensureIndex({"da": 1});
true
> db.habratest.find({da: 123}); # поиск ведется уже по индексу.
{"_id" : ObjectId( "4af18c86977fd21033ca67f8") , "abra" : "foo" , "ka" : true , "da" : 123 , "bra" : {"foo" : "bar"}}
> db.habratest.update({da: 123},{"$set": {pi: 3.14159265}}); # атомарная операция задания свойства
> db.habratest.find({da: 123});
{"_id" : ObjectId( "4af18c86977fd21033ca67f8") , "abra" : "foo" , "ka" : true , "da" : 123 , "bra" : {"foo" : "bar"} , "pi" : 3.14159265}
> db.habratest.update({da: 123},{"$set": {"bra.pi": 3.14159265}}); # задание вложенного свойства
> db.habratest.find();
{"_id" : ObjectId( "4af190176ca438316825ddef") , "abra" : "foo" , "ka" : true , "da" : 123 , "pi" : 3.14159265 , "bra" : {"foo" : "bar" , "pi" : 3.14159265}}
> db.habratest.remove({ka: true});
> db.habratest.find();
> db.habratest.drop();
{"nIndexesWas" : 2 , "msg" : "all indexes deleted for collection" , "ns" : "test.habratest" , "ok" : 1}
Свойство _id генерируется клиентом автоматически, и присуще каждому объекту, при желании, ID можно формировать самостоятельно, вкладывая в него полезные данные.
Думаю, всё понятно. За пикантными подробностями работы функций советую обратиться к документации.
Теперь давайте подключим драйвер к нашему средству разработки, список доступных драйверов —
mongodb.org/display/DOCS/Drivers. В статье я буду ориентироваться на PHP, однако и с любым другим языком всё будет вполне аналогично. В PHP драйвер представлен расширением 'mongo' в PECL.
Пьем первую чашку
Я подготовил небольшое приложение-пример —
ХабраЛоггер.
Думаю, из кода и комментариев к нему всё понятно, если нет, я с радостью отвечу на вопросы. Возможно вы заметили, что в начале скрипта пара классов наследуется от оригиналов, это не обязательно и лишь дает синтаксический сахар в виде $mongo->mydb->mycollection. Время выполнения мотивирует (0.411 миллисекунды).
Наверняка кто-то из вас заметил операцию group() в куске кода:
$stat['hostsHours'] = $db->hosts->group(
array('hour' => true) // keys
,array('count' => 0) // initial object
,'function (obj, prev) {++prev.count;}' // reduce function
,array('url' => $url) // condition
);
В первом аргументе задаются ключи (поля) для группировки — GROUP BY hour
В втором — исходное состояние объекта, которое будет перед первой итерацией.
В третьем — функция, которая применяется для редукции (уменьшения) множества.
Ну а в четвертом — фильтр исходя из которого будет сформировано множество для уменьшения.
SQL'ный аналог сего действа — SELECT hour, COUNT(*) count FROM hosts GROUP BY hour
Алгоритм ХабраЛоггера довольно прост, лог пишется в коллекцию hits и hosts, hosts содержит уникальный индекс по IP и дню, таким образом туда попадают лишь уникалы в рамках суток, а при выводе графика проводится несложная группировка.
Подчеркиваю, это лишь пример, и конечно же, более оптимальным решением было бы ротировать лог и проводить агрегацию раз в n минут. Но оптимизацию мы рассмотрим в следующий раз.
Кластеризация? Я ждал этого вопроса!
Для начала определимся с терминами:
Sharding — дробление базы данных на несколько шардов.
Shard — один или несколько равнозначных серверов, которые хранят одну и ту же часть данных.
Config-server — сервер, хранящий мета-информацию в первую очередь о том на каком shard'е какой chunk лежит.
Chunk — диапазон документов по индексу, например коллекцию hosts можно разбивать по индекс на свойстве url.
mongos — демон который принимает запросы от клиентов, взаимодействует с нужными shard'ами и config-серверами, и передает готовый ответ клиенту.
Выглядит это вот так:
Не стану вдаваться в пересказ документации, и в описание поведения mongos на каждом типе запроса. Шардинг подробно описан в официальный англоязычной документации —
Sharding Introduction.
Скажу лишь — папаша Google использует сходную схему.
Тушим свет, спускаем воду
На сегодня всё. Учтите, что тема следующей статьи будет по заявкам радиослушателей. Могу рассказать о GridFS (файловой системе на MongoDB) и рассмотреть другие примеры.
Спасибо за внимание, друзья! Жду ваших отзывов.
P.S. статистику посещений этой статьи можно посмотреть
тут, а статистику другой статьи, которая шпионится с ночи —
тут.
UPD: Шпионим за главной страницей Хабра в реальном времени :-)
комментарии (50)
2. Она не только встраиваемая. На основе их документации мы сделали вполне себе настоящий сервер db4o. Конечно с «серверностью» дела там обстоят не очень гладко, но всё решаемо.
3. Аудитории разные, но это же не значит, что о ней не нужно знать
Какова производительность операции group?
ждите нового графика…
веселая строчка)
Впечатляет, кстати, Ваш труд.
Конкретно узнать насколько можно лишь на реальном железе и реальных тестах, попробуйте если интересно, это вовсе несложно.
Спасибо, я рад.
Что ещё? Достаточно ли сказано о масштабировании?
Также у 10gen есть еще пара проектов, например, свой сервер приложений (с которого компания и начинала пока не переключилась на базу)
Нужен реальный пример и workaround'ы
Например для получения объекта { _id: «1234_4456»; a: 1234; b: 4456 }, мы используем $collection->find( array( "_id" => «1234_4456» ) ), что, теоретически, должно быть быстрее, чем $collection->find( array( «a» => 1234, «b» => 4456 ) ).
А если конкретно то интересует возможность создания ителектуального поиска на базе данной БД. Или буду рад если посоветуете сетевую БД ибо именно сетевая модель заложена в поиск.
Что имеется в виду под интеллектуальным поиском? Что такое сетевая БД?
Интелектуальный поиск это типа раньше вы искали зеленые гардины, а потом ищите кушетку и поиск подбирает кушетку так что бы она подходила под зеленые гардины. В таком вот духи. Эта вся фигня строится на сетевой основе. Тоесть имеем нод, и связанные с ним ноды, каждая связь которых имеет вес.
Сетевые БД это бд которые имеют именно такую структуру.
ru.wikipedia.org/wiki/%D0%A1%D0%B5%D1%82%D0%B5%D0%B2%D0%B0%D1%8F_%D0%A1%D0%A3%D0%91%D0%94
Еще бы бенчмарки в сравнении.
У меня давно есть идея распределенной отказоустойчивой платформу как у Google или круче, на которой и поисковик можно написать, и армию ботов поднять не проблема.
Скажите, была бы вам интересна статья об этом? HOWTO повторить Google, с некоторой долей теории, исследований и рассуждений.
Многое есть в оф. документации.
НО ваш личный опыт работы с MONGO ниоткуда не почерпнешь!
— С удовольствием бы прочитал про масштабируемость и отказоустойчивость в формате «как мы это поднимали у себя».
— Интересно узнать про то, сталкивались ли вы с падением шарда, перераспределением данных между шардами (например решили убрать один шард и надо данные распределить по оставшимся), добавление нового шарда.
— Где-то прочитал, что при записи в Mongo комп перегрузился и база упала. На восстановление ушло 30 минут. Как от таких случаев вы защищаетесь на своем продакшене?
— С удовольствием бы прочитал про масштабируемость и отказоустойчивость в формате «как мы это поднимали у себя».
С удовольствием напишу.
— Интересно узнать про то, сталкивались ли вы с падением шарда, перераспределением данных между шардами (например решили убрать один шард и надо данные распределить по оставшимся), добавление нового шарда.
Вы видно перепутали сервер и шард. Если шард полностью упал, то данные уже не спасти (только если из резервных копий), если упал сервер в шарде (их рекомендуется 3 на шард), то всё работает как работало, как только добавляется новый сервер, он без проблем синхронизируется и вступает в бой с клиентами.
— Где-то прочитал, что при записи в Mongo комп перегрузился и база упала. На восстановление ушло 30 минут. Как от таких случаев вы защищаетесь на своем продакшене?
С MySQL бывало и похуже, это нормально. Во-первых сервер не может просто так перезагрузиться — по крайней мере за год работы этого продакшена ни разу не было, во-вторых, два или три сервера уж точно одновременно не навернуться, но если это произошло — восстанавливать и вперед.
Хотя я слышал, что какие-то энтузиасты делают проект для просмотра и редактирования коллекций через веб-интерфейс, но мне даже смотреть не захотелось.
А сортировку он поддерживает?
Возможно ли на нем сделать подобное:
ORDER BY RAND()*hosts DESC LIMIT 5
то есть выводить 5 случайных документов, но вероятность показа будет больше у тех, у кого hosts больше.
> ORDER BY RAND()*hosts DESC LIMIT 5
Нет. И это огромный плюс… MySQL любит делать фишки удобные для использования, но которые при этом кладут на производительность. Данная конструкция в MySQL вызовет фулскан всей таблицы и положит сервер на немаленькой табличке. Ведь чтобы отсортировать результат и взять сверху 5 элементов, необходимо каждый ряд в таблице проверить и положить в память результат, потом сделать сортировку пузырьком в памяти, а затем уже отпилить маленький кусочек и послать ответ клиенту.
Но есть и правильные пути. Например, задайте индекс по свойству hosts и делайте запросы hosts > min && hosts < max. Где min и max там ограничители выбираемой группы, которые задаются псевдослучайно с задаваемой вероятностью. Еще можно задать какое-то числовое свойство группы (в ней скажем 20 объектов), и выбирать запросом (k IN list) 5 таких групп, а затем выбирать для каждой группы случайный элемент (уже на клиенте). Согласен, может быть не очень красиво, за то это будет хорошо работать и на миллиарде рядов, а вот RAND() загнется куда раньше.
Да, придётся думать, но без этого никуда.
А этот пистолет стреляет? А ногу у меня получится себе отстрелить?
Представьте, например, баннерную систему.
Миллионы запросов в сутки к скрипту, который отдает баннеры сайтам-партнерам.
Перед выдачей баннера, скрипт должен выбрать баннер из всех. Учесть кол-во кликов которые нужно отдать и т.д. и взависимости от этого вывести баннер.
Для таких задач что нужно использовать?
Поэтому я и поинтересовался насчет Mongo, вдруг она подойдет.
Кстати, в первой задаче (про функцию RAND): индекс на hosts, и find().sort(«hosts»:1).limit(rnd(),1) нужное количество раз. rnd() — Ваша хитрая схема с вероятностями.
Вторая задача (про баннерную систему): думаю я бы инкрементил клики в Memcached, и скидывал бы изменения в MongoDB раз в минуту, например. А выборку баннера сделал бы по индексу само собой.
Очень поверхностно пока знаком с Mongo.
Как я понял rnd это моя фунцкция?
А возможно подобной схемой вытаскивать сразу 5-7 баннеров одним махом?
Да, это Ваша функция которая выполняется в программе и возвращает количество для skip'а.
Неа, нельзя, но это ничего.
Тема очень интересная, особенно практические комментарии и примеры.
Буду рад видеть продолжение :-)
Мне кажется с каучуком по-удобнее — готовый rest-сервис и прозрачность работы из JS кода с клиента… Плюс есть подозрение что при больших базах каучук будет много надежнее.
Как я понял, ссылок на объекты в MongoDB нет. Как нам поступать на примере тех же комментариев, когда надо вытащить логин каждого автора коммента (разумеется, логин может изменяться, а привязка идет по id).
Спасибо!
Когда будут следующая статья о Mongo? Наверняка нашлись новые минусы.
Спасибо.