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.

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

ссылка на оригинал статьи http://habrahabr.ru/post/185254/


Комментарии

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *