RethinkDB: Введение

rethinkdb

Давненько не писал, а всё потому что всё время нашей игре уделяю. Меня часто спрашивают, какие технологии мы используем. В особенности про базы данных много вопросов. Когда слышат, что используем RethinkDB, то просят поделится инфой о том, как ведёт себя база и т.п. Тесты с большой нагрузкой мы пока не устраивали (надеюсь в ближайшие месяцы потестим, тогда поделюсь циферками). Пока же решил пару вводных статей о Ресинке написать. Вообще, удивительно, что в ру сегменте про базу почти нет информации. Хотя её можно в один ряд с риаком, arangodb, или той же MongoDB поставить.

Небольшое отступление

Первоначально мы вообще начинали с Riak‘а. Но у нас с ним вечно проблемы были: то Ring разваливался, то тупо падал риак целиком, плюс с репликацией вопросы были. Если вы читаете эту статью, то в курсе должны быть про CAP (теорема Брюера). Если нет, то я ниже расскажу о ней, и том, чем отличается RethinkDB от Riak’а приминительно к этой теореме (в этом плане Rethink ближе к MongoDB).

Наверно стоит сказать, почему мы выбрали именно Rethink. Ответ: хрен его знает. Первоначально меня привлекла простота, ReQL (это их язык запросов), классное комьюнити. Но с каждым днём, Rethink всё больше покорял моё сердце. Чего не скажешь о Riak -.- Если в случае с Riak’ом, как правило, открытия были не из приятными, то Rethink наоборот же, приятно удивлял. Естественно и у него есть свои минусы (куда уж без них), но в целом я доволен это базой.

Комьюнити

Я имею ввиду в первую очередь самих разработчиком. Во-первых, радуют довольно частые обновления. Если в случае с Kubernetes, я на подобное жаловался (там постоянно что-то отваливалось после обновлений), то у Rethink’а с этим всё замечательно. Если обновление минорно, то вам просто надо обновить Ресинк (ну да, остановив базу перед этим). Если же надо обновиться до мажорной версии, то надо просто забекапить данных и накатить потом на новую версию. К тому же, нынче все индексы так же в дамб попадают, так что обновление проходит без проблем.

Плюс разработчики с радостью идут на контакт. Не так давно даже в слаке сделали канал. Никогда не игнорят, всегда помогут с проблемами/вопросами. Да и вообще довольно доброжелательные ребята.

В общем про RethinkDB

RethinkDB — это open source NoSQL база данных (сорсы на гитхабе), располагающая встроенной системой уведомления об изменениях. Вместо запросов к базе на наличие изменений, вы можете «подписываться» на потоковые обновления, что устранит ненужные запросы к базе. Правда, у этого фичи есть и свои минусы: вы не можете продолжить фид с того места, где закончили, в случае обрыва соединения с ресинком. Вроде бы обещали в версии 2.2 запилить эту фичу. В момент подписки вам будет выдаваться уникальный id. После чего вы в любой момент сможете продолжить получать изменения (и получить пропущенные изменения, к примеру, при востановлении соединения с базой).

Шардинг/репликация

Довольно просто настроить кластер с шардингом и репликацией данных. Можно почитать в доках про это. Мало того, что сам кластер легко в конфигах настраивается, так ещё и шардинг таблиц легко настроить из веб-морды. С недавних пор (ура! ура!) появился автоматический failover для кластера из 3-х и более машин.

Сплит поинт ресинк выберет автоматически и разделит данные по шардам. Правда, автоматически ребалансить в будущем он не будет. В итоге может статься, что количество документов в щардах будет сильно не равным. Но всегда можно ручками ребаланснуть шарды из гуятины.

Вы можете сами настроить число реплик и шардов для каждой таблицы (или сразу для всех таблиц). Вам нет необходимости напрямую указывать связь между таблицей и сервером. RethinkDB использует определённые эвристики, чтобы настроить всё оптимальным путём.

