Введение
Друзья, в первую очередь хочу поблагодарить вас за высокую оценку моего труда, это приятно, и мотивирует меня продолжать. Итак, почему надо покупать наших слонов я думаю вы уже поняли из первой статьи, кто-то уже скачал и попробовал на вкус, а кто-то только собирается. Как бы там ни было, начнем.
Сегодня мы поставим 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: Шпионим за главной страницей Хабра в реальном времени.