mongodb/

Большинство программистов (кроме тех, кто вообще не следит за новостями) наверняка что-то слышали о MongoDB, но никогда не пользовалось этой СУБД. Давайте же выясним, что умеет MongoDB, а что не умеет, а также, вооруженные Perl и Mojolicious , напишем простую сокращалку ссылок, использующую MongoDB.

Теоретическая часть

Что в первую очередь следует знать о MongoDB?

  • MongoDB — это документо-ориентированная СУБД. Данные в MongoDB хранятся в документах, которые объединяются в коллекции. Каждый документ представляет собой JSON-подобную структуру. Проведя аналогию с реляционными СУБД, можно сказать, что коллекциям соответствуют таблицы, а документам — строки в таблицах. Максимальный размер документа в MongoDB 2.x составляет 16 Мб (в более ранних версиях — лишь 4 Мб);
  • В отличие от РСУБД MongoDB не требует какого-либо описания схемы базы данных — она может постепенно меняться по мере развития приложения, что есть удобно;
  • Поддерживаются индексы, в том числе по массивам и вложенным документам, а также геопространственные индексы. Поддерживаются уникальные и составные индексы;
  • Также MongoDB есть атомарные операции, compare-and-swap , курсоры , запись без подтверждения и даже MapReduce (но я бы не спешил переходить с Hadoop на MongoDB);
  • Размер коллекции в MongoDB может быть ограничен числом документов или мегабайтами. Если коллекция слишком разрастется, старые документы будут удалены. Эта возможность может пригодится, если вы собираетесь хранить в MongoDB какие-то временные данные;
  • Интерфейс MongoDB сильно напоминают работу с DBIx::Class . Типа получаем ORM «из коробки». В запросах могут использоваться функции на языке JavaScript;
  • В MongoDB поддерживается журналирование, а также асинхронная репликация двух видов — master-slave репликация и наборы реплик. Разработчики MongoDB рекомендуют использовать последние. Набор реплик представляет собой тот же master-slave, но в случае падения мастера среди реплик автоматически выбирается новый мастер. После возобновления своей работы бывший мастер становится репликой;
  • Пожалуй, самая значительная особенность MongoDB заключается в том, что документы могут быть автоматически сегментированы по нескольким наборам реплик. Сегментирование производится по диапазону; чтобы отнести документ к конкретному диапазону, используется сегментный ключ (shard key). Данные распределяются между наборами реплик так, чтобы каждый набор содержал примерно одинаковый объем данных. Если кластер перестает справляться с нагрузкой, можно просто добавить в него еще один набор реплик — перераспределение данных произойдет автоматически;
  • В документах MongoDB можно хранить бинарные данные — картинки, mp3 и так далее. Однако для данных размером более 1 Мб рекомендуется использовать GridFS. GridFS — это соглашение о хранении файлов произвольного размера в MongoDB, поддерживаемое всеми официальными драйверами. У меня уже чешутся руки написать свой RapidShare, а у вас?
  • MongoDB используют GitHub, SourceForge, Foursquare, Bit.ly, About.me, MTV, CNN, New York Times, Forbes, Disney, EA и многие другие ;

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

Есть множество других тонких моментов. Например, при обновлении документа в коллекции, ограниченной по размеру, документ не может увеличиваться. Еще MongoDB не позволяет выполнять неоптимизированную сортировку документов (когда производится выборка большого объема данных, а у вас нет подходящего индекса). А еще для работы с файлами MongoDB использует mmap . В связи с этим MongoDB обычно использует больше места на диске, чем другие СУБД. Также, если вы хотите работать с объемами данных, превышающими 4 Гб, вам понадобятся 64-х битные сервера.

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

Пример — простая сокращалка ссылок

В Debian/Ubuntu установка MongoDB производится очень просто:

sudo apt-get install mongodb

Во FreeBSD — чуть сложнее:

pkg_add -r mongodb
/ usr / local / etc / rc.d / mongod onestart

Лично я во время установки MongoDB под FreeBSD столкнулся с такой ошибкой:

Creating user ‘mongodb’ with uid ‘922’.
pw: user ‘mongodb’ already exists

Если вы вдруг тоже с ней повстречаетесь, просто выполните после установки следующую команду:

pwd_mkdb -p /etc/master.passwd

Поздравляю, установка MongoDB завершена! Это, конечно, не кластер с наборами реплик и автоматическим сегментированием, но в качестве тестового окружения самое то. Помимо самого сервера MongoDB (mongod) у нас в распоряжении есть ряд полезных утилит, среди которых следует отметить mongodump, mongorestere и mongo. Первые две, очевидно, предназначены для резервного копирования и восстановления из резервной копии. Последняя является оболочкой для работы с mongod. А еще, если зайти на localhost:28017, то можно увидеть веб-интерфейс к mongod с логами, информацией о нагрузке и тп.

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

git clone git: // github.com / afiskon / mongo_shortener.git
cd mongo_shortener
sudo . / INSTALLDEPS.sh # устанавливаем зависимости
prove -rl . / t # прогоняем тесты
starman —port 3000 . / script / mongo_shortener

Теперь, если зайти на locahost:3000, то можно будет попробовать сокращалку в действии:

Сокращалка ссылок, использующая MongoDB

Напоминаю, что я программист, а не дизайнер, и делать красивые сайты не умею 🙂 Теперь попробуем разобраться, как же сокращалка работает. Для начала откроем файл lib/MongoShortener/Database.pm:

package MongoShortener :: Database ;

use strict ;
use warnings ;
use MongoDB ;

my $db ;

sub getHandle {
unless ( defined $db ) {
$db = MongoDB :: Connection -> new (
host => ‘mongodb://localhost:27017’ ,
) -> get_database ( ‘MongoShortener’ ) ;

$db -> urls -> ensure_index ( { code => 1 } , { unique => 1 } ) ;
}

return $db ;
}

1 ;

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

Также следует обратить внимание на файл lib/MongoShortener/Main.pm:

sub _create_short_url {
my ( $self , $url ) = @_ ;
my $db = MongoShortener :: Database :: getHandle ( ) ;
my $code = undef ;
for ( 1 .. 5 ) {
$code = 1 + int rand ( 2 ** 40 1 ) ;
try {
$db -> urls -> insert ( { code => $code , url => $url } , { safe => 1 } ) ;
} catch {
$code = undef ;
} ;
last if defined $code ;
}
die ‘CODE_GEN_FAILED’ unless defined $code ;

my $short_url = encode_base64url ( pack ( ‘Q’ , $code ) ) ;
$short_url =~ s/A+$// ;

return HOME_URL ( ) . $short_url ;
}

sub _resolve_short_url {
my ( $self , $short_url ) = @_ ;
$short_url .= ‘A’ x ( 11 length $short_url ) ;
my $code = unpack ( ‘Q’ , decode_base64url ( $short_url ) ) ;

my $db = MongoShortener :: Database :: getHandle ( ) ;
my $doc = $db -> urls -> find_one ( { code => $code } ) ;
return defined $doc ? $doc -> { url } : undef ;
}

Как видите, в коллекции urls хранятся документы, содержащие поля code и url. Первое из них представляет собой случайное число от 1 до 2 40 − 1. Поскольку по этому полю построен уникальный индекс, если мы попытаемся создать два документа с одинаковыми code, будет брошено исключение. Поэтому производится до пяти попыток создания нового документа. В поле url, очевидно, хранится сокращаемая ссылка.

Обратите внимание на второй аргумент метода insert. По умолчанию запись в MongoDB производится без подтверждения. То есть, мы просто даем команду «создай/обнови/удали документ такой-то» и не дожидаемся ответа. По понятным причинам в данном случае нам критически важно дождаться подтверждения от сервера. Что и достигается путем передачи вторым аргументом { safe => 1 } методу insert.

Можете немного поэкспериментировать с MongoDB, поработав с ней через оболочку mongo:

$ mongo MongoShortener
MongoDB shell version: 2.0.6
connecting to: MongoShortener

Смотрим список коллекций в базе:

> show collections

Создать новый документ (попробуйте создать два документа с одинаковым code):

> db.urls.insert({ code: 123, url: «http://remontka.com/» });

Просмотреть список документов (в поле _id всегда хранится уникальный ID документа):

> db.urls.find();

Создание индекса:

> db.urls.ensureIndex({ code: 1 }, { unique: true });

Просмотр списка индексов:

> db.urls.getIndexes();

Удаление индекса (перезапустите сокращалку и посмотрите, создастся ли он снова):

> db.urls.dropIndex({ code: 1 });

Обновление документа:

> db.urls.update({ code: 123 }, { url: «http://example.ru/» });

Удаление документа:

> db.urls.remove({ code: 123 });

Выход из оболочки:

> exit

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

Дополнительные материалы

В качестве источников дополнительной информации по MongoDB я бы рекомендовал следующие:

А что вы скажите о MongoDB? Уже пробовали использовать эту СУБД в реальных проектах? Как впечатления?

EnglishRussianUkrainian