Riak-js. Основы использования и трудности поиска

в 19:17, , рубрики: json, map reduce, node.js, nosql, riak, search, баги, Веб-разработка, метки: , , , , ,

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

Источник

* - обязательные к заполнению поля


https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js