Обзор Riak
Riak — распределенная
opensource база данных, разработанная на Erlang и
спроектированная в расчете на:
- Высокую доступность и устойчивость к сбоям;
- Масштабируемость и простоту обслуживания;
- Универсальность.
У проекта отличная официальная документация на английском, далее же в этой статье я расскажу об основных её особенностях чуть подробнее, а также хитростях и подводных камнях, выявленных в процессе применения на практике (с перспективы веб-разработки).
Высокая доступность и устойчивость к сбоям
- Все данные в кластере реплицируются по принципу соседей на хэш
кольце (см. логотип для иллюстрации) и даже в случае сбоев
доступны посредством интеллектуального перенаправления запросов
внутри кластера. - В случае возникновения коллизий из-за разрыва сетевого соединения
или просто одновременной записи, на запрос получения данных может
вернуться несколько версий и приложение само может решить как их
объединить или какую версию использовать.
Масштабируемость и простота обслуживания
- Добавление нового сервера тривиально путем копирования конфига и
одной команды. - Перераспределение данных и все остальное прозрачно происходит за
сценой. - Минимальный рекомендуемый размер Riak кластера — 5 серверов, меньшее
количество не дает раскрыть весь потенциал. - Одинаково легко обслуживать как маленький, так и большой кластер.
- Есть коммерческая Enterprise версия с поддержкой от Basho,
компании-разработчика Riak (изначально выходцы из Akamai),
равноправной зашифрованной репликацией между датацентрами и
поддержкой SNMP. - Есть встроенный веб-интерфейс для мониторинга и управления
кластером, у меня правда так и не дошли руки его освоить:
Универсальность
- Схема отсутствует, ключи и данные — произвольные бинарные строки.
Ключи располагаются в пространствах имен (bucket). - Сериализация — на усмотрение разработчика, популярные варианты —
Erlang’овский BERT, JSON для других платформ, можно использовать
просто как файловую систему. - Модульная система хранилищ данных, альтернатив много, основная —
Google LevelDB; еще интересный
вариант с хранением полностью в оперативной памяти — получается
продвинутый распределенный кэш с репликацией, поиском и пр. - Гибко настраиваемое количество узлов кластера, которые должны
подтвердить успешность операции, чтобы она считалась успешной: можно
указывать для всего кластера, пространства имен и даже конкретного
запроса. Riak в любом случае остается eventually consistent базой
данных (AP из CAP теоремы), но с возможностью управлять балансом
производительности операций и надежностью выполнения запросов. - Три интерфейса доступа (API):
- Google ProtocolBuffers — для основного
использования в боевых условиях. - HTTP REST — для использования в
языках, где нет готового клиента на ProtocolBuffers и для того,
чтобы по-быстрому что-то посмотреть из консоли через curl. Хотя
по факту клиенты для большинства языков программирования есть и
проще делать запросы через интерпретатор. - Еще есть прямой интерфейс Erlang-сообщений, но даже из самого
Erlang им пользоваться не рекомендуют, не говоря уже о
реализациях Erlang node (BERT) на других платформах.
- Google ProtocolBuffers — для основного
- Вместе с данными хранятся метаданные для разных целей, которые
используются в соответствующих типах запросов:- Векторные часы
для разрешения конфликтов версий данных (обязательно, есть
автоматическое разрешение); - Индекс для полнотекстного поиска (концептуально позаимствован у
Lucene/Solr, опционально); - Индекс для простых выборок (по бинарным и числовым полям,
опционально); - Связанные ключи (отдаленный аналог внешних ключей,
опционально).
- Векторные часы
- Встроенная поддержка MapReduce, фазы можно
реализовывать на Erlang или
JavaScript; для обоих языков есть библиотека с
наиболее популярными случаями, которые можно использовать для
образца. - Есть поддержка выполнения операций до/после операций записи/чтения
(hooks), чаще всего используются для построения полнотекстного
индекса, но можно реализовать и свои, специфичные для приложения.
Недокументированные возможности
Пока я их нашел всего две:
- Счетчики: как такового API в для увеличения/уменьшения числовых
значений (increment/decrement) в Riak нет, так как он не лезет
внутрь хранящихся данных. Зато есть векторные часы, которые растут с
каждой операцией записи по ключу. Чтобы реализовать увеличение
(increment) необходимо записать в Riak пустую бинарную строку с
опцией return_body, и у вернувшегося значения сложить все поля в
векторных часах. Пример на
Erlang. Если нужно еще и
уменьшение (decrement) этого можно добиться с помощью пары счетчиков
«плюс и минус» и вычитать второе значение из первого. Для авто
инкремента основных ключей не самый лучший вариант, но для не особо
критичных случаев вполне себе работает. - Выборка по списку ключей (multiget): такого API тоже нет, но
здесь на выручку приходит MapReduce. Это, пожалуй, наиболее
популярное его применение. На вход подаем имеющийся список ключей и
используем фазы из готовой библиотеки: reduce_set_union и
map_identity. Данные возвращаются неотсортированные и требуют
небольшой обертки на выходе, но все равно это намного быстрее, чем
последовательно проходить по списку ключей и делать для каждого
обычный get. Пример на Erlang.
Вам подобные трюки в комментариях.
Подводные камни
- Если в Вашем приложении необходима функциональность постраничного
просмотра отсортированных данных (pagination), то будьте готовы
реализовать её на клиенте. То есть Riak быстро сделал нужную выборку
всех «страниц» и уже на клиенте её придется отсортировать и выкинуть
лишнее. Вообще в большинстве случаев результаты запросов к Riak
приходят в произвольном порядке из-за его распределенной природы. - В продолжение к предыдущему: в REST Solr интерфейсе есть
аргументы (в ProtoBuf это тоже добавили в одной из последних
версий), которые, казалось бы, достаточны для реализации
постраничного просмотра: sort, start, rows — что еще
нужно? На практике оно работает не так, как было бы логично.
Сортировка по значению (заданная в sort) применяется ПОСЛЕ того, как
была отсчитана страница по start и rows. Они отмеряются по ключам
или рейтингу значения в полнотекстном поиске и никак иначе. С тем же
успехом эти 5-10 значений можно очень быстро отсортировать и на
клиенте. Зачем-то это может быть и нужно, но в моем случае оказалось
совершенно бесполезно. - У Riak есть 4 основных типа запросов: простой
get/set, полнотекстовый поиск, вторичные ключи (secondary
indices), МapReduce и проход по связанным ключам (link walking).- Если Ваши данные являются сериализованным JSON, BERT или XML, то
в большинстве случаев Вам нужны лишь первые два из них,
исключение — упомянутая выше выборка по списку ключей через
MapReduce. - Основной сценарий использования вторичных индексов — метаданные
к произвольным неструктурированным бинарным данным, например в
случае с аналогом файловой системы. Либо совсем примитивные
случаи, когда правда нужно сделать простую выборку по одному
целочисленному полю, что бывает редко. - Если данные сериализованы, то связанные ключи проще хранить
внутри данных, а не средствами СУБД. Разницы в
производительности нет, в итоге делается тот же MapReduce с теми
же фазами.
- Если Ваши данные являются сериализованным JSON, BERT или XML, то
- Хоть Riak «из коробки» и правда надежнее многих других СУБД и 1-2
упавших/отключенных сервера в кластере внешне практически не
заметны, есть одно но. Если один узел упал — соединения всех
подключенных к нему клиентов теряются. Два основных
пути преодоления этого момента:- Если кластер клиентов и кластер Riak расположены на разных
серверах, то между ними можно поставить отказоустойчивый TCP
балансировщик нагрузки, в частности HAProxy или
IPVS здесь наиболее органично вписываются. - Если на одних и тех же, то есть вариант поставить балансировщик
нагрузки перед клиентами (для веба возможно и в HTTP/HTTPS
режиме), а каждый клиент подключается к своему локальному
серверу Riak и если один, другой или оба сразу упали, то
отрубать весь физический сервер целиком.
- Если кластер клиентов и кластер Riak расположены на разных
Выводы
Riak отлично подходит для многих вариантов использования, как в Интернет
среде, так и в смежных вроде телекома. Обладает отличным набором
положительных «черт характера», о которых шла речь в начале статьи.
Прекрасно справляется с большим потоком как операций записи, так и
операций чтения.
Как уже упоминалось, практически единственный сценарий, где Riak совсем
не справляется, это выборки по большим объемам данных с сортировкой и
постраничным выводом. Но даже в этом случае никто не мешает использовать
отдельный сервис, который будет индексировать нужным образом данные и
подготавливать список идентификаторов для последующей multiget выборки
из Riak. К слову, проекты по этой части уже появляются, например
Yokozuna — интеграция
полноценного Solr с Riak (Riak Search — лишь частичный порт Solr+Lucene
на Erlang).
Дополнительная информация по теме
- Riakcon 2012 — свежая
конференция от Basho, рассказывают о различных сферах применения
Riak в реальных проектах - Список компаний, использующих Riak — можно увидеть очень солидные известные имена
- Сравнение Riak с другими NoSQL СУБД
- Документация по API
- Официальный GitHub и список сторонних opensource проектов
- Сравнительная таблица MapReduce, Secondary Indices и Link Walking
Источник: Обзор Riak