Наверх ▲

Apache Cassandra - еще одно NoSQL-хранилище

Владимир Климонтович Владимир Климонтович Со-основатель и технический директор компании GetIntent - стартапа в области интернет-рекламы

Владимир Климонтович: Здравствуйте, меня зовут Владимир Климонтович. Сегодня я буду рассказывать про Apache Cassandra. Это еще одно NoSQL хранилище, которых сейчас очень много. Есть и те, кто его использует. В частности, его используем мы. 

О чем я буду рассказывать?

Сначала я скажу пару вводных слов о NoSQL: что это такое, и зачем это нужно или не нужно использовать.

Потом я расскажу про такую систему хранения данных, как Amazon Dynamo. Это проприетарная система хранения данных от Amazon, которую они довольно подробно описали в статье и которая легла в основу архитектуры Cassandra. 

После этого я немного расскажу про Google Big Table. Это тоже проприетарная система, только уже от Google, которая тоже стала основой для архитектуры Cassandra. 

Затем будет рассказ о том, как Cassandra выглядит снаружи: что это за система, какие с ней можно проводить операции. Будет поверхностный обзор того, как она устроена внутри. Как решается проблема масштабирования, как происходит запись, чтение, как устроена репликация.

Также я упомяну о нашем опыте использования Cassandra. 

Про NoSQL

Я не люблю это слово. Думаю, остальные тоже не любят. Все считают, что его модно использовать, а зачем и почему – не особо понимают.

Почему это плохое и неприятное слово?

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

В данном случае под SQL понимается довольно простая вещь. Это MySQL с хранилищем InnoDB, Microsoft SQL и Oracle, которые устроены более или менее одинаково. Это такие строковые базы данных с индексами на b-деревьях. Обычно, когда в быту говорят "NoSQL", понимается, что это не MySQL. 

Почему-то многие хотят использовать какие-то NoSQL-решения вместо MySQL. Если хорошо посмотреть, то во многих случаях хорошо настроенный MySQL-кластер будет работать, чем Cassandra, HBase и так далее.

Но с MySQL-кластером есть некоторые проблемы. Во-первых, чтобы хорошо его настроить, нужно хорошо разбираться в MySQL, а с Cassandra, HBase и прочими иметь дело намного проще. Сразу все работает, и не нужно думать про настройку. В общем, поэтому все их используют. Хотя это не всегда правильно.

Теорема CAP

Совсем немного теории. Существует CAP-теорема (CAP: consistency, availability, partition tolerance). Наверное, многие о ней слышали. Будем рассматривать систему с точки зрения трех параметров. 

• Первый – это непротиворечивость (англ. consistency). Это значит, что все клиенты видят один и тот же набор данных в один и тот же момент времени.

• Второй – это доступность (англ. availability). На каждый запрос будет дан какой-то ответ. 

• Третий – устойчивость к разделению (англ. partition tolerance). Система будет продолжать работать, если мы разделим сетевое подключение между любыми двумя нодами в системе. Возможно, это не очень строгая формулировка. Но, я думаю, в целом суть понятна.

Утверждается, что нельзя построить систему, которая удовлетворяет всем трем параметрам. Можно выбрать только два. В частности, Cassandra – это устойчивая к разделению и доступная система. Нет непротиворечивости. В этом есть некоторые плюсы, связанные со скоростью и вообще с тем, что она немного более устойчива к разного рода сетевым проблемам. Нет непротиворечивости, хотя это так или иначе решается в Cassandra. На уровне клиента есть настройки, которые позволяют решить часть проблем, связанных с отсутствием непротиворечивости. Про это я расскажу.

NoSQL-хранилища

На слайде перечислены NoSQL-хранилища. Есть MongoDB. На самом деле, архитектурно это тот же MySQL-кластер, в некотором смысле, только с JSON-интерфейсом.

Есть Apache Hadoop. Это не совсем NoSQL, поскольку это офлайн-хранилище с возможностью написания логики с помощью MapReduce.

Есть семейство Google BigTable. Тут все буквально скопировано с BigTable напрямую. Например, HBase.

Есть то, что скопировано с Amazon Dynamo, решение называется Riak. Есть такое хранилище. С ним я не работал. 

Есть Cassandra. Это Google BigTable + Amazon Dynamo. Взяли что-то из одного, что-то из другого – получили Cassandra.

