10 августа 2011 г.

Атомарность в MongoDb, хранимые функции, выполнение кода на стороне сервера

Атомарность и Lock в MongoDb
Как известно, атомарность операций типа update не присуща MongoDb. Это создаёт некоторые трудности. В качестве примера можно привести классическую проблему с инкрементом поля в записи. В случае с атомарными операциями мы получили бы следующее:
Клиент 1: в записи значение 1, запрос, в записи значение 2
Клиент 2: ------------------------------ в записи значение 2, запрос, в записи значение 3 ...
...
В случае с Mongo:
Клиент 1: в записи значение 1, запрос, в записи значение 2

Клиент 2: в записи значение 1, запрос, в записи значение 2
...


Это объясняется тем, что Mongo предоставляет клиентам 2, 3 и т.д. те данные, которые сохранены персистентно в самой свежей ревизии записи в коллекции, не дожидаясь, пока клиент 1 закончит свою операцию. На этом в Mongo строится неблокирующий доступ к данным (клиенты не дожидаются друг друга). Чуть подробнее про неблокирующий доступ к общим ресурсам можно узнать на Википедии и в специализированных статьях, а мы пойдём к простому примеру и решению проблемы.


Пускай наше приложение занимается учётом переходов по некоторой ссылке, при каждом запросе увеличивая значение числа переходов в записи, соответствующей URL'у ссылки.
Наверное, более правильно было бы не увеличивать значение поля, а сохранять в таблице (коллекции) каждый запрос, и когда какому-нибудь клиенту потребуются данные о количестве переходов, производить подсчёт количества записей, однако здесь на лицо другие проблемы — требуется больше памяти, необходимо удаление старых записей и т.п.
Создадим коллекцию, содержащую запись о переходах по URL'у этого блога в MongoDb:

> var a = {queries: 0, url:'http://joydevel.blogspot.com/'};
> db.stat.save(a);
Оказывается, дальше всё очень просто. Согласно документации MongoDb, Mongo позволяет выполнять на сервере JS-код. Сам js-код выполняется в javascript-машине MongoDb (spidermonkey) в основном потоке. Как известно, js-код не реализует многопоточность. И как нас особо предупреждает документация, выполнение js-кода блокирует работу базы. Таким образом, мы получаем средство для реализации блокировки.
API для выполнения js-кода на стороне сервера предусматривает метод db.eval, получающий на вход js-функцию и параметры к ней.
Создадим такую функцию:

var incQueriesForPage = function( url ) { 
urlRecord = db.stat.findOne( { "url" : url } );
if (urlRecord != null) {
urlRecord.queries++;
db.stat.save(urlRecord);
return urlRecord.queries;
}
else return false;
}
Теперь, чтобы производить атомарный инкремент поля queries, мы просто вызываем эту функцию через db.eval вот так:


На этом всё про атомарность, реализуемую с помощью блокировок.

Хранимые функции
Порой появляется необходимость в обновлении данных в базе скопом. Конечно, можно посылать для каждого обновления запрос к базе, что будет нагружать как сервер СУБД, так и канал до него. Однако, можно пойти иным путём: создать хранимую функцию, реализующую обновления необходимых объектов и сохранить её прямо в базе. Для этого MongoDb предоставляет удобный API db.system.js. Сохраним необходимую функцию:
> db.system.js.save ({_id:"someFunc", value:function(x) {return x*x;}});
db.system.js ведёт себя как обычная коллекция, поэтому код вызова только что сохраненной функции выглядит так:

> db.eval(db.system.js.findOne({_id:"someFunc"}).value, 15);
225


Использовать хранимые функции можно точно также, как и stored procedures в MySQL, Postgresql, Oracle.
На этом, пожалуй, всё. Не забывайте, что выполнение js на server-side'е блокирует доступ! ;-)



Отправить комментарий