Использование MongoDB
В последнее время NoSQL стало модным словом в веб-разработке. Некоторые проекты используют подобные БД для узких частей архитектуры, некоторые мигрируют на них полностью. Такие БД как правило отличаются от реляционных простой архитектурой и высокой масштабируемостью. На данный момент нас интересует MongoDB как одна из наиболее популярных NoSQL баз данных. Постараемся выделить сильные и слабые стороны, рассмотреть некоторые особенности разработки, область применения в друпале, а также постараемся понять на практическом примере что означает термин Map/Reduce.
MongoDB это что-то среднее между key-value хранилищами (которые обычно быстры и масштабируемы) и традиционными реляционными базами данных (MySQL, PostgreSQL и т.п.), которые предоставляют расширенные запросы и богатый функционал.
MongoDB (от "humongous") это масштабируемый, высокопроизводительный, документо-ориентированный сервер БД с открытым исходным кодом. Написан на C++. Возможности MongoDB:
- Документо-ориентированное хранилише данных
- Документы в JSON-поодбном формате со свободной схемой предоставляют простоту и мощь
- Полная поддержка индексов
- Индексы по любому элементу (даже вложенному) как мы и привыкли
- Репликация и высокая доступность
- Зеркалирование через локальные и глобальные сети для масштабирования и ваше спокойствие за сохранность данных
- Авто-шардинг
- Масштабируйте горизонтально без ущерба для функциональности
- Выборка
- Мощные запросы документов основанные на их структуре
- Быстрая запись и изменение данных
- Простые атомарные операции
- Map/Reduce
- Гибкая агрегация данных
- GridFS
- Храните файлы любого размера в БД без усложнения вашего приложения
- Коммерческая поддержка
- Доступны коммерческая поддержка, обучение и консалтинг
Когда это можно будет использовать
MongoDB можно использовать в своих веб-приложениях прямо сейчас. Последние версии достаточно стабильны для использования в production. Проект разрабатывает команда специалистов на постоянной основе, выходят багфиксы, появляются новые фичи (идеи по проекту), используется немалым числом крупных интернет-проектов среди которых присутствует SourceForge. Написаны драйверы для популярных языков программирования.
Для Drupal на данный момент написан модуль, который обещает реализовать указанные выше возможности (по большей части в Drupal 7), но он пока находится на стадии разработки. Также есть драйвер для DBTNG (Database Layer: The Next Generation), который позволяет нам обращаться с сервером MongoDB как с любой другой БД в друпале (конечно исключая выполнение SQL-запросов). В Drupal 7 появилась отличная возможность хранить поля (Fields замена CCK) не только в общей базе данных, но и по отдельности выносить их в разные хранилища. Эта реализация также обещана в упомянутом выше модуле.
Коротко говоря, мы сможем без проблем использовать этот сервер БД в новом релизе друпала для оптимизации производительности и масштабирования.
Особенности использования
Рассмотрим некоторые различия в работе с MongoDB по сравнению с привычным SQL. Все примеры кода написаны на JavaScript и могут быть проверены в интерактивной консоли, о ней ниже.
Консольные инструменты
По аналогии с тем, как в консоли mysql мы можем выполнять SQL-запросы, интерактивная консоль MongoDB позволяет выполнять команды сервера БД используя язык JavaScript. По умолчанию используется движок SpiderMonkey, при желании мы можем поменять его на V8. На официальном сайте можно найти online-вариант консоли, который работает прямо в браузере, плюс к тому небольшое введение для начинающих. Начинать рекомендуется именно с этого.
Структура информации
Структуру данных в реляционных системах на примере MySQL мы можем представить в виде следующей иерархии:База данных -> Таблица -> Строка -> Поле + Значение
.
Как это все выглядит в MongoDB:База данных -> Коллекция -> Документ -> ключ + значение
.
Таблицы в реляционных БД должны быть жестко структурированы, в то время как в MongoDB можно создавать документы произвольной структуры.
Пример документа в формате JSON:
{
doc_id: 153,
title: 'Some title',
body: '...A lot of text...',
author_id: 73,
date: 'Sun Oct 31 2010 03:00:00 GMT+0300 (MSK)',
additional: {
location: {
country: 'Russia',
city: 'Moscow'
},
category: 'books'
}
tags: ["tag1", "tag2", "tag3"]
}
Как можно заметить у нас полная свобода во вложенности ключей документа, что избавляет нас от необходимости денормализации как в SQL, т.е. нам не нужно разносить одну сущность в разные документы. Как и в SQL в MongoDB есть индексы, причем полная их поддержка. Мы можем стоить индекс по любому ключу приведенного выше документа, в том числе по вложенному country или по массиву tags. Можно делать составные индексы по нескольким ключам документа. Правила оптимизации индексов для SQL во многом подходят и для MongoDB и описаны в документации.
Выборка
Предположим нам надо выбрать все документы из определенной коллекции у которых значение city равно "Moscow".
В SQL нам приходится прибегать к инструкции JOIN, т.к. в унифицированной системе наподобие друпала мы не всегда можем записать всю информацию в одну таблицу. Например:
SELECT docs.title, loc.city FROM documents docs
INNER JOIN doc_location d_loc ON d_loc.doc_id = docs.doc_id
INNER JOIN location loc ON loc.loc_id = d_loc.loc_id
Как это делается в MongoDB, учитывая что все хранится в одном документе:
db.find({additional.location.city: "Moscow"});
По аналогии с JOIN мы также можем создавать ссылки на объекты других коллекций, и нам не придется делать отдельные запросы для получения связанных документов.
MongoDB хорошо справляется с большим количеством документов (миллионы), скорость выборки как и в SQL оптимизируется индексами, лимитами на количество получаемых документов за один запрос, как и в привычных реляционных БД индексы отрицательно влияют на скорость записи. Есть знакомая нам операция EXPLAIN, выполняющая те же функции что и в MySQL.
Запись
В SQL есть оператор INSERT для добавления и UPDATE для обновления записей.
Запись в MongoDB Выполняется при помощи трех функций: insert - добавление, save и update - для обновление и добавление.
Функция save это враппер для update, упрощающий синтаксис команды.
Примеры:
// $doc - произвольный документ
// Вставить документ
db.insert($doc);
// Обновить документ или добавить новый если его не существует, 2 варианта
db.save($doc);
// или
db.update({name: "Joe"}, $doc, true); // первый аргумент - условие, второй - новый документ, третий - вставка если исходный документ не найден
// Атомарная операция. Увеличить параметр counter на единицу
db.update({name: "Joe"}, {$inc : {counter : 1}});
MongoDB поддерживает несколько видов атомарных операций.
Мы можем использовать синхронный и асинхронный тип записи, асинхронный по умолчанию и он быстрее, т.к. приложению не приходится ждать ответа от сервера.
MongoDB рекомендуют использовать при большом количестве одновременных запросов (более тысячи в секунду), особенно при большом количестве операций записи. Судя по многочисленным отзывам, именно быстрая запись одно из главных преимуществ этой БД.
Говоря о записи, стоит упомянуть о Capped Collections.
Дело в том что для обычных коллекций при записи в каждый документ неявно добавляется ключ _id, который хранит уникальный идентификатор документа. Также по этому ключу обязательно строится индекс. Коллекции имеют динамически изменяемый размер.
В коллекциях типа Capped все это обстоит несколько по другому. Ключ _id создается, но для ускорения записи индекс по нему не строится (по умолчанию). Также для таких коллекций предварительно устанавливается занимаемый ими объем, что тоже добавляет скорости. Но имеются определенные ограничения, например обновление документов разрешено только если размер документа не изменился, удаление документов не поддерживается, когда в коллекции заканчивается место новые документы вытесняют старые. При использовании Capped Collections скорость записи документов будет приближена к скорости записи системных логов. Можно использовать эту особенность, скажем, для систем ведения статистики, кеша, логов.
Агрегация
Допустим, перед нами стоит задача: мы имеем таблицу с комментариями, надо выбрать суммарное количество голосов за каждого автора.
В SQL это решается довольно просто, как-то так:
SELECT author, SUM(votes) FROM comments GROUP BY author;
В MongoDB реализация посложнее но более мощная. Называется она Map/Reduce.
Говоря простым языком это альтернатива оператора GROUP BY и функций-агрегаторов (SUM, MAX, MIN, ...) для NoSQL (в нашем контексте). В общих чертах как происходит эта операция:
- Сначала выбираются необходимые документы из БД. Т.к. это по сути обычный запрос на выборку, к нему подходят общие правила оптимизации подобных операций, такие как добавление индексов и лимитирование количества выбираемых данных.
- На языке JavaScript создается функция map, которая проходит по каждому документу, найденному на предыдущем шаге, и собирает необходимую информацию для агрегации.
- На том же самом JS создается функция reduce, которая получает данные маппинга, сгруппированные по какому-то определенному в функции map ключу. Происходит долгожданная агрегация данных.
- При желании можно определить функцию finalize, которая будет запускаться после reduce и производить финальные действия над данными.
- Получаем результат агрегации и используем его в нашем приложении
Постараемся понять как это происходит на примере (взято от сюда).
Структура документа
Предположим, что у нас есть коллекция комментариев следующей структуры (JSON):
{
text: "lmao! great article!",
author: 'kbanker',
votes: 2
}
В этом документе у нас есть комментарий автора "kbanker" с двумя голосами.
Идем по шагам.
Функция map (этап маппинга)
Как мы отметили ранее, map - это функция на языке JavaScript, которая проходит по каждому документу и собирает необходимую информацию в формате пары ключ -> значение. Генерируется эта пара при помощи вызова операции emit:
// Ключ - имя пользователя автора;
// Значение - количество голосов за текущий комментарий.
var map = function() {
emit(this.author, {votes: this.votes});
};
Функция Reduce (этап агрегации)
В каждый вызов функции reduce (по одному на ключ) поступает два аргумента: ключ и массив значений, собранные на этапе маппинга. В нашем примере для автора "kbanker" вызов reduce будет примерно таким:
reduce('kbanker', [{votes: 2}, {votes: 1}, {votes: 4}]);
Теперь опишем функцию для подсчета голосов:
var reduce = function(key, values) {
var sum = 0;
values.forEach(function(doc) {
sum += doc.votes;
});
return {votes: sum};
};
Вызовем в консоли две команды, первая - для запуска операции, вторая - для получения результатов:
var op = db.comments.mapReduce(map, reduce);
db[op.result].find();
На выходе мы получаем необходимые сгруппированные данные и можем использовать их в нашем приложении.
Более подробное описание можно найти в статье оригинале (ссылка выше), а также в документации.
Важно знать про одну особенность. Map/Reduce, а в частности маппинг, довольно медленно работает. В Функции map рекомендуется писать наиболее оптимизированный JavaScript-код. В двух словах - поменьше операций присваивания и циклов, оптимизация JS-кода отдельная тема для разговора (в MongoDB можно использовать разные JS-движки). Ускорить работу этой операции можно масштабированием. Если мы размажем нашу базу по кластеру, то маппинг будет производиться одновременно на всех машинах и обрабатывать разные данные, и в итоге получается, что скорость работы маппинга прямо пропорциональна количеству серверов в шардированной базе. Это пожалуй один из основных минусов этой БД при использовании на одном сервере.
Также для агрегации имеется функция group, но мы её не рассматриваем, потому как она не будет работать в масштабированной архитектуре и, не смотря на то что выполняется заметно быстрее map/reduce, она все же не решает вышеописанной проблемы и при определенном количестве записей все-равно тормозит.
Администрирование
Рассмотрим некоторые аспекты администрирования БД. Полная документация находится здесь.
Перенос БД / Резервное копирование
Для создания горячей копии БД удобно использовать утилиту mongodump, которая по умолчанию идет в пакете с сервером. Создание бекапа всей БД сводится к одной команде, например: mongodump -d DATABASE_NAME
На выходе мы получаем папку с файлами в формате BSON. Эту папку используем для восстановление бекапа, например: mongorestore BACKUP_FOLDER
.
Прочитайте соответствующий раздел документации.
Интерактивная консоль
Описанная выше интерактивная консоль является отличным инструментом администрирования БД. При помощи нее мы можем тестировать запросы, проверять соединение, создавать индексы, просматривать статус выполнения текущей операции и вообще любые другие административные функции.
Административный интерфейс
Для MongoDB есть несколько визуальных инструментов для администрирования, среди которых есть нативные клиенты для OS X и .NET, а также веб-интерфейсы на PHP, Python, Ruby. Найти их можно в соответствующем разделе.
Резюме
Если оценивать относительно Drupal, то MongoDB конечно же не претендует на полноценную замену привычного нам MySQL или PostgreSQL. Его можно использовать для увеличения быстродействия отдельных элементов архитектуры и особенно узких мест. Например, неплохим решением было бы использование этой БД для ведения статистики, системы кэширования, хранения пользовательских сессий, ведения системного журнала watchdog, системы управления очередями и т.д. Помимо этого, модули могут активно использовать эту БД для хранения своих данных если в этом есть выгода.
Комментарии
21 октября, 2010
Отличный материал. Хотелось бы больше практических примеров.
Хотите что-то добавить?