Конкретика

Немного отвлеклись, теперь давайте вернемся к конкретике. Чтобы рассказать, как работает Cassandra, я перечислю основные архитектурные идеи Amazon Dynamo и Google BigTable.

Amazon Dynamo, как я уже сказал ранее, компания Amazon представила в 2007-м году в статье. Архитектура описана, но сама система платная – скачать и запустить ее нельзя. Но можно прочитать статью и написать то же самое.

Основные идеи Amazon Dynamo

Это не база данных. Это распределенный HashMap. В Amazon Dynamo есть всего 2 операции – get (получить что-то по ключу) и получить какое-то значение (англ. value) по ключу. Судя по статье, в Amazon это решение используется для таких вещей, как управление сессиями, корзиной пользователя, и так далее. Amazon Dynamo подходит для вполне насущных вещей, которые случаются во время работы пользователя с сайтом. Решение Amazon Dynamo не предназначено для того, чтобы хранить какие-то исторические данные.

Что еще особенного в Amazon Dynamo? Это абсолютно распределенное решение. Во многих системах существует какая-то главная нода, которая является координатором, и какие-то ведомые ноды, на которых хранятся данные. В Amazon Dynamo все ноды равноправны, и нет никакого координатора. Как это работает, подробнее расскажу позже.

Собственно, то, что называется Distributed Hash Table. Очень похожая технология Distributed Hash Table используется в BitTorrent, она всем знакома на практике, по крайней мере.

Архитектура Cassandra

Основная идея – это "кольцо токенов" (англ. Token Ring). Что это такое? У нас есть какое-то количество серверов в кластере. Например, их 4, как на картинке. Мы каждому серверу назначим токен. Это, грубо говоря, некоторое число. Но сначала мы определяем, какие у нас вообще, в принципе, бывают ключи. 

Предположим, что у нас ключи 64-битные. Соответственно, каждому серверу мы назначим 64-битный токен. После этого мы их выстроим по кругу, и согласно этому отсортируем токены. Каждый сервер у нас будет отвечать за какой-то из диапазонов токенов (Token Range). 

Здесь по картинке все довольно понятно. Например, сервер T1 отвечает за токены от T1 включительно до T2 и так далее. Это основная идея архитектуры в Cassandra и Dynamo. 

Как устроена репликация?

Мы укажем некоторый основной сервер, который отвечает за какой-то диапазон токенов. Например, сервер с токеном T1 отвечает за T1 и T2. Укажем, что следующий сервер будет отвечать за тот же диапазон токенов. За каждый интервал данных отвечают сразу два сервера. Примерно так устроена репликация. Если у нас один сервер "падает", то данные, в принципе, доступны и сохраняются.

На самом деле, не знаю, как в Dynamo, но в Cassandra политика репликации настраиваемая. Можно сделать кое-что посложнее.

Как происходит запись в Cassandra? 

Клиент ничего не знает о том, как ноды в кластере объединены в это кольцо токенов. Он знает только список нод, которые есть в кластере, и посылает команду на запись произвольному серверу. Возможно, используется какая-то балансировка нагрузки. Клиент запоминает, какой сервер сколько отвечал, и посылает команду тому серверу, который отвечает быстрее. Это решается на клиенте.

После того как произвольный сервер получил команду на запись в Amazon Dynamo, он становится координатором этой операции. Он отвечает за то, чтобы все было хорошо. Поскольку сами серверы уже "знают" о топологии кластера, о том, как они выстроены в кольце токенов, сервер сам решает, куда отправить команду на запись. Осуществляется запись на нужные серверы: на один сервер репликации, на два – в зависимости от настроек репликации кластера.

Еще важный момент: сам клиент выбирает критерий успешности записи. Их может быть несколько. Например, клиент может указать, что если команда на запись ушла хоть куда-то – все, считаем запись успешной. Дальше сервер сам разберется, как и куда это "разложить". 

Он может указать: «Я хочу считать запись успешной, когда данные попали на один сервер репликации, а со вторым уже сам сервер разберется (попали, не попали – неважно)». Либо он может сказать: «Запись успешна, когда данные попали на все серверы репликации». Соответственно, от этого зависит скорость работы. Это довольно удобно. Если мы, например, пишем не очень ценное, можно, в принципе, писать быстро и не беспокоиться о потерях.

