...

понедельник, 1 июля 2013 г.

[Из песочницы] Riak-js. Основы использования и трудности поиска

Meta




Доброго времени суток!

В данный момент я работаю над достаточно большим проектом, состоящим из нескольких модулей, и использующий разные технологии. Но сам сайт, а точнее его back-end написан целиком на Node.js, а Riak является основным хранилищем. Ничего не буду писать про сам Riak, на хабре и так есть отличная обзорная статья.


Как и для любой другой NoSQL базы данных, чтобы интегрировать функциональность БД в Node.js вам необходимо использовать драйвер или клиент этой базы данных, кому как нравится называть. Вам это надо для удобства пользования и составления запросов к БД, конечно вы можете это делать и напрямую, используя незатейливую команду curl.


Сразу хочу оговориться, что клиенты или драйвера для различных NoSQL БД называют по-разному, я же буду говорить или как об ORM или как о клиенте конкретной ДБ. Кстати, имено так о себе и пишут в Riak-js репозитории:



Node.js client for Riak.



Вот некоторые, а возможно что и все Node.js клиенты для riak



  • riak-js — используемый в нашем проекте

  • Simpleriak

  • Riak-PB — использует protobuff, может быть немого быстрее


Из-за незначительного опыта работы с последними двумя, сказать мне вообщем-то про них нечего, поэтому дальше речь пойдет только riak-js.


Немного основ


В принципе работать с Riak-js очень просто, как и с большинством других DB ORM. Достаточно легко и без всяких сложностей можно научится делать запросы к базе данных, а затем обрабатывать полученный ответ. Если вы раньше работали, например с mongoose ( MongoDB ORM для Node.js), то вам не составит никакого труда освоиться и с Riak-js.


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


Рассмотрим функцию get:



//users - корзина (bucket), аналог таблицы в реляционных БД
//id - искомый id, можно и хардкодом. ID'шники в Riak выглядят так NrIKwuvHZmJNIoQc8PeP8s12ic4
//3 параметр - это конечно callback
db.get('users', id, function(err, result){
console.log(result);
})


В примере выше, result будет красивенький json файл:



{ name = {
fname: 'Ivan',
lname: 'Ivanov'
},
age: '30',
city: 'New York',
hobbies: ['football', 'programming', 'reading old books']
}


Другой пример, на этот раз мы будем сохранять объект в базу данных. Хочу заметить, что сохранять мы можем что угодно и куда угодно. Key создается автоматически и по дефолту не входит в состав значения, но в данном примере я покажу как его можно сохранить, как ID для дальнейшего использования, когда вам понадобится где-то указывать ID объекта или иметь специальный идентификатор.



db.save('users', ' ' , result, function(err, user, meta){
if(err){
throw err;
}

user.id = meta.key; // Маленький хак чтобы сохранить key, как ID юзера. Объект meta тянет на отдельную статью,
// и суть его я расскрывать здесь не буду
db.save('users', user.id, user);

});


Теперь в нашей базе, в корзине users, есть 2 практических одинаковых юзера



// Юзер которого мы доставали в первом примере, у которого id еще не является частью всего значения
// key: 'NrIKwuvHZmJNIoQc8PeP8s12ic4'
{ name = {
fname: 'Ivan',
lname: 'Ivanov'
},
age: "30",
city: "New York",
hobbies: ['football', 'programming', 'reading old books']
}

// Наш сохраненный юзер, где мы сохраняем key в поля id, уже после того как создали юзера
// Фактические мы его перезаписываем (update)
{ name = {
fname: 'Ivan',
lname: 'Ivanov'
},
age: "30",
city: "New York",
hobbies: ['football', 'programming', 'reading old books'],
id: "MFz6fqzxATUxelg69LrZjEysOCx" // meta.key
}



Поиск


Ок, но что если нам нужно найти группу юзеров или сделать более серьезный запрос. То мы можем использовать search.find


Например давайте искать всех юзеров старше 18, которые живут в Нью-Йорке.



db.search.find('users', ' age > 18 AND city = "New York" ', function(err, users){
//Если надо вставить переменную, то делаем так :
// db.search.find('users', ' age > 18 AND city = " ' + city + ' " ', function(err, users){
if(err){
throw err;
}

//Обратите внимание, что результатом нашего запроса будет массив из юзеров
console.dir(users);

});


Однако, если используя db.get или db.getAll мы получаем красивенькие json файлы, то теперь результат придет в немного измененном виде.



{ id: 'MHAHovXhEWq82HBWCcwyBdyQ6jA', // id юзера который удовлетворяет условиям поиска
index: 'users',
fields:
{ name_fname: 'Ivan', // так станет выглядить наш объект name
name_lname: 'Ivanov',
age: '30',
city: 'New York',
hobbies: 'football programming reading old books', // А так станет выглядить массив ['football', 'programming', reading old books'']
},
{ id: 'MHAHovXhEWq82HBWCcwyBdyQ6jA', // id 2-ого юзера который удовлетворяет условиям поиска
....


Как вы видите с таким файлом достаточно неудобно работать, например если вам надо отрендерить hobbies, то нормально это сделать путем простого перебора элементов не получаться, придется придумывать велосипеды типа split(' ') и это только в том случае если у вас нету сложных-составных элементов, как у меня в примере 'reading old books'. В этом же случае, вам придется на стадии сохранения каждого хобби в массив заменять пробелы в составных элементах на "_" и только потом делать split(' '). А если у вас несколько вложенных объектов и несколько массивов в них? И вам надо подсчитать их длину? Тут придется браться за AJAX, что не всегда удобно и быстро. Стоит все же отметить, что даже делать второй отдельный запрос, чтобы узнать длину hobbies будет быстрее, чем совать AJAX функцию на страницу.


Чтобы этого избежать и не мучиться с корявым json файлов, лучше всего подойдет вместо db.search.find использовать MapReduce. Что это такое и о преимуществах использования данного метода вы можете прочитать во все той же статье, ссылку на которую я давал выше.


Справедливости ради стоит отменить, что такое кривое отображения файлы происходит не по вине riak-js, а из-за search-механизма самого Riak. Вы получите точно такой же результат, если будите запрашивать что-нибудь напрямую



curl "http://localhost:8098/solr/users/select?q=city:New*&wt=json"


Вернемся к MapReduce.


Давайте поищем тех самых юзеров из Нью-Йорка старше 18. Для это напишем простенькую функции к которой будем обращаться.



function getUsers(callback){
db.mapreduce.add('users') //название корзины
.map('Riak.mapValuesJson') //метод который используем на этапе map
.run(function (err, data) {
if (err) {
throw err;
}

for (var i = 0; i < data.length; i++) {
if(data[i].age > 18 && data[i].city.match('New York')){
console.dir(data[i]);
}
}

callback(null, data);
});
}


В результате мы получим неизмененный json файл,, содержащий всех юзеров, которые удовлетворяют условиям нашего поиска. Я считаю, что этот подход наиболее правильный и гибкий, по сравнению с db.search. Во-первых мы используем MapReduce, что само по себе является одной из причем почему был выбран именно Riak в качестве БД (учитывая, что все это потом будет запускаться именно на серверах amazon), а во-вторых вы имеете намного больше возможностей в составлении запросов, используя любимый всеми нами JavaScript.


Результатом будет массив из юзеров



[{ name = {
fname: 'Ivan',
lname: 'Ivanov'
},
age: "30",
city: "New York",
hobbies: ['football', 'programming', 'reading old books']
// key: 'NrIKwuvHZmJNIoQc8PeP8s12ic4', нету id, так как мы его не сохраняли
},
{ name = {
fname: 'Ivan',
lname: 'Ivanov'
},
age: "30",
city: "New York",
hobbies: ['football', 'programming', 'reading old books'],
id: "MFz6fqzxATUxelg69LrZjEysOCx" // meta.key
},
{ name = {
fname: 'Petr',
lname: 'Petrov'
},
age: "22",
city: "New York",
hobbies: ['hunting'],
id: "UvCI0FwqvUK3I8ZzNh46IlylI2q" // meta.key
},
...
]


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


docs.basho.com/riak/latest/tutorials/querying/MapReduce/#When-to-Use-MapReduce



When you want to return actual objects or pieces of the object – not just the keys, as do Search & Secondary Indexes



В вольном переводе: «Когда вы хотите получить непосредственно сам объект или его части, а не только ключи (id), как это происходит при Search или использовании Secondary Indexes»


Заключение


Riak — мощная и надежная БД, с которой при этом легко работать. Riak-js мог бы быть немного и лучше, но вцелом вся функциональность есть и пользоваться можно. Что касается поиска, то вместо search, лучше использовать MapReduce — это, то ради чего и создавался Riak, для хранения огромных массивов данных и быстрого поиска по ним используя свободные кластеры.


Ситуацию с поиском может поправить yokozuna, но как пишут сами разработчики. Yokozuno — это прототип и находится в стадии разработки, и не рекомендуется к использованию в данный момент.


ps.


Буду рад любой критике и комментариям.


This entry passed through the Full-Text RSS service — if this is your content and you're reading it on someone else's site, please read the FAQ at fivefilters.org/content-only/faq.php#publishers. Five Filters recommends: 'You Say What You Like, Because They Like What You Say' - http://www.medialens.org/index.php/alerts/alert-archive/alerts-2013/731-you-say-what-you-like-because-they-like-what-you-say.html


Комментариев нет:

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