Cassandra — теория и поднятие своего кластера в AWS

17 июня 2015

Созданная в 2008-м году ребятами из Facebook, на сегодняшний день Cassandra (название иногда сокращают до «C*») является одним из наиболее популярных NoSQL решений. Проект полностью написан на Java и успешно используется в IBM, Apple, Netflix, Twitter, Яндексе, CERN, SoundCloud, Rackspace, а также множестве других компаний. Сегодня мы поднимем наш собственный маленький Cassandra-кластер в облаке Амазона, но сначала, как обычно, немного теории.

Теоретическая часть

Главное, что нужно знать о Cassandra:

  • Cassandra считается гибридом key-value и column-oriented баз данных. Как и в РСУБД, есть базы данных, в них таблицы (column families), в них строки. Значения в строках хранятся в виде троек (имя столбца, значение, таймстамп). Лично мне всегда больше нравилось думать о Cassandra, как о базе данных, где каждая строка представляет собой хэш-таблицу. Возможно, вам такое представление покажется более понятным. В строках одной и той же таблицы могут быть определены разные столбцы. Таким образом, Cassandra эффективно хранит разряженные таблицы. Притом, в одной таблице может быть до двух миллионов столбцов;
  • C точки зрения пользователя Cassandra ведет себя как урезанная РСУБД с поддержкой своего диалекта SQL без джоинов (Сassandra Query Language, CQL). Можно считать Сassandra в какой-то степени NewSQL решением. Хотя при этом на Cassandra и графы неплохо моделируются. Если столбец в строке не определен, пользователь видит его, как null;
  • Помимо обычных значений, вроде целых чисел, строк и дат, в столбцах могут храниться коллекции, например, map<T1, T2>, list<T> и set<T>. Также поддерживаются counter columns и static columns;
  • Есть поддержка TTL на уровне отдельных столбцов в конкретной строке: insert into ... using ttl [SEC]. Есть поддержка TTL по умолчанию на уровне таблицы;
  • Cassandra является распределенной и отказоустойчивой базой данных. Одна строка может храниться на нескольких физических машинах. Каком количестве — определяется фактором репликации конкретной таблицы. Если у вас фактор репликации N и вы погасили N-1 машин, данные будут и дальше доступны. Внутри это хозяйство реализовано во многом так же, как в Riak, с хэш-кольцом, AP и вот этим всем;
  • Только, в отличие от Riak, все конфликты разрешаются автоматически, благодаря хранению таймстампов для каждого столбца в конкретной строке. Например, вы почти одновременно делаете два запроса — update users set email = 'test@example.ru' where uid = 123 и update users set birthday = 586787696 where uid = 123, создавая тем самым конфликт на узлах, отвечающих за хранение этой строки. В этом случае конфликт будет успешно разрешен по времени последнего изменения столбцов, и вы получите пользователя с адресом test@example.ru и днем рождения 586787696;
  • Кроме того, есть поддержка CAS, реализованная внутри на основе алгоритма Paxos. Понятное дело, в этом случае таймстампы для разрешения конфликтов не используются;
  • При каждом чтении и записи клиент может указать желаемый уровень консистентности — ANY, ONE, TWO, THREE, QUORUM, SERIAL, ALL и другие. Например, уровень ONE (используется по умолчанию) говорит, что запрос должен дойти хотя бы до одного узла, отвечающего за хранение строки, а уровень QUORUM — что запрос должно получить большинство узлов, например, 2 из 3. Таким образом, можно всегда выбирать между скоростью выполнения запросов и надежностью. По умолчанию в фоне ноды Cassandra крутят read repair процесс, приводящий все ноды в консистентное состояние, поэтому для многих задач ONE является вполне подходящим уровнем;
  • Данные хранятся в LSM tree со всеми вытекающими последствиями. Например, использовать Cassandra в режиме вроде message queue очень плохо, потому что вы будете постоянно писать данные, затем tombstone’ы, затем выполнять сжатие данных, из-за чего время ответа станет прыгать в широких пределах. За счет использования LSM tree Сassandra обеспечивает очень высокую скорость записи и при этом хорошую скорость чтения;
  • Несмотря на то, что memtable в Cassandra используется только для буферизации записи, а не для кэширования чтения, в Cassandra предусмотрен row cache, да и кэш файловой системы никто не отменял. Поэтому, в принципе, есть шанс обойтись в проекте одной только Cassandra, не поднимая дополнительно никаких Memcached, Redis или Couchbase. Хотя, конечно, каждый конкретный случай нужно мерить отдельно. А еще в Enterprise версии есть in-memory таблицы;
  • У каждой таблицы есть primary key, который может быть составным. Primary key при этом делится на две части — partition key и clustering key. Первый используется для шардирования данных по нодам, в то время, как второй — для упорядочивания данных внутри конкретной ноды. Правильно подобрав clustering key можно делать эффективные выборки по диапазонам значений;
  • Поддерживаются несколько стратегий шардированния данных в кластере. Например, Murmur3Partitioner распределяет данные между узлами по хэшу от partition key и используется по умолчанию. Но также доступен и ByteOrderedPartitioner, название которого говорит само за себя;
  • Помимо clustering key есть еще и вторичные индексы. Являются довольно эффективными. В смысле, что не приводят к посылке запросов всем узлам кластера, если в запросе указан partition key. Поддержка вторичных индексов делает запись медленнее, поэтому по возможности лучше их избегать. В Cassandra версии 2.1 была добавлена возможность строить вторичные индексы по коллекциям. При этом Cassandra не поддерживают выборку при помощи вторичных индексов по диапазону, видимо, из-за разряженности таблиц;