Как происходит чтение?

Примерно так же, как и запись. Снова отправляется команда произвольному серверу в нашем кольце, в нашем кластере. Сервер становится координатором данной конкретной операции чтения. Он, зная о том, где и как расположены данные, начинает опрашивать соседние серверы и отдает данные клиенту. 

Клиент определяет и критерий успешности чтения. Он может сказать, что чтение состоялось, когда данные были взяты хотя бы с одного сервера репликации. Либо он может сказать, что чтение состоялось, когда данные были собраны со всех серверов. Почему это важно, я чуть-чуть подробнее расскажу.

Google BigTable

Это еще один предшественник Cassandra, у которого была позаимствована некоторая другая архитектурная часть Cassandra. Тоже в двух словах расскажу, как работает это решение.

BigTable – это решение, немного более похожее на базы данных. Там есть таблицы, которые, с первого взгляда, такие же, как в MySQL. Но есть и существенное различие. 

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

Рассмотрим пример...

Например, мы будем писать социальную сеть (сейчас все это делают) и хранить отношения между людьми (друг, не друг, из какой категории друг и прочее). Как мы будем это делать в BigTable? Очень просто. У нас ключом будет имя пользователя. Допустим, у нас есть 3 пользователя. Колонка у нас тоже будет хранить имя пользователя. Соответственно, пара «ключ + определенное значение» у конкретной колонки обозначает отношение дружбы между этими людьми (взаимно, невзаимно и так далее).

На самом деле, намного проще рассматривать BigTable и хранилища семейства BigTable не как таблицы, а как хранилища отображений из пары «ключ – значение» в значении колонки. Мне лично так намного проще все это представлять.

Какие операции выполняются  на таких таблицах?

Операция только одна. Я написал такой аналог SQL – найти все ключи, которые принадлежат конкретному интервалу, чтобы имена колонок тоже были из какого-то интервала. Вот и вся операция. Это общий случай. Частный случай – мы, например, можем взять все колонки по конкретному ключу. Или наоборот – значение колонки по всем ключам. 

Как хранит данные Google BigTable?

Немного иначе, чем Amazon Dynamo. Собственно говоря, здесь на картинке все более или менее понятно нарисовано. У нас есть ведущий (англ. master) узел, который "знает" о структуре сетей и является координатором каждой из операций на чтение и запись. 

Есть серверы, обозначенные на картинке как "Tablet servers". Это такие ведомые (англ. slave) серверы, на которых хранятся данные. Как происходят чтение и запись? Клиент сначала посылает ведущему узлу запрос о том, что он хочет получить, какие данные по каким ключам. Ведущий узел "знает", где хранятся данные и отвечает клиенту, какие конкретно ведомые серверы опросить. После этого клиент обращается за данными уже напрямую к этим серверам.

Довольно хорошая система, поскольку весь трафик, связанный с данными, не идет через ведущий узел, а распределен между этими серверами. 

Как хранятся данные внутри ведомых серверов?

Есть такое понятие, как Memtable. Это просто таблица данных в памяти. Туда попадают самые-самые "свежие" записи. Если мы записали 100 ключей на какой-то ведомый сервер, они попадут в память. На диск они не сбросятся. Кроме того, что они попали в Memtable, они дублируются журналом обновлений (англ. commit log) на диске. Зачем это нужно? Если вдруг у нас сервер "упадет", мы можем его перезапустить, и Memtable заполнится обратно.

Допустим, наша таблица Memtable заполнена (обычно это как-то настраивается). В нее у нас попал миллион ключей или тысяча ключей. Данные пора сбрасывать на диск. Данные сбрасываются в такую структуру данных, которая называется SSTable. Это такой файл, который позволяет найти данные по конкретному ключу за линейное время. Но это неизменяемый файл. Мы его один раз записали, внутрь уже вставить что-то не получится.

Данные приходят, заполняют Memtable. Каждый раз Memtable с какой-то периодичностью сбрасывается на диск. Мы получаем много-много SSTable-файлов. 

К каждому SSTable-файлу прикреплен Bloomfilter. Это хеш-структура, которая отвечает на запрос о том, принадлежит ли данный ключ этому множеству. Она может "соврать". На самом деле, ключ может не принадлежать подмножеству. Это некоторая вероятностная структура, но ошибается она редко.

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

