Большинство программистов (кроме тех, кто вообще не следит за новостями) наверняка что-то слышали о 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 до 2 40 − 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? Уже пробовали использовать эту СУБД в реальных проектах? Как впечатления?