Найти тему

Обзор механизма репликации системы Cassandra

Оглавление

Описание системы

Cassandra — это отказоустойчивая распределенная NoSQL СУБД. Она разрабатывалась инженерами Facebook и основной фокус был на создании системы без единых точек отказа, способной легко масштабироваться и обслуживать базы с петабайтами данных. Помимо этого, система должна была выдерживать высокий рейт на запись без деградации времени чтений.

Иными словами, Cassandra задумана как eventually consistent система, а в терминах CAP-теоремы её можно выразить как AP систему.

Особенности архитектуры

Каждая таблица в базе данных представляет из себя набор словарей, индексируемых при помощи строкового ключа. Элементы внутри этого словаря (или column family) именуются колонками. Каждая колонка состоит из трех частей: имени, значения и таймстемпа, последние нужны для разрешения write-конфликтов. Чем таймстемп больше, тем новее считается запись в таблицу.

В отличие от многих распределенных систем с Master/slave архитектурой, все узлы в кластере Cassandra равноценны. Преимуществом такого решения является отсутствие боттлнека при масштабировании: новые узлы можно легко вводить в кластер.

Данные между узлами распределены равномерно при помощи consistent hashing, что позволяет легко масштабировать кластер. Суть этого механизма заключается в том, что значения хеш-функции представляют из себя замкнутое кольцо: каждый узел кластера случайным образом выбирает себе точку на этом кольце и хранит в себе все данные в диапазоне, заканчивающемся в этой точке и начинающемся предыдущей точкой другого узла на этом же кольце. Благодаря этому, при добавлении или удалении узлов кластера удаётся перепартицировать лишь данные этого узла, не затрагивая соседние.

Рисунок 1. Пример партицирования таблицы в Cassandra
Рисунок 1. Пример партицирования таблицы в Cassandra

К примеру, на Рисунке 1 добавление нового узла приведёт к разделению самого длинного интервала на 2 равные части. Тем самым, узел 4 возьмёт на себя ответственность за часть данных узла 2. Все остальные данные при этом не будут затронуты.

Репликация

В Cassandra разработана довольно гибкая система репликации.

Во-первых, для каждой таблицы есть возможность задавать replication factor (RF) — количество узлов, на которые данные будут реплицированы. Координатор запроса — узел, на который пришёл запрос пользователя, -  отвечает за репликацию запроса на другие кластера. Он определяет, к какому диапазону относится данный запрос и перенаправляет данные пользователя к узлу, ответственному за этот диапазон, а также выбирает другие RF - 1 узел, на которые нужно отправить  данные.

Во-вторых, Cassandra также позволяет задавать политики репликации с учётом топологии кластера. При включенной политике данные будут реплицированы на разные серверные стойки/датацентры. Координатор учитывает это при подборе узлов для репликации. Отметим, что здесь используется Zookeeper для хранения информации об узлах и диапазонах, за которые эти узлы ответственны. Для этого в Cassandra есть лидер, которых хранит всю информацию и следит за тем, чтобы на каждом узле находилось не более RF - 1 диапазона.

Также в Cassandra можно задавать уровень консистентности при репликации — он характеризует количество синхронных реплик при репликации. Понятно, что оборудование имеет привычку ломаться и из-за этого дождаться ответа от всех реплик часто представляется невозможным.  Поэтому данные обычно синхронно реплицируются на Кворум из реплик (1 + replicas count // 2) и асинхронно на оставшиеся узлы. При этом, для недоступных узлов координатор записывает локально Hinted Handoff — подсказку для отсутствующих узлов о недошедшем до них запросе. Когда ноды подсоединятся к кластеру, координатор отправит им эту подсказку.

Помимо Кворума, в Cassandra можно также задавать уровень консистентности Any/One/Two/Local Quorum и другие.

О консистентности

Поговорим про особенности работы системы Cassandra при конкурентных модификациях одного ключа в таблице.

В статье от DataStaх был рассмотрен алгоритм, построенный на векторных часах. Этот механизм использовался в системе Dynamo и его суть состоит в том, что каждый узел хранит свою собственную версию часов. Далее, во время запроса на чтение координатор зачитывает состояние из реплик. В случае конфликтов координатор возвращает пользователю несколько версий одной записи и разрешение конфликтов ложится на пользователя. Этот подход становится слишком тяжелым в случае сложных конфликтов и поэтому в Cassandra эта проблема решена иначе.

Как уже было сказано выше, консистентность регулируется задаваемым уровнем консистентности при записи, а также важную роль играют таймстемпы. При обновлении колонки в базу записывается новое значение вместе со свежим таймстемпом. Cами таймстемпы могут генерироваться либо изнутри Cassandra, либо предоставляться пользователями извне. Также как и с записями, в чтениях тоже есть возможность задания уровня консистентности. Помимо этого, при чтении используется подход last-write-wins: координатор запроса опрашивает несколько реплик и возвращает пользователю значения с самым свежим таймстемпом. В случае равенства таймстемпов, выбирается лексикографически большее значение.

Данный механизм борьбы с конфликтами не всегда является консистентным. В анализе от jepsen io было продемонстрировано наличие dirty write аномалии в системе Cassandra. Эта аномалия была вызвана коллизией двух таймстемпов, имеющих микросекундную точность. Дело в том, что использование физического времени накладывает определенные ограничения, одним из которых является точность: если в качестве таймстемпов используются микросекунды, то существует вероятность коллизии двух таймстемпов, что и было продемонстрировано в эксперименте jepsen. Поэтому, для гарантий изоляции, генератор таймстемпов обязан возвращать уникальные таймстемпы - в некотором роде логические часы.

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

Заключение

Анализ СУБД Cassandra показывает, что у этой системы есть свои преимущества и недостатки и, к сожалению, она не везде применима.

Cassandra заточена под write-intensive нагрузки с высокой доступностью, но достигается это за счёт потери консистентности. Поэтому использование Cassandra в областях, где важна строгая консистентность, не представляется возможным. Как показало исследование jepsen io, в Cassandra были баги даже в аннонсированных lightweight транзакциях, которые должны были частично закрыть отсутствие ACID транзакций.

Помимо этого, архитектура системы не предназначена для аналитических запросов. По сути, Cassandra — это мощная Key-Value СУБД, где значения схематизированы. Она хороша для лукапов и селектов, но более сложные запросы на ней не удастся оптимально написать из коробки.

В остальных случаях Cassandra представляет из себя мощную высокодоступную систему с удобными механизмами кросскластерной репликации. Это может быть очень полезно при построении геораспределенных кластеров.

Источники:

1. https://www.cs.cornell.edu/projects/ladis2009/papers/lakshman-ladis2009.pdf

2. https://aphyr.com/posts/294-jepsen-cassandra

3. https://youtu.be/qQLOofy8SYI

4. https://medium.com/@brunocrt/the-distributed-architecture-behind-cassandra-database-fba8b5cc4785

5. https://www.bigdataschool.ru/wiki/cassandra

6. https://www.datastax.com/blog/why-cassandra-doesnt-need-vector-clocks