Если хочется побольше контроля, то можно настроить реплики с привязкой к серверный тегам (server tags). Каждому серверу можно назначить один и более тегов, и каждая таблица может иметь определённое число реплик закреплённых за конкретным тегом. К примеру, есть у вас 6 машин: 2 с тегом us_west, 2 с us_east и 2 с тегом london. В дальнейшем закрепим 4 сервера (us_west и us_east) за Америкой и тегом us. Тогда можно сконфигурировать таблицы, используя reconfigure как-то так:

r.table('a').reconfigure(shards=2, replicas={'us_east':2, 'us_west':2,'london':2}, primary_replica_tag='us_east')
r.table('b').reconfigure(shards=2, replicas={'us':2, 'london':1}, primary_replica_tag='london')

Во втором запросе две реплики из группы us могут быть на одном из 4-х американских серверов.

Примечание: серверные теги нельзя настроить через гуятину, только с помощью ReQL запросов и скриптов.

Немного о консистентности

Каждый шард имеет первичную реплику. Все запросы на чтение/запись к шарду перенаправляются на соответствующую реплику, где они упорядочиваются и выполняются. Данные всегда остается согласованным и без конфликтов, и чтение, которое следует сразу за записью гарантированно увидит изменения. Тем не менее, ни чтению, ни записи, не гарантируются успешность выполнения, если первичная реплика недоступна.

RethinkDB поддерживает up-to-date и out-of-date чтение. По-умолчанию все запросы идут как up-to-date, что подразумевает пересылку запросов к первичной реплике. В этом режиме клиент всегла видит последние изменения и согласованные данные.

Вы также можете пометить запрос как out-of-date. В этом режиме нет надобности обращаться к первичной реплике, запрос будет направление к ближайшей. Такие запросы выполняются быстрее, у них более высокая доступность, но при этом мы жертвуем согласованностью, и не обязательно получим последние изменения.

CAP теорема

Эвристическое утверждение о том, что в любой реализации распределённых вычислений возможно обеспечить не более двух из трёх следующих свойств:

  • согласованность данных (consistency) — во всех вычислительных узлах в один момент времени данные не противоречат друг другу;
  • доступность (англ. availability) — любой запрос к распределённой системе завершается корректным откликом;
  • устойчивость к разделению (англ. partition tolerance) — расщепление распределённой системы на несколько изолированных секций не приводит к некорректности отклика от каждой из секций.

Системы вроде Cassandra или Riak выбирают доступность. И бывают большие проблемы в случае разделения сети. В приложениях, использующих эти бд, приходится решать проблемы связанные с конфликтами, восстановлением, и откликом из-за кворума.

RethinkDB (или та же MongoDB) выбирают консистентность, в следствии чего нет проблем связанных с несогласованными данными. Правда из-за этого страдает доступность. В RethinkDB в случае разделения сети поведение системы зависит от того, на какой стороне оказался клиент. Если там, где большинство голосующих реплик шарда, к которому стучится клиент, никаких проблем не будет. Если же со стороны сети, где половина или менее голосующих реплик, то при выполнении up-to-date запросов, клиент отхватит ошибку доступа. К примеру, если пользователь выполняет up-to-date range запрос, затрагивающий несколько щардов, то все первичные реплики должны находится с одной стороны разделённой сети, а иначе failure of availability.

Если же вы пометите запрос как out-of-date, то RethinkDB перенаправит запрос к ближайшей реплике, вместо того, чтобы направить к первичной. Никаких ошибок не будет до тех пор, пока хотя бы одна реплика будет со стороны разделения. Правда, в таком случае высок риск получить out of date (старые, неактуальные) данные. В целом это не проблема, если вы используете эти данные для отчётов, аналитики, статистики, где актуальность данных не так важна.

Кластеризация

Как уже выше говорил, сейчас имеется автоматический фейловер, но необходимо, чтоб в кластере было хотя бы три ноды ресинка.

Обновление состояние и конфигурации кластера в распределённых системах весьма не тривиальная задача. RethinkDB в большинстве случаев использует Raft, иногда использует версионные полурешётки (semilattices) с внутренними временными метками.

В заключении

Про ReQL и фиды в этой статье уже не напишу, ибо материала по этим темам хватит на несколько отдельных статей. Может в будущем найдётся время для этого.

P.S. за изображение спасибо Annie Ruygt (: