Мой первый опыт использования MongoDB
24 октября 2012
Большинство программистов (кроме тех, кто вообще не следит за новостями) наверняка что-то слышали о 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 производится очень просто:
Во FreeBSD — чуть сложнее:
/usr/local/etc/rc.d/mongod onestart
Лично я во время установки MongoDB под FreeBSD столкнулся с такой ошибкой:
pw: user 'mongodb' already exists
Если вы вдруг тоже с ней повстречаетесь, просто выполните после установки следующую команду:
Поздравляю, установка MongoDB завершена! Это, конечно, не кластер с наборами реплик и автоматическим сегментированием, но в качестве тестового окружения самое то. Помимо самого сервера MongoDB (mongod) у нас в распоряжении есть ряд полезных утилит, среди которых следует отметить mongodump, mongorestere и mongo. Первые две, очевидно, предназначены для резервного копирования и восстановления из резервной копии. Последняя является оболочкой для работы с mongod. А еще, если зайти на localhost:28017, то можно увидеть веб-интерфейс к mongod с логами, информацией о нагрузке и тп.
Теперь давайте запустим на локалхосте сокращалку ссылок, использующую MongoDB. Исходный код сокращалки вы можете найти на гитхабе. Заметьте, что к моменту, когда вы будете читать эту заметку, код в репозитории может отличаться от кода, приводимого далее.
cd mongo_shortener
sudo ./INSTALLDEPS.sh # устанавливаем зависимости
prove -rl ./t # прогоняем тесты
starman --port 3000 ./script/mongo_shortener
Теперь, если зайти на locahost:3000, то можно будет попробовать сокращалку в действии:
Напоминаю, что я программист, а не дизайнер, и делать красивые сайты не умею :) Теперь попробуем разобраться, как же сокращалка работает. Для начала откроем файл lib/MongoShortener/Database.pm:
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:
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 до 240 − 1. Поскольку по этому полю построен уникальный индекс, если мы попытаемся создать два документа с одинаковыми code, будет брошено исключение. Поэтому производится до пяти попыток создания нового документа. В поле url, очевидно, хранится сокращаемая ссылка.
Обратите внимание на второй аргумент метода insert. По умолчанию запись в MongoDB производится без подтверждения. То есть, мы просто даем команду «создай/обнови/удали документ такой-то» и не дожидаемся ответа. По понятным причинам в данном случае нам критически важно дождаться подтверждения от сервера. Что и достигается путем передачи вторым аргументом { safe => 1 } методу insert.
Можете немного поэкспериментировать с MongoDB, поработав с ней через оболочку mongo:
MongoDB shell version: 2.0.6
connecting to: MongoShortener
Смотрим список коллекций в базе:
Создать новый документ (попробуйте создать два документа с одинаковым code):
Просмотреть список документов (в поле _id всегда хранится уникальный ID документа):
Создание индекса:
Просмотр списка индексов:
Удаление индекса (перезапустите сокращалку и посмотрите, создастся ли он снова):
Обновление документа:
Удаление документа:
Выход из оболочки:
Можете в качестве упражнения добавить в сокращалку подсчет числа переходов по ссылкам (подсказка — используйте атомарную операцию инкремента).
Дополнительные материалы
В качестве источников дополнительной информации по MongoDB я бы рекомендовал следующие:
- На официальном сайте полно исчерпывающей справочной информации, в том числе на русском языке;
- Книга MongoDB в действии — must read;
- Для быстрого старта подойдет онлайн-книга The Little MongoDB Book и ее перевод на русский язык (раз и два);
- Презентация Почему MongoDB охренительна;
- На момент написания этих строк в общей сложности на развитие MongoDB было выделено 73 млн долларов;
А что вы скажите о MongoDB? Уже пробовали использовать эту СУБД в реальных проектах? Как впечатления?
Вы можете прислать свой комментарий мне на почту, или воспользоваться комментариями в Telegram-группе.