5 November 2009

MongoDB — варим хороший кофе

NoSQL
Введение

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

Сегодня мы поставим 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: Шпионим за главной страницей Хабра в реальном времени.
Tags: web 2.0 mongodb guide
Hubs: NoSQL
+45
17.1k 173
Comments 51
Ads