И еще парочка интересных моментов. Начиная с Cassandra 2.1 появились user defined types. А в Cassandra 2.2 можно будет писать хранимые процедуры и агрегаты. Есть поддержка аутентификации и работы с клиентом по SSL. В Cassandra 2.2 обещают добавить роли. Есть репликация между дата-центрами, rack awareness, и даже MapReduce (понятно, что не нужно пытаться использовать Cassandra вместо Hadoop!). Предусмотрено удобное резервное копирование. Команда nodetool snapshot буквально моментально создает снапшот базы данных, после чего его можно заливать в S3. Запускаем по крону на каждой ноде, и профит! Подробности о резервном копировании можно найти здесь. Наконец, Cassandra можно использовать в качестве распределенной файловой системы — см Cassandra File System (CFS).

Звучит интересно? Так давайте же поднимем в облаке Амазона наш собственный Cassandra кластер!

Установка Cassandra в AWS

Устанавливать будем на инстансы m3.large c Ubuntu 14.04 LTS. В Security Group разрешаем ходить на порт 22 откуда угодно, а на порты 1024-65535 только изнутри VPC (172.16.0.0/12). На инстансах m3.large своп выключен (проверяем командой swapon -s), у вас должно быть так же.

Время на машинах внутри AWS может сильно разъезжаться, совсем недавно я видел разницу в несколько минут. Поэтому первым делом устанавливаем ntpd:

sudo apt-get install ntp
ntpq -pn

Затем ставим Java:

sudo add-apt-repository ppa:webupd8team/java
sudo apt-get update
sudo apt-get install oracle-java8-installer
sudo apt-get install oracle-java8-unlimited-jce-policy

Наконец, устанавливаем Cassandra:

curl -L http://debian.datastax.com/debian/repo_key | sudo apt-key add -
echo "deb http://debian.datastax.com/community stable main" | \
  sudo tee -a /etc/apt/sources.list.d/cassandra.sources.list
sudo apt-get update
sudo apt-get install dsc21 cassandra-tools

Важные файлы и каталоги:

  • /etc/cassandra/cassandra.yaml — основные настройки;
  • /etc/cassandra/cassandra-env.sh — все параметры JVM;
  • /var/log/cassandra/system.log — смотрим сюда, если что-то сломалось;
  • /var/lib/cassandra/ — все данные;

У меня Cassandra крутится еще и локально, в Vagrant, для тестов. В файле /etc/cassandra/cassandra-env.sh я прописал:

MAX_HEAP_SIZE="350M"
HEAP_NEWSIZE="150M"

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

JVM_OPTS="$JVM_OPTS -Djava.rmi.server.hostname=10.110.0.10"
JVM_OPTS="$JVM_OPTS -Dcom.sun.management.jmxremote.port=9999"
JVM_OPTS="$JVM_OPTS -Dcom.sun.management.jmxremote.authenticate=false"
JVM_OPTS="$JVM_OPTS -Dcom.sun.management.jmxremote.ssl=false"

И подключиться к Cassandra с помощью jconsole или jvisualvm:

Подключились к Cassandra с помощью VisualVM

В jconsole во вкладке MBeans можно посмотреть разные интересные метрики, например, количество попаданий в кэш и такого рода вещи.

Создание кластера

Работать с одной нодой как-то неинтересно, поэтому поднимем-ка мы кластер из трех нод.

Останавливаем Cassandra:

sudo service cassandra stop

Сносим все данные:

sudo rm -rf /var/lib/cassandra/data/system/*

Правим /etc/cassandra/cassandra.yaml. Поменять нужно следующее.

cluster_name: 'CassandraCluster'

А то по умолчанию там написано «Test Claster». Если у вас больше одного кластера, убедитесь, что они имеют уникальные названия.

seed_provider:
      parameters:
          - seeds: "172.1.2.3"

Это наша первая нода, поэтому в seeds пишем только ее адрес внутри VPC.

listen_address: 172.1.2.3

Этот адрес также передается по Gossip другим нодам, поэтому тут нельзя написать «слушай все интерфейсы».

rpc_address: 0.0.0.0

А вот rpc address используется только для взаимодействия с клиентом, поэтому здесь можно.

broadcast_rpc_addess: 172.1.2.3

Но только если указать при этом конкретный broadcast rpc addess.

endpoint_snitch: GossipingPropertyFileSnitch

По умолчанию используется SimpleSnitch. В случае с Амазоном имеет смысл рассмотреть опции Ec2Snitch и Ec2MultiRegionSnitch. В двух словах, эта опция позволяет разместить реплики данных как можно дальше друг от друга, чтобы все реплики не находились в одной стойке или датаценте. За подробностями обращайтесь к комментариям в конфиге.

Запускаем Cassandra:

sudo service cassandra start

Проверяем, что нода поднялась:

nodetool status

Поднимаем вторую ноду аналогично. В консоли Амазона можно создать AMI, а из этого AMI — готовую машину, после чего перейти к шагу с остановкой сервера и удалением данных. Список seeds указываем такой же, listen_address меняем. Затем поднимаем и третью ноду. В seeds указываем адрес первой и второй ноды. Для всех остальных нод можно использовать такой же список из трех нод. Все узлы добавлять в seeds не рекомендуется. В результате nodetool status должен показать столько нод, сколько мы добавили.

Поздравляю, кластер поднят! Просто, не правда ли?

Основы работы с cqlsh и языком CQL

Cassandra поддерживает несколько интерфейсов. С ней можно работать по протоколу Thrift, который считается устаревшим и который в Cassandra 3.0 обещают выкинуть, а также по новому протоколу CQL, название которого совпадает с названием языка запросов. Для работы с Cassandra по трифту можно воспользоваться утилитой cassandra-cli. Но нам, к счастью, незачем беспокоиться об обратной совместимости, поэтому сразу воспользуемся более актуальной утилитой cqlsh.

Создадим новую базу данных, или, в терминологии Cassandra, кейспейс:

cqlsh> create keyspace test with replication={'class':'SimpleStrategy',
                                              'replication_factor':2};
cqlsh> use test;

Создадим новую таблицу:

cqlsh:test> create table todo_list ( id int primary key,
                                     description text );

Заполним ее какими-нибудь данными:

cqlsh:test> insert into todo_list (id,description) values (1,'Item 1');
cqlsh:test> insert into todo_list (id,description) values (2,'Item 2');
cqlsh:test> insert into todo_list (id,description) values (3,'Item 3');

Выборка значений:

cqlsh:test> select * from todo_list;

 id  | description
-----+-------------
 1   |      Item 1
 2   |      Item 2
 3   |      Item 3

(3 rows)

Нельзя просто так взять и выбрать строку по description, это вам не MySQL:

cqlsh:test> select id from todo_list where description = 'Item 1';
InvalidRequest: code=2200 [Invalid query] message="No secondary
  indexes on the restricted columns support the provided operators: "

Поэтому построим вторичный индекс:

cqlsh:test> create index on todo_list (description);

Вот теперь можно:

cqlsh:test> select id from todo_list where description = 'Item 1';

 id
-----
 1

(1 rows)

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

Переключение в «вертикальный вывод», аналог \x из PostgreSQL:

cqlsh:test> expand on
Now Expanded output is enabled

cqlsh:test> expand off
Disabled Expanded output.

Включение-выключение трассировки запросов:

cqlsh:test> tracing on;
Now Tracing is enabled

cqlsh:test> tracing off;
Disabled Tracing.

Получение информации о таблице или кейспейсе:

cqlsh:test> desc table todo_list;

... skiped ...

cqlsh:test> desc keyspace test;

... skiped ...

Удаление строки:

cqlsh:test> delete from todo_list where id = 1;

Удаление таблицы:

cqlsh:test> drop table todo_list;

Удаление кейспейса:

cqlsh:test> drop keyspace test;

Выход из cqlsh:

cqlsh:test> exit

Работа с составными primary keys, counter columns, коллекциями и прочим, к сожалению, выходит за рамки данного поста. Что касается составных primary keys и определения, где у него partition key, а где clustering key, синтаксис там примерно такой:

cqlsh:test> create table
                table1 (field1, field2, field3, field4, field5)
            primary key ((field1, field2), field3, field4))
            with clustering order by (field3 desc, field4 asc);

Здесь (field1, field2) будет partition key, (field3, field4) — clustering key. Придумывание правдоподобного примера и проверку эффективности range scan’ов по clustering key при помощи трассировки можете считать своим домашним заданием.

Заключение

В первом приближении, впечатления от Cassandra у меня только положительные. Документация отличная, много книг, активное сообщество, радует похожесть по семантике на РСУБД. Падение ноды ни на чем не сказывается, ну и так далее. Следует только иметь в виду, что в мире Cassandra есть много устаревших книг и статей. Как уже отмечалось, Thrift устарел, и сейчас рулит CQL3. Если вы слышали про supercolumns, то их тоже использовать уже не рекомендуют.

Ссылки по теме:

Ну и, как обычно, про Reddit и StackOverflow не забывайте.

А используете ли вы Cassandra и если да, то для каких задач?

Дополнение: Простая программа на Scala, работающая с Cassandra

Метки: , , .


Вы можете прислать свой комментарий мне на почту, или воспользоваться комментариями в Telegram-группе.