Riak и Riak Search. Первое знакомство

от автора

В статье в ознакомительных целях рассматривается процесс создания простого хранилища простых текстовых документов на базе Riak версии 2.1.1 и организация поиска по ним с помощью Riak Search (Yokozuna). В качестве клиентской библиотеки используется официальный клиент для Erlang.

Для начала представим, что у нас есть огромное количество таких документов:

  • title — заголовок;
  • body — содержимое;
  • tags — тэги;
  • created_at — время создания;
  • smiles — количество смайликов (плюсиков, лайков, как хотите)

и огромное количество пользователей, желающих их изменять. Кому интересно, начнём.

Я подразумеваю, что читатели уже имеют некоторое представление о Riak. Если нет, то лучше сначала почитать здесь и тут, или, конечно, официальную документацию.

Установка и начальная настройка

Для запуска Riak Search в системе должна быть установлена Java. Установить сам Riak в OSX можно через Homebrew, а Erlang, если потребуется, будет установлен автоматически:

brew install riak 

Для наших учебных целей нет смысла разворачивать целый кластер, можно ограничиться всего одним узлом, поэтому перед запуском потребуется выполнить лишь минимальную настройку. В конфиге нужно активировать Riak Search:

## To enable Search set this 'on'. ## ## Default: off ## ## Acceptable values: ##   - on or off search = on 

Если Riak установлен с помощью Homebrew, то конфиг лежит здесь — /usr/local/Cellar/riak/2.1.1/libexec/etc/riak.conf

Еще потребуется увеличить лимит на открытые файлы, если этого не сделать, при запуске будет выведено предупреждение. В OSX Yosemite я сделал так:

echo kern.maxfiles=65536 >> /etc/sysctl.conf echo kern.maxfilesperproc=65536 >> /etc/sysctl.conf sudo sysctl -w kern.maxfiles=65536 sudo sysctl -w kern.maxfilesperproc=65536 echo ulimit -n 65536 65536 >> ~/.bash_profile ulimit -n 65536 65536 

Теперь можно запустить Riak:

riak console 

И можно протестировать из другой shell-сессии:

riak-admin test 

Ответ должен быть примерно таким: Successfully completed 1 read/write cycle to ‘riak@127.0.0.1’

Подробнее об установке и настройках:

Riak Data Types

Eventually Consistent хранилища, к которым относится Riak, допускают возникновение т.н. Data Incostistent ситуаций, когда содержимое одного и того же ключа на разных репликах отличается. В зависимости от настроек, Riak может попытаться решить конфликты сам с помощью vector clocks или timestamps, или же переложить обязанность определить правильную версию значения на приложение, предоставив ему все имеющиеся версии (siblings). В реальной ситуации, если ваши документы будут редактироваться в многопользовательском режиме и реплицироваться на несколько узлов, мердж конфликтных данных может стать весьма непростой задачей. В этом случае, возможно, лучшим решением будет использование Riak Data Types (также известных как CRDT). Эта технология позволяет описать данные с помощью специальных типов, которые возьмут на себя решение задачи конвергентности данных кластера и освободят приложение от обязанностей по решению конфликтов.

Riak Data Types, на текущий момент, реализуют следующие пять типов CRDT:

  • flag — Битовый флаг. Доступные операции — снять, установить. Может использоваться только внутри map;
  • counter — Счётчик. Доступные операции — увеличить, уменьшить. Тоже может использоваться только внутри map;
  • register — Какое-либо значение (хранится как строка);
  • set — Множество значений. Доступные операции — добавить элемент, удалить элемент;
  • map — Контейнер для других типов. Позволяет хранить внутри себя флаги, счётчики, регистры, множества и вложенные map’ы. Доступные операции — добавить поле, удалить поле, а также операции для внутренних полей, соответствующие их типам

В соответствие с этими типами, будем представлять наши документы как map со следующей структурой:

  • title — register;
  • tags — set;
  • body — register;
  • created_at — register;
  • smiles — counter

Создадим и активируем bucket-type под названием documents-type для хранения наших документов:

riak-admin bucket-type create documents-type '{"props":{"datatype":"map"}}' riak-admin bucket-type activate documents-type 

Более подробная информация по Eventually Consistent, Riak Data Types и CRDT:

Riak Search

Мы хотим реализовать поиск наших документов по тэгам, по заголовку, по дате создания и по содержимому. Для этого мы будем использовать технологию Riak Search под кодовым названием Yokozuna, которая по своей сути является посредником между хранилищем Riak и поисковиком Apache Solr. Yokuzuna сама запускает и мониторит на каждом узле кластера отдельный JVM-процесс с Solr, передаёт ему поисковые запросы и изменения в данных.

Для того, чтобы Solr знал как индексировать наши документы, нам необходимо создать поисковую схему. Вообще Riak Search имеет и дефолтную схему на все случаи жизни — _yz_default, которую удобно использовать во время разработки, но для рабочего окружения лучше создать свою.

Так как структура данных у нас уже определена, мы создадим схему сразу. В схеме нужно перечислить поля документа, для каждого из них указать тип, необходимо ли строить по нему индекс и хранить его значения, чтобы потом вернуть их в поисковой выдаче. Также в схему нужно обязательно включить служебные поля Riak Search. Следует заметить, что, при использовании Riak Data Types, к названиям полей добавляется суффикс, соответствующий их типу. Таким образом, у нас получится следующее описание:

    <field name="title_register" type="string" indexed="true" stored="false" multiValued="fase" />     <field name="body_register" type="text_ru" indexed="true" stored="false" multiValued="true" />     <field name="tags_set" type="string" indexed="true" stored="false" multiValued="true" />     <field name="created_at_register" type="tdate" indexed="true" stored="false" multiValued="false" omitNorms="true" /> 