Когда SSTable-файлов оказывается очень много, происходит процесс уплотнения (англ. compaction). В фоновом режиме маленькие SSTable-файлы складываются в один. Их получается мало, но они большие по размеру.

Примерно так все это устроено.

Причем здесь Cassandra?

Сейчас расскажу. Разработчики решения Cassandra взяли идеи из архитектур BigTable и Dynamo. Я расскажу, какие именно, а также немного более подробно объясню, как устроено чтение и прочие операции в Cassandra.

Что взято из Dynamo?

Из Dynamo взята структура кольца токенов. Полная распределенность, у нас нет никакого ведущего узла. Клиент "общается" со всеми данными. Также реализован алгоритм чтения и записи.

Что взято из BigTable?

Из BigTable разработчики решения Cassandra взяли модель данных. В отличие от Amazon Dynamo, Cassandra не просто распределенный хеш. Там есть колонки, ключи и значения у колонок. Взяли локальную структуру данных на серверах. В Dynamo, насколько я помню, все устроено немного не так, как в BigTable. Тем не менее, для Cassandra использовали локальную структуру на серверах, как у BigTable. 

Здесь имеется та же таблица Memtable, куда временно попадают данные, тот же журнал обновлений, где они дублируются на случай падения ноды. Есть абсолютно такой же файл SSTable, взятый у Google, с тем же алгоритмом. 

Абсолютно такое же фоновое уплотнение SSTable, "схлопывание" маленьких файлов SSTable в один. Такая же модель данных. Вместо put и get можно задавать сложные запросы. 

Еще разработчики применили там индексы, которые не очень хорошо работают.

Как Cassandra выглядит снаружи?

Начну с терминологии. Есть такое понятие, как кластер. Это установка решения Cassandra, это все наши ноды. Для этого кластера настраиваются различные параметры:как распределены данные, какая у нас есть структура и так далее. 

Ключевое пространство (англ. keyspace), на мой взгляд, абсолютно неудобный термин. Это, на самом деле, то же самое, что база данных в терминологии MySQL. Там тоже есть настройки какого-то уровня.

Семейство колонки (англ. column family). Это то же самое, что таблица в терминах MySQL и прочих стандартных баз данных. 

Еще есть суперколонки (англ. super columns). Не очень хотел о них рассказывать, но, видимо, надо, поскольку в Cassandra их позиционируют как важную опцию.

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

Пример

Мы храним отношения пользователей социальной сети, которые дружат или не дружат. Они могут либо дружить, либо могут быть подписаны на обновления страниц друг друга. Мы имеем такие суперколонки: "Друзья" и "Подписчики". Внутри них есть колонки, в которых мы храним данные.

Какие операции поддерживаются в Cassandra? 

• Есть операция "mutation", которая осуществляется, когда мы указываем, что по данному ключу в данную колонку мы должны получить данное значение. Почему операция называется "mutation"? Потому что Cassandra "не различает" операции insert, update и прочие. У нас есть только такая операция. Нет данных – значит, они появятся. Были старые данные – значит, они перезапишутся.

• Есть операция "get". Эта операция позволяет получить значение по данному ключу и по данной колонке.

• Есть операция "multi_get". То же самое по многим ключам.

• Есть операция "get_slice". То же самое, только можно отфильтровать колонки, например, по интервалу или по принадлежности к какому-то множеству.

• Есть операция "get_range_slices", когда мы получаем что-то не по конкретному ключу, а по интервалу ключей. 

Это практически все операции. Их немного, но чуть больше, чем у Amazon Dynamo.

Теперь расскажу о том, как Cassandra работает изнутри, как происходит алгоритм записи и прочее.

К сожалению, в отличие от MySQL и каких-то стандартных вещей, это нужно знать прикладному разработчику. Без этого разработка не идет, и непонятно, почему. Приходится знать.

Идея такая же, как и в Amazon Dynamo. Команда идет на произвольный сервер в кластере, и этот сервер становится координатором данной конкретной операции. Сервер находит нужные реплики в зависимости от настройки, которая называется "ReplicationStrategy". О ней будет чуть попозже.

Если у нас "ReplicationFactor" равен единице, то есть мы храним все на одном сервере. Мы знаем ключ и знаем сервер, который отвечает за данный интервал токенов. 

Сразу после того как пришла команда записи на сервер, данные сохраняются локально в некоторой таблице. Этот сигнал называется "Hinted handoff". Он тоже есть в Amazon Dynamo. Зачем это нужно? На случай, если у нас какие-то серверы недоступны. Нам необязательно с ошибкой завершать операцию записи, потому что клиенту может быть не очень важно, чтобы данные разложились по всем серверам сразу. Тогда сервер сохранит данные локально, скажет, что все окей. Когда серверы снова начнут функционировать, данные будут распределены по всем серверам репликации.

Критерий успешности записи определяется клиентом. Таких критериев 4. 

Первый критерий называется "ANY". Это значит, что запись признается успешной, как только данные попадают на начальный сервер. Неважно, распределились они по серверам репликации или нет. Главное, что они попали на наш произвольный сервер, который является координатором.

Есть критерий "ONE". Как только данные попали хотя бы на 1 сервер репликации, считается, что все записано успешно.

Есть критерий "QUORUM". Это "ReplicationFactor" поделить пополам + 1.

Есть критерий "ALL". Запись признается успешной после "раскладывания" данных по всем серверам репликации, которые ответственны за данный ключ.

Как устроена репликация?

Рассмотрим простой случай. У нас есть 4 сервера, каждый отвечает за свой токен. У нас есть сервер, который отвечает за нулевой. Предположим, что у нас ключи от одного до ста. Сервер отвечает за нулевой, 25-й, 50-й, 75-й. 

Точно так же, как в Amazon Dynamo, каждый сервер отвечает за два интервала ключей. Первый сервер отвечает как за интервал ключей от 0 до 25 (за свой интервал), так и за интервал ключей предыдущего сервера. Думаю, по картинке все более или менее понятно.

Все это работает очень хорошо, и не только внутри одного дата-центра. Одна из отличительных особенностей Cassandra – то, что это решение может хорошо реплицировать данные между дата-центрами. Почему это важно? Это может пригодиться тем, кто использует Amazon, Esety и прочие похожие сервисы. Например, у того же Amazon есть такое свойство, что целый дата-центр может упасть. Если мы хотим, чтобы система работала хорошо, неплохо было бы, чтобы она продолжала работать со второго дата-центра. 

Как устроена репликация между дата-центрами?

Это своего рода хак к структуре Cassandra. Идея такая. В Cassandra есть такое требование: у каждого сервера должен быть свой уникальный токен.

Хорошо, давайте сделаем так. Назначим серверам такие токены. Выглядит это довольно искусственно. Первый сервер получает 0, второй получает 1. "Раскрасим" эти серверы в разные цвета. Допустим, "белые" серверы у нас находятся в одном дата-центре, а "серые" – в другом. 

Настроим репликацию следующим образом: копия попадает на какой-то сервер, а следующая копия идет на сервер через один. Например, согласно таким настройкам, ключ со значением 42 попадет на 25-й и на 26-й сервер.

После этого можно "белые" ноды "отправить" в один дата-центр, "серые" – в другой. Так будет работать репликация. Команда будет идти на произвольный сервер. Поскольку копии "знают", где они находятся, они все будут осуществлять репликацию между собой.

Как работает запись?

Мы разобрали, как работает запись на высоком уровне, до того момента, как команда на запись приходит к конкретному серверу репликации, который отвечает за данный интервал. 

Внутри это работает так. Использована идея с Memtable, хранилищем новых записей. Туда попадают все записи сразу. Есть настраиваемый критерий "flush". Настроить его можно по таймауту, по размеру и как-то еще (не помню).

Memtable будет периодически "сбрасываться" на диск и превращаться в SSTable. SSTable – это файл с данными, такой же, как у Google. На самом деле, это 3 файла: данные, index + bloomfilter. Периодически будет происходить уплотнение SSTable. Все маленькие файлы, которые постепенно сбрасывались на диск, будут в фоновом режиме собираться в большие файлы.

Как устроены команды чтения?

Команду чтения, как и у Amazon Dynamo, в Cassandra обрабатывает произвольный сервер в кластере. Какой – решает клиент. Клиенты бывают разные. Обычно сервер определяется на основе загруженности и близости по сети. 

