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.
Буду рад любой критике и комментариям.
Автор: 505abc