Под спойлером полное содержимое файла со схемой:

docs_seacrh_schema.xml

<?xml version=«1.0» encoding=«UTF-8» ?>
/>
/>
/>
/>

/>

/>
/>
/>
/>
/>
/>
/>
/>
/>

_yz_id

/>
/>

/>
/>
/>
/>

/>

/>

Подробности по Riak Search:

Доступ к Riak из Erlang

Пришло время наконец подключиться к нашей ноде с помощью клиента для Erlang:

# скачаем и скомпилируем клиент: git clone https://github.com/basho/riak-erlang-client.git cd riak-erlang-client/ make cd .. # запустим REPL: erl -pa riak-erlang-client/ebin riak-erlang-client/deps/*/ebin 

Подключаемся

{ok, RiakPid} = riakc_pb_socket:start_link("127.0.0.1", 8087). % Проверим соединение pong = riakc_pb_socket:ping(RiakPid). 

Создаём поисковую схему и индекс

{ok, Schema} = file:read_file("docs_search_schema.xml"). ok = riakc_pb_socket:create_search_schema(RiakPid, <<"documents-schema">>, Schema). ok = riakc_pb_socket:create_search_index(RiakPid, <<"documents-index">>, <<"documents-schema">>, []). 

Создаём bucket и назначаем поисковый индекс

ok = riakc_pb_socket:set_search_index(RiakPid, {<<"documents-type">>, <<"documents-bucket">>}, <<"documents-index">>). 

Создаём новый документ

Map = riakc_map:new().  % Заголовок документа - регистр (register) Map1 = riakc_map:update({<<"title">>, register},                         fun(Reg) ->                             riakc_register:set(<<"DocumentTitle">>, Reg)                         end,                         Map).  % Тело документа - тоже регистр (register) Map2 = riakc_map:update({<<"body">>, register},                         fun(Reg) ->                             riakc_register:set(<<"Some Document Body">>, Reg)                         end,                         Map1).  % Тэги - множество (set) Map3 = riakc_map:update({<<"tags">>, set},                         fun(Set) ->                             Set1 = riakc_set:add_element(<<"Tag One">>, Set),                             Set2 = riakc_set:add_element(<<"Tag Two">>, Set1),                             Set2                         end,                         Map2).  % Дата создания - регистр (register) Map4 = riakc_map:update({<<"created_at">>, register},                         fun(Reg) ->                             % Cледует заметить что Solr понимает только даты в формате ISO8601.                             % https://cwiki.apache.org/confluence/display/solr/Working+with+Dates.                             ISODateFmtStr = "~4.10.0B-~2.10.0B-~2.10.0BT~2.10.0B:~2.10.0B:~2.10.0BZ",                             {{Year, Month, Day}, {Hour, Min, Sec}} = calendar:universal_time(),                             ISODate = list_to_binary(io_lib:format(ISODateFmtStr, [Year, Month, Day, Hour, Min, Sec])),                              riakc_register:set(ISODate, Reg)                         end,                         Map3).  % Выполним операции по созданию нашего документа в хранилище MapOperations = riakc_map:to_op(Map4). ok = riakc_pb_socket:update_type(RiakPid, {<<"documents-type">>, <<"documents-bucket">>}, <<"DocumentKey">>, MapOperations). 

Находим и получаем документы

% Загрузим определения записей Riak Client. rr("riak-erlang-client/include/riakc.hrl").  % Пример запроса по заголовку тэгу, содержимому и дате создания {ok, Results} = riakc_pb_socket:search(RiakPid, <<"documents-index">>, <<"title_register:DocumentTitle AND tags_set:\"Tag One\" AND body_register:Some AND created_at_register:[1972-05-20T17:33:18Z TO NOW]">>).  % Получение документов Docs = Results#search_results.docs. lists:foldr(fun({_Index, Doc}, Acc) ->                 {_, DocumentId} = lists:keyfind(<<"_yz_rk">>, 1, Doc),                 {ok, {map, Image, _, _, _}} = riakc_pb_socket:fetch_type(RiakPid, {<<"documents-type">>, <<"documents-bucket">>}, DocumentId),                 Image             end,             [],             Docs). 

Меняем документ

riakc_pb_socket:modify_type(RiakPid, fun(Map) ->                                          % Обновим содержимое                                          UpdatedMap1 = riakc_map:update({<<"body">>, register},                                                                  fun(Register) ->                                                                      riakc_register:set(<<"Новое содержимое">>, Register)                                                                  end, Map),                                           % Удалим тэг                                          UpdatedMap2 = riakc_map:update({<<"tags">>, set},                                                                  fun(Set) ->                                                                      riakc_set:del_element(<<"Tag One">>, Set)                                                                  end, UpdatedMap1),                                           % Добавим 10 смайликов                                          UpdatedMap3 = riakc_map:update({<<"smiles">>, counter},                                                                  fun(Counter) ->                                                                      riakc_counter:increment(10, Counter)                                                                  end, UpdatedMap2),                                          UpdatedMap3                                      end,                             {<<"documents-type">>, <<"documents-bucket">>}, <<"DocumentKey">>, []).  % Проверим riakc_pb_socket:search(RiakPid, <<"documents-index">>, <<"body_register:\"Содержимое\"">>). 

Удаляем документ

riakc_pb_socket:delete(RiakPid, {<<"documents-type">>, <<"documents-bucket">>}, <<"DocumentKey">>).  % Проверим riakc_pb_socket:search(RiakPid, <<"documents-index">>, <<"body_register:\"Содержимое\"">>). 

Больше информации по работе с клиентской библиотекой:

Пока на этом всё. Если эта статья окажется полезной, то в следующий раз напишу о том, как для всего этого сделать веб-интерфейс на базе cowboy.

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


Комментарии

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

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