Сервер определяет ближайший сервер репликации на основе настройки, которая называется "snitch". Есть разные опции. Первая – "Simple", класть просто на случайный сервер репликации, самый близкий в кольце токенов. Есть настройка, которая основана на топологии сети. Сервер будет класть не на произвольный сервер репликации, а на тот, который ближе по сети. Есть динамический snitch. Это значит, что сервер будет запоминать статистику времени ответа сервера репликации.

Так же, как и в случае с записью, клиент определяет критерий успешности чтения. Есть 3 настройки. Первая: данные есть хотя бы на одном сервере репликации. Второй уровень – QUORUM: данные есть на количестве серверов репликации пополам + 1. ALL: данные есть на всех серверах репликации.

Зачем это нужно? Почему не считать успешным ответ, когда данные есть на одном сервере репликации? Казалось бы, какая нам разница. На самом деле, разница есть. Только убедившись, что мы получили данные со всех серверов репликации, мы достигаем непротиворечивости. Если мы получили данные со всех серверов репликации, соответственно, они везде одинаковы. Соответственно, мы получили то, что клиент туда записал. Если мы удовольствовались одним сервером репликации, может быть, все произошло по-другому. Допустим, какой-то другой клиент записал данные, они попали на сервер репликации № 1, а мы прочитали данные с сервера репликации № 2 и получили что-то противоречивое.

Так решается проблема с непротиворечивостью, хотя это довольно медленно. Ждать ответ от всех реплик – это долго.

Как разрешаются конфликты в Cassandra?

Довольно просто. Каждое значение в колонке имеет временную метку (англ. timestamp). Она проставляется клиентом на этапе посылки команды на запись. Обычно это текущее время, хотя можно реализовать какую-то специальную логику, записывать туда какие-то версии и прочее. Когда клиент читает данные с одного сервера репликации или со всех, он всегда получает последнюю временную метку. 

Что еще есть в Cassandra?

Есть кэширование ключей (уровень "key cache"). Это значит, что данные, на самом деле, будут читаться не с диска. Данные о положениях в ключе файла будут читаться из памяти. После этого будет читаться файл.

Есть кэширование строк (англ. row cache). Строки в таблицах будут кэшироваться в памяти. Чтение будет происходить не с диска. Принцип простой. Самые "горячие" ключи будут храниться в кэше. Но разработчик Cassandra не рекомендует использовать эту возможность (даже, скорее, не разработчики, а какие-то крупные пользователи). 

Говорят, что есть mmap, такой же кэш операционной системы, которую использует Cassandra. Лучше не использовать никакое кэширование и полагаться на него.

Индексы

Еще одна бесполезная возможность в Cassandra – это индексы. Можно указать, что данная колонка индексирована, и получить, допустим, все строчки в семействе колонки, у которых данная колонка равна данному значению. Есть такая возможность, при этом очевидно, что ее довольно сложно реализовать в такой распределенной системе. Нужно опросить все серверы в системе, чтобы что-то найти, поскольку данные секционированы по ключам. 

В Cassandra есть довольно странное ограничение (не знаю, почему оно такое). Можно искать колонки только по четкому значению. Допустим, найти все ключи, у которых значение колонки больше, чем заданное, невозможно.

Кроме того, что индексы странно работают: они очень сильно замедляют запись данных на практике. То есть довольно бесполезная возможность.

Масштабирование в Cassandra

Ключевая особенность Cassandra – это легкое масштабирование. В любой момент, всегда можно добавить еще какое-то количество серверов в кластер. Это преподносится как существенное преимущество.

Как было сказано, у каждого сервера есть некоторый токен, за который он отвечает. Есть операция "move token". Можно переназначить токен. Указать, что этот сервер отвечает теперь за другой диапазон. Это можно сделать прямо "на ходу". Соответственно, топология, картинка с распределением токенов и диапазонов изменится. При этом миграция будет происходить в фоновом режиме. Для клиентов это все будет довольно прозрачно. 

Пока миграция не закончена, сервер будет отвечать как за старый "кусок" данных, так и за новый. Аналогично будет отработана ситуация, когда мы добавляем что-то в наше кольцо токенов.

Эта картинка иллюстрирует идею масштабирования. Проще всего масштабироваться в 2 раза, как и везде. К примеру, у нас есть 4 сервера, каждый из которых отвечает за токен 0, 25, 50 и 75 (каждый за свой диапазон). Между ними очень легко вставить серверы, которые помечены на картинке оранжевым, каждому назначить токен, который находится ровно посередине между соседями. 

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

Что хорошо в Cassandra, а что плохо? 

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

 Cassandra обеспечивает легкость администрирования, по крайней мере, для начала. Мы просто покупаем или арендуем серверы (Cassandra – это ровно один сервер), и "рассказываем" серверу про любого его соседа, назначаем ему этот токен. Сервера сами "узнают" друг о друге через механизм под названием "gossip", и все начинает работать. 

HBase намного сложнее администрировать или, по крайней мере, устанавливать, поскольку там на каждый сервер есть 4 демона со своими конфигурационными файлами, которые нужно настроить. Это неудобно.

Недостатки Cassandra 

Их очень много, и они довольно неприятные. 

Плохо реализован поиск по диапазону (range scan). Как я сказал, Cassandra поддерживает две операции: можно получить какое-либо значение по ключу либо получить все строки, ключ которых принадлежит данному интервалу. Это реализовано очень плохо. 

Для этого есть причина: Cassandra не умеет передавать данные поточно. На каждый запрос в памяти сначала выделяются данные под ответ. Сначала требуется заполнить этот буфер с данными, только потом возвращается ответ. Запросы, ответ на которые будет большим, в Cassandra выполнять нельзя. 

Какие еще есть недостатки? Очень много настроек вынесено на уровень кластера. Всякие настройки – стратегия хранения ключей и прочее, и прочее. Если мы хотим на одном кластере иметь разные структуры данных с разными политиками репликации и прочее, это будет довольно сложно сделать.

Еще проблема. Уплотнение файлов SSTable, о котором я рассказывал ("схлопывание" маленьких таблиц в большие), хотя и происходит в фоне, все-таки существенно тратит ресурсы серверов и замедляет работу. Это видно. Запросы начинают работать медленно. Это значит, что где-то происходит уплотнение. 

Собственно говоря к недостаткам стоит отнести и протокол коммуникации, который не умеет передавать данные поточно. Это такой протокол "free" от "Facebook". Он очень неприятный с технической точки зрения, и с ним тяжело работать.

Наш опыт использования Cassandra я опишу буквально в двух словах.

Что мы делаем?

Мы занимаемся онлайн-рекламой, и мы храним события, которые происходят в мире онлайн-рекламы. Нам нужно многое знать о показах, объявлениях, кликах и конверсии. В основном мы храним показы. 

Какой у нас объем данных?

Один из наших клиентов: где-то 1 миллиард событий в месяц и 100 миллионов уникальных пользователей. 

Мы храним данные так, как показано в табличке. Ключом у нас является идентификатор пользователя, именем колонки является идентификатор события. Соответственно, внутри лежит вся информация о событии. Мы используем Cassandra как классическое колоночное хранилище. Колонки никогда не совпадают. У каждой строки есть свой уникальный набор колонок. Так мы храним данные.

Какие запросы мы обрабатываем в Cassandra?

Например, мы хотим узнать, сколько было уникальных пользователей в каком-то городе. Или нам интересно, сколько уникальных пользователей видели рекламное объявление X, но не видели рекламное объявление Y. Или нам нужно собрать данные о том, какие объявления кликал конкретный пользователь.

Вопрос № 3 – стандартный вопрос для Cassandra. Вопросы № 1 и 2 Cassandra не умеет решать, потому что это агрегационные запросы. Нам не просто надо получить небольшие данные. Нам потребуется получить очень-очень много данных, их как-то сжать, что-то с ними сделать и дать маленький ответ.

Чем нам нравится Cassandra?

Очень быстрая запись, автоматически все группируется по user id. Нет никаких проблем с дубликатами, в отличие от какого-нибудь Hadoop. Когда мы записываем там логи, мы всегда боимся записать одну и ту же строчку два раза. В Cassandra таких проблем нет. Загрузили один и тот же файл два раза – данные перезаписались.

Наши проблемы 

Поскольку очень плохо реализован поиск по диапазону (а он нам нужен для агрегационных запросов), нам пришлось делать свое специальное решение. Опять же, есть проблемы с тем, что Cassandra не умеет выполнять логику прямо на серверах. Нельзя вынести логику на свои ноды, ЦП которых обычно простаивает.

Как мы решаем проблемы?

Проблему агрегации мы думали решить с помощью Hadoop, MapReduce. Hadoop умеет запускать "MapReduce jobs" на данных Cassandra. Также думали про собственные решения.

Минусы Hadoop понятны. Это операционные расходы (поддерживать Hadoop на кластере рядом с Cassandra – это тяжко). Взаимодействие Hadoop и Cassandra реализовано неважно, потому что можно использовать только одно семейство колонки как источник данных. Все специальные решения с Hadoop сделаны плохо.

У нас написано свое решение. Это такие демоны, которые имеются на каждом из серверов Cassandra и получают запросы: например, сагрегировать данные по такому-то критерию. Демоны агрегируют их и выдают клиенту уже сагрегированные данные.

Немного цифр

У нас пока довольно тестовый проект. Данных мало – всего 600 гигабайт. Поэтому кластер тоже очень маленький – 3 сервера конфигурации m1.large в Amazon, 2 ЦП и 7,5 гигабайт оперативной памяти, хотя оперативная память в Cassandra не очень хорошо используется. 

Запись – где-то 12 тысяч событий в секунду, чтение – где-то 9 тысяч. Опять же, это удивительно для хранилищ, потому что обычно все бывает наоборот.

Наверное, все. Задавайте вопросы, если что интересно.

Вопросы и ответы

Вопрос из зала: Могли бы вы поподробнее рассказать про конфигурацию нод в кластерах? Что они "знают" друг о друге – каждый знает только о соседе? Как происходит маршрутизация запроса?

Владимир Климонтович: Да, об одном только. У ноды есть соседи, о которых она знает. В параметр конфигурации можно написать ровно одного соседа. Нода "спросит" соседа о его соседях, и так они друг о друге "знают". Это механизм "auto discovery".

Вопрос из зала: То есть реально возможна ситуация, когда придется опросить все сервера, чтобы понять, кто в итоге должен обработать запрос?

Владимир Климонтович: Нет. Все это происходит на этапе запуска кластера. Когда кластер стартовал, они уже все друг о друге "знают". Зачем спрашивать лишний раз?

Вопрос из зала: Я правильно понял, что этот пограничный токен (нулевой, 25-й) хранится только на одном кластере всегда? Он не реплицируется?

Владимир Климонтович: Нет, реплицируется.

Вопрос из зала: Там был 0, 1, 26…

Владимир Климонтович: Токен определяет исключительно то, за какой интервал отвечает данный сервер, к которому этот токен приставлен.

Реплика из зала: Я понял, что "серые" отвечали с 1-го по 25-й.

Владимир Климонтович: Да-да-да, именно так. 

Реплика из зала: Тогда получается, нулевой только на одном сервере хранится.

Владимир Климонтович: Нулевой токен?

Реплика из зала: Да.

Владимир Климонтович: Либо на одном, если ReplicationFactor=1, либо на нем же и на следующем, если ReplicationFactor=2. Либо на нем же и через один, если у нас такие настройки.

Реплика из зала: Но между дата-центрами они, получается, разноситься не будут.

Владимир Климонтович: Почему? Будут.

Реплика из зала: Я так понял, что нулевой, 25-й – в одном дата-центре, 1-й, 26-й – в другом дата-центре.

Владимир Климонтович: Нет, не совсем так. Нулевой будет как в одном дата-центре, так и в другом. Нулевой будет лежать на ноде, отвечающей за нулевой токен в первом дата-центре, и на ноде, отвечающей за какой-нибудь 87-й токен во втором дата-центре. Спасибо за вопросы!

Комментарии

Нет ни одного комментария

Только пользователи могут оставлять комментарии

Возможно, вам будет интересно:

Максим Гапонов

Максим Гапонов

Советник генерального директора, Banki.ru, Москва.

Cегодня мы будем говорить, в общем и целом, если не рассматривать какие-то классические труды по жизненному циклу, как мы разрабатываем продукт, как мы его отдаем в жизнь.

Дмитрий Самиров

Дмитрий Самиров

Разработчик серверной части рекламной системы Advaction.

Александр Панков и Дмитрий Самиров предлагают варианты нестандартного использования MySQL-репликации для написания высоконагруженных демонов.

Артём Гавриченков

Артём Гавриченков

Ведущий разработчик в компании "Highload Lab".

В докладе описаны типичные ошибки программирования при написании серверных приложений на основе TCP-сокетов.