Cassandra hammock


Созданная в 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 = '[email protected]' where uid = 123

    и update users set birthday = 586787696 where uid = 123, создавая тем самым конфликт на узлах, отвечающих за хранение этой строки. В этом случае конфликт будет успешно разрешен по времени последнего изменения столбцов, и вы получите пользователя с адресом [email protected] и днем рождения 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:

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

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

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

  • /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 я прописал:

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

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

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

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


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

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

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

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

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

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

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

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

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

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

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

Запускаем Cassandra:

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

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


должен показать столько нод, сколько мы добавили.

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

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

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

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

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

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

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

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

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

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

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

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


из PostgreSQL:

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

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

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

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

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

Выход из cqlsh:

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

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

Источник: eax.me

Apache Cassandra

Cassandra ― это реализация семейства NoSQL Column, поддерживающая модель данных Big Table (большая таблица) с использованием некоторых архитектурных аспектов системы Amazon Dynamo. Некоторые из преимуществ Cassandra:

  • высокая масштабируемость и надежность без элементов, отказ которых приводит к выходу из строя всей системы;
  • реализация семейства NoSQL Column;
  • очень высокая пропускная способность для операций записи и хорошая пропускная способность для операций считывания;
  • SQL-подобный язык запросов (начиная с версии 0.8) и поддержка поиска посредством вторичных индексов;
  • настраиваемая согласованность и поддержка репликации;
  • гибкая схема.

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

Cassandra хранит данные в соответствии с моделью данных “семейство столбцов”, как показано на рисунке 1.

Рисунок 1. Модель данных Cassandra

Модель данных Cassandra

Кликните, чтобы увидеть увеличенное изображение

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

  • Столбец ― это основной элемент модели данных Cassandra, и каждый столбец содержит имя, значение и метку времени. Мы проигнорируем метку времени, что позволит нам представить столбец как пару имя-значение (например, автор=«Азимов»).
  • Строка ― это именованная коллекция столбцов. Например, в листинге 1 показано, как могут быть представлены строки.
    Листинг 1. Пример строки
     "Second Foundation"-> {  author="Asimov",   publishedDate="..",  tag1="sci-fi", tag2="Asimov"  }

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

  • Семейство столбцов ― это именованная коллекция строк. Листинг 2 демонстрирует, как может выглядеть пример данных.
    Листинг 2. Пример семейства столбцов
     Books->{  "Foundation"->{author="Asimov", publishedDate=".."},  "Second Foundation"->{author="Asimov", publishedDate=".."},  ...  }

    Часто говорят, что семейство столбцов соответствует таблице в реляционной модели. Как показано в следующем примере, на этом сходство этих моделей заканчивается.

  • Пространство ключей ― это группа из многих семейств столбцов, собранных вместе. Оно логически группирует семейства столбцов и обеспечивает изолированные области имен.

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

Модели данных Cassandra и РСУБД

Из приведенного выше описания модели данных Cassandra следует, что в каждом семействе столбцов данные размещаются в двухмерном пространстве (2D). Чтобы извлечь данные из семейства столбцов, требуется два ключа: имя строки и имя столбца. В этом смысле реляционная модель и Cassandra схожи, хотя существует ряд важных различий.

  • Реляционные столбцы однородны по всем строкам таблицы. Обычно существует четкая вертикальная связь между элементами данных, но в случае столбцов Cassandra это не так. По этой причине вместе с каждым элементом данных (столбцом) Cassandra хранит имя столбца.
  • В реляционной модели двухмерное пространство данных является полным. В каждой точке 2D-пространства должно храниться хотя бы значение null. В случае Cassandra это тоже не так; могут быть строки, содержащие всего несколько элементов, в то время как в других их могут быть миллионы.
  • В реляционной модели схема предопределена и не может изменяться во время выполнения, в то время как Cassandra позволяет это делать.
  • Cassandra всегда хранит данные таким образом, что столбцы отсортированы по именам. Это облегчает поиск данных в столбцах с помощью секционированных запросов, но затрудняет поиск данных в строке, если не использовать распределитель, сохраняющий порядок сортировки.
  • Другим важным отличием является то, что имена столбцов в РСУБД представляют собой метаданные, а не данные. В Cassandra же имена столбцов могут включать данные. Следовательно, строки Cassandra могут иметь миллионы столбцов, тогда как в реляционной модели их обычно всего десятки.
  • Благодаря четко определенной, неизменяемой схеме реляционная модель поддерживает сложные запросы, включающие соединения, агрегирование и многое другое. В реляционной модели пользователи могут определить схему данных, не беспокоясь о запросах. Cassandra не поддерживает соединений и большинство SQL-методов поиска. Запросы, необходимые для приложения, должны быть определены в схеме.

Чтобы разобраться в приведенных выше различиях, рассмотрим пример сайта оценки книг, где пользователи могут добавлять книги (автор, рейтинг, цена, ссылка), комментарии (текст, время, имя) и метки. Приложение должно поддерживать следующие операции:

  • добавление книг;
  • добавление комментариев к книгам;
  • добавление меток книг;
  • вывод списка книг, отсортированных по рейтингу;
  • вывод списка книг с определенной меткой;
  • вывод комментариев для книги с заданным идентификатором.

С помощью реляционной модели такое приложение реализуется тривиально. На рисунке 2 показана схема “объекты-отношения” (Entity-Relationship – ER) для базы данных.

Рисунок 2. ER-модели для сайта оценки книг

Блок-схема модели данных сайта оценки книг

Кликните, чтобы увидеть увеличенное изображение

Давайте посмотрим, как это можно реализовать с помощью модели данных Cassandra. В листинге 3 показана возможная схема Cassandra, в которой первая строка представляет собой семейство столбцов Books, содержащее несколько строк, столбцы каждой из которых соответствуют свойствам книги. <TS1> и <TS2> ― метки времени.

Листинг 3. Схема Cassandra для сайта оценки книг
Books[BookID->(author, rank, price, link, tag<TS1>, tag<TS2> ..,   cmt+<TS1>= text + "-" + author) ... Tags2BooksIndex[TagID->(<TS1>=bookID1, <TS2>=bookID2, ..) ]  Tags2AuthorsIndex[TagID->(<TS1>=bookID1, <TS2>=bookID2, ..) ] RanksIndex["RANK" -> (rank<TS1>=bookID)]

Таблица 1 содержит пример набора данных согласно схеме.

Таблица 1. Пример данных для сайта оценки книг
Имя семейства столбцов Пример набора данных
Books

“Foundation” -> (“author”=”Asimov”, “rank”=9, “price”=14, “tag1″=”sci-fi”, “tag2″=”future”, “cmt1311031405922″=”best book-sanjiva”, “cmt1311031405923″=”well I disagree-srinath”)
“I Robot” -> (“author”=”Asimov”, “rank”=7, “price”=14, “tag1″=”sci-fi” “tag2″=”robots”, “cmt1311031405924″=”Asimov’s best-srinath”, “cmt1311031405928″=”I like foundation better-sanjiva”)
RanksIndex “Rank” -> (9=”Foundation”, 7=”I Robot”)
Tags2BooksIndex “sci-fi” -> (“1311031405918″=”Foundation”, “1311031405919”=”I Robot”
“future” -> …
Tags2AuthorsIndex “sci-fi” -> (1311031405920=”Asimov”)
“future” -> …

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

Модель семейства столбцов Books использует строку для представления каждой книги, название которой служит идентификатором строки. Данные о книге представлены в виде столбцов, хранящихся в строке.

Приглядевшись, можно заметить, что элементы данных (комментарии и теги, которые соотносятся с книгами по принципу “один ко многим”) также находятся в пределах одной строки. Для этого к именам столбцов меток и комментариев добавляется метка времени. Этот подход позволяет хранить все данные в одном столбце. Такая мера позволяет избежать операций соединения (JOIN) для извлечения данных. Так Cassandra обходит отсутствие поддержки операций соединения.

Это обеспечивает несколько преимуществ.

  • Можно получить все данные о книге за один запрос, считав целую строку.
  • Можно получить комментарии и метки без операций соединения, используя секционированные запросы с диапазонами cmt0-cmt9999 и tag0-tag9999.

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

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

Поэтому для достижения лучших результатов в модели данных Cassandra нужно выполнять поиск путем создания специальных индексов и использования порядка сортировки строк и столбцов. Другие три семейства столбцов (Tags2BooksIndex, Tags2AuthorsIndex и RankIndex) организованы именно так. Пользователям нужно искать книги по меткам, и семейство столбцов Tags2BooksIndex строит индекс, сохраняя имя метки в качестве идентификатора строки, в которую в качестве столбцов помещаются все книги, помеченные этой меткой. Как показано в примере, в качестве ключей столбца добавляются метки времени, но это для того, чтобы обеспечить уникальные идентификаторы столбцов. Реализация поиска просто считывает индекс, разыскивая строку по имени метки, и находит совпадения, считывая все столбцы, хранящиеся в строке с этим rowID.

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

Таблица 2. Сравнение реализации запросов
Описание запроса Запрос как SQL Реализация Cassandra
Список книг, отсортированных по рейтингу

Выполнение запроса
"Select * from Books order by rank" и затем для каждого результата выполнить "Select tag from Tags where bookid=?" и "Select comment from Comments where bookid=?"
Выполнить секционированный запрос к семейству столбцов RankIndex, чтобы получить упорядоченный список книг, и для каждой книги выполнить секционированный запрос к семейству столбцов Books, чтобы получить сведения о книге.
По метке найти авторов книг с данной меткой. Select distinct author from Tags, Books where Tags.bookid=Books.bookid and tag=? Считать все столбцы для данной метки из Tags2Authors с помощью секционированного запроса.
Для данной метки получить список книг с данной меткой. Select bookid from Tags where tag=? Считать все столбцы для данной метки из Tags2BooksIndex с помощью секционированного запроса.
Для данной книги получить список комментариев к этой книге, отсортированный по времени создания. Select text, time, user from Comments where bookid=? Order by time В семействе столбцов Books сделать секционированный запрос из строки, соответствующей данной книге. Они отсортированы по меткам времени, которые используются в качестве имени столбца.

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

  • Select * from Books where price > 50;
  • Select * from Books where author="Asimov"

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

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

Использование Cassandra из среды Java

У Cassandra много клиентов, написанных на разных языках. В этой статье рассматривается клиент Hector (см. раздел Ресурсы), наиболее широко используемый Java-клиент для Cassandra. Пользователи могут наращивать свои приложения, добавляя JAR-файлы Hector в classpath приложения. В листинге 4 показан пример клиента Hector.

Сначала выполняется подключение к кластеру Cassandra. Инструкции по настройке узла Cassandra содержатся на странице Cassandra Getting Started (см. раздел Ресурсы). Если конфигурация не изменена, она, как правило, работает через порт 9160. Затем определяется пространство ключей. Это можно сделать через клиент или с помощью файла конфигурации conf/cassandra.yaml.

Листинг 4. Пример кода клиента Hector для Cassandra
Cluster cluster = HFactory.createCluster('TestCluster',   new CassandraHostConfigurator("localhost:9160"));  //определение пространства ключей Keyspace keyspace = HFactory.createKeyspace("BooksRating", cluster);  //добавление нового столбца.  String rowID = "Foundation";  String columnFamily = "Books";  Mutator<String>  mutator = HFactory.createMutator(keyspace, user); mutator.insert(rowID, columnFamily,   HFactory.createStringColumn("author", "Asimov"));  //считывание столбца  ColumnQuery<String, String, String>  columnQuery = HFactory.createStringColumnQuery(keyspace); columnQuery.setColumnFamily(columnFamily).setKey(”wso2”).setName("address"); QueryResult<HColumn<String, String>  result = columnQuery.execute(); System.out.println("received "+ result.get().getName() + "= "   + result.get().getValue() + " ts = "+ result.get().getClock());

Полный код для сайта оценки книг приведен в разделе Загрузка. Он содержит примеры секционированных запросов и других сложных операций.

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

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

Архитектура кластера Cassandra показана на рисунке 3. Сразу видно, что Cassandra ― распределенная система. Она состоит из нескольких узлов и распределяет данные между этими узлами (или секционирует их по терминологии баз данных).

Рисунок 3. Кластер Cassandra

Кластер Cassandra

Кликните, чтобы увидеть увеличенное изображение

Для распределения элементов данных по узлам Cassandra использует последовательное хэширование. Проще говоря, Cassandra использует хэш-алгоритм для вычисления хэш-значений ключей каждого элемента данных, хранящегося в Cassandra (имя столбца, ID строки и т.п.). Диапазон хэш-значений или все возможные хэш-значения (т.н. пространство ключей) распределяется между узлами кластера Cassandra. Затем Cassandra назначает каждому элементу данных свой узел, и этот узел отвечает за хранение и управление этим элементом данных. Подробные описание архитектуры Cassandra содержится в документе “Cassandra – A Decentralized Structured Storage System” (Cassandra – децентрализованная структурированная системы хранения данных – см. раздел Ресурсы).

Такая архитектура предоставляет следующие возможности:

  • Cassandra распределяет данные между узлами прозрачно для пользователей. Любой узел может принимать любой запрос (чтение, запись или удаление), и если данные хранятся не в этом узле, перенаправляет его в нужный узел;
  • пользователи могут определить необходимое количество реплик, и Cassandra прозрачно обеспечит создание реплик и управление ими;
  • настраиваемая согласованность: при хранении и считывании данных пользователи могут выбирать уровень согласованности по каждой операции. Например, если при записи или чтении используется уровень согласованности «кворум», то данные записываются и считываются более чем из половины узлов кластера. Поддержка настраиваемой согласованности позволяет выбрать уровень согласованности, наиболее подходящий для данного случая;
  • Cassandra обеспечивает очень быструю запись, более быструю, чем чтение, со скоростью передачи данных порядка 80-360 МБ/с на узел. Это достигается с помощью двух подходов:
    • Cassandra хранит большую часть данных в оперативной памяти ответственного узла, и любые обновления выполняются в памяти, а затем записываются в постоянную систему хранения (файловую систему) ленивым методом. Однако во избежание потери данных Cassandra регистрирует все транзакции в журнале фиксации транзакций на диске. В отличие от обновления элементов данных на диске, записи в журналы фиксации могут только добавляться, что исключает задержку вращения диска. Подробнее о характеристиках дисковых накопителей см. в разделе Ресурсы;
    • если не требуется полная согласованность записей, Cassandra записывает данные в достаточное число узлов без разрешения конфликтов несоответствия, которые разрешаются только при первом считывании. Этот процесс называется «ремонтом при чтении».

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

Возможные сюрпризы Cassandra

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

Никаких транзакций, никаких JOIN-операций

Хорошо известно, что Cassandra не поддерживает транзакции ACID. В ней есть пакетные операции, но нет никакой гарантии, что подоперации в составе пакетной операции будут выполнены в атомарном режиме (либо все, либо ничего). Этот вопрос подробнее рассматривается в разделе Сбой операции может оставить след.

Кроме того, Cassandra не поддерживает операции соединения (JOIN). Если нужно объединить два семейства столбцов, придется извлекать и объединять данные программным способом. Для больших наборов данных это часто дорогостоящая и трудоемкая операция. Cassandra пытается обойти это ограничение, сохраняя как можно больше данных в одной и той же строке, как описано в примере.

Нет внешних ключей, и ключи неизменяемы

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

Ключи должны быть уникальными

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

Существует два пути решения этой проблемы. Во-первых, можно использовать составной ключ. Иными словами, создать ключ, объединив несколько полей, и это решение часто применяется для ключей строк. Второе решение: когда есть опасность повторения одного и того же ключа, к ключу добавляется случайное значение или метка времени. Это часто делается с индексами, когда индекс хранит значение в виде имени столбца. Например, в приложении оценки книг в качестве имени столбца используется рейтинг. Чтобы исключить возможность появления записей с одним и тем же именем столбца, к значению рейтинга в качестве суффикса добавляется метка времени.

Сбой операции может оставить след

Как объяснялось выше, Cassandra не поддерживает атомарных операций. Но она поддерживает идемпотентные операции. Идемпотентные операции оставляют систему в одном и том же состоянии независимо от того, сколько раз выполнялась операция. Все операции Cassandra являются идемпотентными. Если операция не удалась, ее можно повторить без всяких проблем. Это обеспечивает механизм восстановления после сбоев.

Cassandra поддерживает и пакетные операции, но они тоже не дают никакой гарантии атомарности. Поскольку операции идемпотентны, клиент может продолжать попытки до тех пор, пока все операции пакета не будут успешно выполнены.

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

Усложненный поиск

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

С методом организации поиска связаны три вида трудностей.

  1. Для создания специальных методов поиска программист должен в определенной степени знать механизм индексации и детали системы хранения данных. Поэтому для работы с Cassandra требуются более квалифицированные программисты, чем для работы с реляционными моделями.
  2. Специальные индексы сложны и в большой мере зависят от порядка сортировки. Порядок сортировки может быть одного из двух типов: столбцы всегда сортируются по имени, а порядок сортировки строк работает только с использованием распределителя с сохранением порядка сортировки (см. раздел Ресурсы).
  3. В отличие от реляционных моделей, для добавления нового запроса часто требуются новые индексы и изменения в коде. Для этого разработчик должен анализировать запросы, прежде чем сохранять данные.

Суперстолбцы и распределители, сохраняющие порядок сортировки, не поощряются

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

Распределитель Cassandra решает, как распределить (нарезать) данные между узлами, и существует множество его реализаций. При использовании распределителя с сохранением порядка сортировки идентификаторы строк хранятся отсортированными, и Cassandra может выполнять сегментацию (поиск) по rowID. Однако такой распределитель распределяет данные между узлами неравномерно, и при больших наборах данных некоторые узлы могут быть перегружены, в то время как другие остаются недогруженными. Поэтому разработчики не поощряют и использование распределителей с сохранением порядка сортировки.

Восстановление после отказа обеспечивается вручную

Если в кластере Cassandra произошел отказ узла, кластер будет продолжать работать, если у него есть реплики. Полное восстановление ― то есть перераспределение данных и компенсация недостающих реплик ― это ручная операция, выполняемая с помощью инструмента командной строки node tool (см. раздел Ресурсы). Кроме того, пока выполняется эта ручная операция, система будет недоступна.

Она помнит удаления

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

Поэтому Cassandra должна помнить, что элемент данных был удален. В версии 0.8 Cassandra сохраняла все данные, даже если они были удалены. Это приводило к росту потребления дискового пространства при операциях с интенсивным обновлением данных. На самом деле нужно помнить не сами удаленные данные, а только факт удаления элемента данных. Это исправление внесено в более поздние версии Cassandra.

Заключение

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

Кроме того, рассмотрены некоторые ограничения подхода Cassandra. однако это общие ограничения для большинства NoSQL-решений и часто ― сознательные компромиссы, позволяющие обеспечить высокую масштабируемость.

Ресурсы для скачивания

  • этот контент в PDF
  • Пример кода приложения для оценки книг (CassandraSample.zip | 42 КБ)

Источник: www.ibm.com

Теорема CAP (известная также как теорема Брюера)

Теорема CAP (см. рисунок 1) (известная также как теорема Брюера) — эвристическое утверждение о том, что в любой реализации распределённых вычислений возможно обеспечить не более двух из трёх следующих свойств:

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

С точки зрения теоремы CAP, распределённые системы в зависимости от пары практически поддерживаемых свойств из трёх возможных распадаются на три класса — CA, CP, AP.

CA Система, во всех узлах которой данные согласованы и обеспечена доступность, жертвует устойчивостью к распаду на секции. Такие системы возможны на основе технологического программного обеспечения, поддерживающего транзакционность в смысле ACID. Примерами таких систем могут быть решения на основе кластерных систем управления базами данных или распределённая служба каталогов LDAP.

CP Распределённая система, в каждый момент обеспечивающая целостный результат и способная функционировать в условиях распада, в ущерб доступности может не выдавать отклик. Устойчивость к распаду на секции требует обеспечения дублирования изменений во всех узлах системы, в этой связи отмечается практическая целесообразность использования в таких системах распределённых пессимистических блокировок для сохранения целостности.

AP Распределённая система, отказывающаяся от целостности результата. Хотя системы такого рода известны задолго до формулировки принципа CAP (например, распределённые веб-кэши или DNS), рост популярности систем с этим набором свойств связывается именно с распространением теоремы CAP. Так, большинство NoSQL-систем принципиально не гарантируют целостности данных, и ссылаются на теорему CAP как на мотив такого ограничения. Задачей при построении AP-систем становится обеспечение некоторого практически целесообразного уровня целостности данных, в этом смысле про AP-системы говорят как о «целостных в конечном итоге» (англ. eventually consistent) или как о «слабо целостных» (англ. weak consistent).

Apache Cassandra полностью реализует принципы A (доступность) и P (устойчивость к разделению – простота масштабирования). При этом Apache Cassandra предоставляет контроль за третьим принципом C (согласованность данных) при выполнении операций.

Модель данных

В терминологии кассандры приложение работает с пространством ключей (keyspace), что соответствует понятию схемы базы данных (database schema) в реляционной модели. В этом пространстве ключей могут находиться несколько колоночных семейств (column family), что соответствует понятию реляционной таблицы. В свою очередь, колоночные семейства содержат колонки (column), которые объединяются при помощи ключа (row key) в записи (row). Колонка состоит из трех частей: имени (column name), метки времени (timestamp) и значения (value). Колонки в пределах записи упорядочены. В отличие от реляционной БД, никаких ограничений на то, чтобы записи (а в терминах БД это строки) содержали колонки с такими же именами как и в других записях — нет. Колоночные семейства могут быть нескольких видов, но в этой статье мы будем опускать эту детализацию. Также в последних версиях кассандры появилась возможность выполнять запросы определения и изменения данных (DDL, DML) при помощи языка CQL[1], а также создавать вторичные индексы (secondary indices).

Конкретное значение, хранимое в кассандре идентифицируется(см. рисунок 2):

  • пространством ключей — это привязка к приложению (предметной области). Позволяет на одном кластере размещать данные разных приложений;
  • колоночным семейством — это привязка к запросу;
  • ключом — это привязка к узлу кластера. От ключа зависит на какие узлы попадут сохранённые колонки;
  • именем колонки — это привязка к атрибуту в записи. Позволяет в одной записи хранить несколько значений.

Структура данных при этом выглядит так:

  • Keyspace
    • ColumnFamily
      • Row
        • Key
        • Column
          • Name
          • Value
        • Column

С каждым значением связана метка времени — задаваемое пользователем число, которое используется для разрешения конфликтов во время записи: чем больше число, тем колонка считается новее, а при сравнении перетирает старые колонки.

По типам данных: пространство ключей и колоночное семейство — это строки (имена); метка времени — это 64-битное число; а ключ, имя колонки и значение колонки — это массив байтов. Также кассандра имеет понятие типов данных (data type). Эти типы могут по желанию разработчика (опционально) задаваться при создании колоночного семейства. Для имён колонок это называется сравнителем (comparator), для значений и ключей — валидатором (validator). Первый определяет какие байтовые значения допустимы для имён колонок и как их упорядочить. Второй — какие байтовые значение допустимы для значений колонок и ключей. Если эти типы данных не заданы, то кассандра хранит значения и сравнивает их как байтовые строки (BytesType) так как, по сути, они сохраняются внутри.

Типы данных бывают такими:

  • BytesType: любые байтовые строки (без валидации)
  • AsciiType: ASCII строка
  • UTF8Type: UTF-8 строка
  • IntegerType: число с произвольным размером
  • Int32Type: 4-байтовое число
  • LongType: 8-байтовое число
  • UUIDType: UUID 1-ого или 4-ого типа
  • TimeUUIDType: UUID 1-ого типа
  • DateType: 8-байтовое значение метки времени
  • BooleanType: два значения: true = 1 или false = 0
  • FloatType: 4-байтовое число с плавающей запятой
  • DoubleType: 8-байтовое число с плавающей запятой
  • DecimalType: число с произвольным размером и плавающей запятой
  • CounterColumnType: 8-байтовый счётчик

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

Запись в кассандру работает с большей скоростью, чем чтение. Это меняет подход, который применяется при проектировании. Если рассматривать кассандру с точки зрения проектирования модели данных, то проще представить колоночное семейство не как таблицу, а как материализованное представление (materialized view) — структуру, которая представляет данные некоторого сложного запроса, но хранит их на диске. Вместо того, чтобы пытаться как-либо скомпоновать данные при помощи запросов, лучше постараться сохранить в коночное семейство все, что может понадобиться для этого запроса. То есть, подходить необходимо не со стороны отношений между сущностями или связями между объектами, а со стороны запросов: какие поля требуются выбрать; в каком порядке должны идти записи; какие данные, связанные с основными, должны запрашиваться совместно — всё это должно уже быть сохранено в колоночное семейство. Количество колонок в записи ограничено теоретически 2 миллиардами. А теперь давайте углубимся в процесс сохранения данных в кассандру и их чтения.[Источник 1]

Особенности

Распределение данных

Рассмотрим каким образом данные распределяются в зависимости от ключа по узлам кластера (cluster nodes) (см. рисунок 3). Кассандра позволяет задавать стратегию распределения данных. Первая такая стратегия распределяет данные в зависимости от md5 значения ключа — случайный разметчик (random partitioner). Вторая учитывает само битовое представление ключа — порядковый разметчик (byte-ordered partitioner). Первая стратегия, в большинстве своем, даёт больше преимуществ, так как вам не нужно заботиться о равномерном распределение данных между серверами и подобных проблемах. Вторую стратегию используют в редких случаях, например если необходимы интервальные запросы (range scan). Важно заметить, что выбор этой стратегии производится перед созданием кластера и фактически не может быть изменён без полной перезагрузки данных.

Для распределения данных кассандра использует технику, известную как согласованное хеширование (consistent hashing). Этот подход позволяет распределить данные между узлами и сделать так, что при добавлении и удалении нового узла количество пересылаемых данных было небольшим. Для этого каждому узлу ставится в соответствие метка (token), которая разбивает на части множество всех md5 значений ключей. Так как в большинстве случаев используется RandomPartitioner, рассмотрим его. Как я уже говорил, RandomPartitioner вычисляет 128-битный md5 для каждого ключа. Для определения в каких узлах будут храниться данные, просто перебираются все метки узлов от меньшего к большему, и, когда значение метки становится больше, чем значение md5 ключа, то этот узел вместе с некоторым количеством последующих узлов (в порядке меток) выбирается для сохранения. Общее число выбранных узлов должно быть равным уровню репликации (replication factor). Уровень репликации задаётся для каждого пространства ключей и позволяет регулировать избыточность данных (data redundancy).

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

Согласованность данных

Узлы кластера кассандры равноценны, и клиенты могут соединятся с любым из них, как для записи, так и для чтения. Запросы проходят стадию координации, во время которой, выяснив при помощи ключа и разметчика на каких узлах должны располагаться данные, сервер посылает запросы к этим узлам. Будем называть узел, который выполняет координацию — координатором (coordinator), а узлы, которые выбраны для сохранения записи с данным ключом — узлами-реплик (replica nodes). Физически координатором может быть один из узлов-реплик — это зависит только от ключа, разметчика и меток.

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

Запись

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

  • ONE — координатор шлёт запросы всем узлам-реплик, но, дождавшись подтверждения от первого же узла, возвращает управление пользователю;
  • TWO — то же самое, но координатор дожидается подтверждения от двух первых узлов, прежде чем вернуть управление;
  • THREE — аналогично, но координатор ждет подтверждения от трех первых узлов, прежде чем вернуть управление;
  • QUORUM — собирается кворум: координатор дожидается подтверждения записи от более чем половины узлов-реплик, а именно round(N / 2) + 1, где N — уровень репликации;
  • LOCAL_QUORUM — координатор дожидается подтверждения от более чем половины узлов-реплик в том же центре обработки данных, где расположен координатор (для каждого запроса потенциально свой). Позволяет избавиться от задержек, связанных с пересылкой данных в другие центры обработки данных. Вопросы работы с многими центрами обработки данных рассматриваются в этой статье вскользь;
  • EACH_QUORUM — координатор дожидается подтверждения от более чем половины узлов-реплик в каждом центре обработки данных независимо;
  • ALL — координатор дожидается подтверждения от всех узлов-реплик;
  • ANY — даёт возможность записать данные, даже если все узлы-реплики не отвечают. Координатор дожидается или первого ответа от одного из узлов-реплик, или когда данные сохранятся при помощи направленной отправки (hinted handoff) на координаторе.

Чтение

Для чтения уровень согласованности будет влиять на количество узлов-реплик, с которых будет производиться чтение (см. рисунок 5). Для чтения существуют такие уровни согласованности:

  • ONE — координатор шлёт запросы к ближайшему узлу-реплике. Остальные реплики также читаются в целях чтения с исправлением (read repair) с заданной в конфигурации кассандры вероятностью;
  • TWO — то же самое, но координатор шлёт запросы к двум ближайшим узлам. Выбирается то значение, которое имеет большую метку времени;
  • THREE — аналогично предыдущему варианту, но с тремя узлами;
  • QUORUM — собирается кворум, то есть координатор шлёт запросы к более чем половине узлов-реплик, а именно round(N / 2) + 1, где N — уровень репликации;
  • LOCAL_QUORUM — собирается кворум в том центре обработки данных, в котором происходит координация, и возвращаются данные с последней меткой времени;
  • EACH_QUORUM — координатор возвращает данные после собрания кворума в каждом из центров обработки данных;
  • ALL — координатор возвращает данные после прочтения со всех узлов-реплик.

Таким образом, можно регулировать временные задержки операций чтения, записи и настраивать согласованность (tune consistency), а также доступность (availability) каждой из видов операций. По сути, доступность напрямую зависит от уровня согласованности операций чтения и записи, так как он определяет, сколько узлов-реплик может выйти из строя, и при этом эти операции все ещё будут подтверждены.

Если число узлов, с которых приходит подтверждения о записи, в сумме с числом узлов, с которых происходит чтение, больше, чем уровень репликации, то у нас есть гарантия, что после записи новое значение всегда будет прочитано, и это называется строгой согласованностью (strong consistency). При отсутствии строгой согласованности существует возможность того, что операция чтения возвратит устаревшие данные.

В любом случае, значение в конце концов распространится между репликами, но уже после того, как закончится координационное ожидание. Такое распространение называется итоговой согласованностью (eventual consistency). Если не все узлы-реплики будут доступны во время записи, то рано или поздно будут задействованы средства восстановления, такие как чтение с исправлением и анти-энтропийное восстановление узла (anti-entropy node repair). Об этом чуть позже.

Таким образом, при уровне согласованности QUORUM на чтение и на запись всегда будет поддерживаться строгая согласованность, и это будет некий баланс между задержкой операции чтения и записи. При записи ALL, а чтении ONE будет строгая согласованность, и операции чтения будут выполняться быстрее и будут иметь большую доступность, то есть количество вышедших из строя узлов, при котором чтение все еще будет выполнено, может быть большим, чем при QUORUM. Для операций записи же потребуются все рабочие узлы-реплик. При записи ONE, чтении ALL тоже будет строгая согласованность, и операции записи будут выполняться быстрее и доступность записи будет большой, ведь будет достаточно подтвердить лишь, что операция записи прошла хотя бы на одном из серверов, а чтение — медленней и требовать всех узлов-реплик. Если же к приложению нету требования о строгой согласованности, то появляется возможность ускорить и операции чтения и операции записи, а также улучшить доступность за счет выставления меньших уровней согласованности.[Источник 2]

Восстановление данных

Кассандра поддерживает три механизма восстановления данных:

  • чтение с восстановлением (read repair) — во время чтения данные запрашиваются со всех реплик и сравниваются уже после завершения координации. Та колонка, которая имеет последнюю метку времени, распространится на узлы, где метки устаревшие.
  • направленной отправки (hinted handoff) — позволяет сохранить информацию об операции записи на координаторе в том случае, если запись на какой-либо из узлов не удалась. Позже, когда это будет возможно, запись повторится. Позволяет быстро производить восстановление данных в случае краткосрочного отсутствия узла в кластере. Кроме того, при уровне согласованности ANY позволяет добиться полной доступности для записи (absolute write availability), когда даже все узлы-реплик недоступны, операция записи подтверждается, а данные сохранятся на узле-координаторе.
  • анти-энтропийное восстановление узла (anti-entropy node repair) — это некий процесс восстановления всех реплик, который должен запускаться регулярно вручную при помощи команды “nodetool repair” и позволяет поддержать количество реплик всех данных, которые возможно были не восстановлены первыми двумя способами, на требуемом уровне репликации.

Запись и чтение с диска

Когда данные приходят после координации на узел непосредственно для записи (см. рисунок 6), то они попадают в две структуры данных: в таблицу в памяти (memtable) и в журнал закрепления (commit log). Таблица в памяти существует для каждого колоночного семейства и позволяет запомнить значение моментально. Технически это хеш-таблица (hashmap) с возможностью одновременного доступа (concurrent access) на основе структуры данных, называемой “списками с пропусками” (skip list). Журнал закрепления один на всё пространство ключей и сохраняется на диске. Журнал представляет собой последовательность операций модификации. Так же он разбивается на части при достижении определённого размера.

Такая организация позволяет сделать скорость записи ограниченной скоростью последовательной записи на жесткий диск и при этом гарантировать долговечность данных (data durability). Журнал закрепления в случае аварийного останова узла читается при старте сервиса кассандры и восстанавливает все таблицы в памяти. Получается, что скорость упирается во время последовательной записи на диск, а у современных жёстких дисков это порядка 100МБ/с. По этой причине журнал закрепления советуют вынести на отдельный дисковый носитель.

Понятно, что рано или поздно память может заполниться. Поэтому таблицу в памяти также необходимо сохранить на диск. Для определения момента сохранения существует ограничение объёма занимаемыми таблицами в памяти (memtable_total_spacein_mb), по умолчанию это ⅓ максимального размера кучи Java (Java heapspace). При заполнении таблицами в памяти объёма больше чем это ограничение, кассандра создает новую таблицу и записывает старую таблицу в памяти на диск в виде сохраненной таблицы (SSTable). Сохранённая таблица после создания больше никогда не модифицируется (is immutable). Когда происходит сохранение на диск, то части журнала закрепления помечаются как свободные, таким образом освобождая занятое журналом место на диске. Нужно учесть, что журнал имеет переплетённую структуру из данных разных колоночных семейств в пространстве ключей, и какие-то части могут быть не освобождены, так как некоторым областям будут соответствовать другие данные, все ещё находящиеся в таблицах в памяти.

В итоге, каждому колоночному семейству соответствует одна таблица в памяти и некоторое число сохранённых таблиц. Теперь, когда узел обрабатывает запрос чтения, ему необходимо запросить все эти структуры и выбрать самое последнее по метке времени значение. Для ускорения этого процесса существует три механизма: блум-фильтрация (bloom filter), кэш ключей (key cache) и кэш записей (record cache):

  • блум-фильтр — это структура данных, которая занимает немного места и позволяет ответить на вопрос: содержится ли элемент, а в нашем случае это ключ, в множестве или нет. При чем, если ответ — “нет”, то это 100%, а если ответ “да”, то это, возможно, ложно-положительный ответ. Это позволяет уменьшить количество чтений из сохранённых таблиц;
  • кэш ключей сохраняет позицию на диске записи для каждого ключа, таким образом уменьшая количество операций позиционирования (seek operations) во время поиска по сохранённой таблице;
  • кэш записей сохраняет запись целиком, позволяя совсем избавиться от операций чтения (см. рисунок 7) с диска.

Уплотнение

В определенный момент времени данные в колоночном семействе перезапишутся — придут колонки, которые будут иметь те же имя и ключ (см. рисунок 8). То есть, возникнет ситуация, когда в более старой сохранённой таблице и более новой будут содержаться старые и новые данные. Для того, чтобы гарантировать целостность, кассандра обязана читать все эти сохранённые таблицы и выбирать данные с последней меткой времени. Получается, что количество операций позиционирования жёсткого диска при чтении пропорционально количеству сохранённых таблиц. Поэтому для того, чтобы освободить перезаписанные данные и уменьшить количество сохранённых таблиц, существует процесс уплотнения (compaction). Он читает последовательно несколько сохранённых таблиц и записывает новую сохранённую таблицу, в которой объединены данные по меткам времени. Когда таблица полностью записана и введена в использование, кассандра может освободить таблицы-источники(таблицами, которые её образовали). Таким образом, если таблицы содержали перезаписанные данные, то эта избыточность устраняется. Понятно, что во время такой операции объем избыточности увеличивается — новая сохранённая таблица существует на диске вместе с таблицами-источниками, а это значит, что объем места на диске всегда должен быть такой, чтобы можно было произвести уплотнение.

Кассандра позволяет выбрать одну из двух стратегий проведения уплотнения:

  • стратегия уплотнения сохраненных таблиц связанных размером (size-tiered compaction) — эта стратегия уплотняет определенным образом выбранные две таблицы. Применяется автоматически в виде фонового уплотнения (minor compaction) и в ручном режиме, для полного уплотнения (major compaction). Допускает ситуацию нахождения ключа во многих таблицах и, соответственно, требует выполнять операцию поиска для каждой такой таблицы.
  • стратегия уплотнение сохраненных таблиц уровнями (leveled compaction) — уплотняет сохраненные таблицы, которые изначально создаются небольшими — 5 МБ, группируя их в уровни. Каждый уровень в 10 раз больший чем предыдущий. Причем, существуют такие гарантии: 90% запросов чтения будут происходить к одной сохраненной таблице, и только 10% пространства на диске будет использоваться под устаревшие данные. В этом случае для выполнения уплотнения под временную таблицу достаточно только 10-кратного размера таблицы, то есть 50 Мб.[2]

Операции удаления

С точки зрения внутреннего устройства, операции удаление колонок — это операции записи специального значения — затирающего значения (tombstone). Когда такое значение получается в результате чтения, то оно пропускается, словно такого значения никогда и не существовало. В результате же уплотнения, такие значения постепенно вытесняют устаревшие реальные значения и, возможно, исчезают вовсе. Если же появятся колонки с реальными данными с еще более новыми метками времени, то они перетрут, в конце концов, и эти затирающие значения.

Транзакционность

Кассандра поддерживает транзакционность на уровне одной записи, то есть для набора колонок с одним ключом. Вот как выполняются четыре требования ACID: атомарность (atomicity) — все колонки в одной записи за одну операцию будут или записаны, или нет; согласованность (consistency) — как уже было сказано выше, есть возможность использовать запросы с строгой согласованностью взамен доступности, и тем самым выполнить это требование; изолированность (isolation) — начиная с кассандры версии 1.1, появилась поддержка изолированности, когда во время записи колонок одной записи другой пользователь, который читает эту же запись, увидит или полностью старую версию записи или, уже после окончания операции, новую версию, а не часть колонок из одной и часть из второй; долговечность (durability) обеспечивается наличием журнала закрепления, который будет воспроизведён и восстановит узел до нужного состояния в случае какого-либо отказа.

Дальнейшее развитие

С целью обеспечения надежности и стабильности в Apache Cassandra 4.0 разработчики проекта проголосовали за замораживание новых функций базы данных версии 3.х.х 1 сентября 2018 года, чтобы сосредоточиться на тестировании и валидации, прежде чем выкатить стабильную бета-версию. Для достижения этой цели сообщество инвестирует в методологии, которые могут быть выполнены в масштабе для тестирования границ возможностей в крупнейших кластерах Cassandra.

Аппаратно-ориентированная потоковая передача Zero Copy в Apache Cassandra 4.0

Потоковое вещание в Apache Cassandra обеспечивает замену хоста, перемещение диапазонов и расширение кластеров (см. рисунок 9). Потоковая передача играет ключевую роль в кластере, и, следовательно, его производительность является ключом не только к скорости операций, в которых он используется, но и к состоянию кластера в целом. В Apache Cassandra 4.0 будет представлена улучшенная реализация потоковой передачи, которая снижает давление GC и увеличивает пропускную способность в несколько раз и теперь она будет ограничена, в некоторых случаях, только дисковым/сетевым вводом-выводом (см. CASSANDRA-14556).

Потоковая передача Cassandra Чтобы понять влияние этих изменений, давайте сначала посмотрим на текущий путь потокового кода. Диаграмма ниже иллюстрирует настройку потокового сеанса, когда узел пытается передать данные от однорангового узла. Допустим, у нас есть кластер из 3 узлов (узлы A, B, C). Узел C перестраивается и должен передавать все данные, за которые он отвечает, из A & B. C устанавливает потоковую сессию с каждым из своих партнеров (см. CASSANDRA-4560, как Cassandra применяет алгоритм Форда-Фалкерсона[3] для оптимизации потоковых партнеров). Он обменивается сообщениями для запроса диапазонов и начинает потоковую передачу данных с выбранных узлов.

На этапе потоковой передачи A собирает все таблицы SST[4], в которых есть разделы в запрошенных диапазонах. Он транслирует каждый SSTable путем сериализации отдельных разделов. После получения раздела узел C преобразует данные в памяти и затем записывает их на диск. Это необходимо для точной передачи разделов из всех возможных таблиц SST для требуемых диапазонов. Этот потоковый путь генерирует мусор, и его можно избежать в сценариях, где необходимо передавать все разделы в SSTable. Это часто встречается, когда вы используете LeveledCompactionStrategy или включили разбиение SSTables по диапазону токенов (см .: CASSANDRA-6696) и т. Д.

Для решения этой проблемы CASSANDRA-14556 добавляет потоковый путь Zero Copy. Это значительно ускоряет передачу SSTables и уменьшает количество мусора и ненужных объектов. Он изменяет путь потоковой передачи, добавляя дополнительную информацию в заголовок потоковой передачи, и использует API ZeroCopy для передачи байтов в сеть и на диск и из нее. Таким образом, теперь SSTable может передаваться с использованием этой стратегии, когда Кассандра обнаруживает, что необходимо передать полный SSTable.

Ведение журнала аудита в Apache Cassandra 4.0

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

До Apache Cassandra 4.0 у сообщества с открытым исходным кодом не было хорошего способа отслеживания такой критической активности базы данных. С этой целью Netflix внедрила CASSANDRA-12151, чтобы у пользователей Cassandra был простой, но мощный инструмент ведения журнала аудита, встроенный в их базу данных изначально.

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

  • Соответствие нормативным требованиям таких законов, как SOX, PCI и GDPR. Эти типы соответствия имеют решающее значение для компаний, которые торгуются на государственных фондовых биржах, хранят платежную информацию, такую ​​как кредитные карты, или сохраняют личную информацию пользователя.
  • Соответствие безопасности. В компаниях часто существуют строгие правила относительно того, какие данные могут быть доступны для каких сотрудников, как для защиты конфиденциальности пользователей, так и для ограничения вероятности взлома данных.
  • Отладка сложных ошибок, связанных с повреждением данных, таких как ошибки в массивно распределенных архитектурах микросервисов, таких как Netflix.

Цели проектирования

Цели разработки журнала аудита в целом разделены на 3 области:

  • Производительность. Учитывая, что точки внедрения журнала аудита работают в пути запроса, производительность является важной целью при принятии любого решения.
  • Точность: Точность требуется при соблюдении требований и, следовательно, является важной целью. Журнал аудита должен быть в состоянии ответить на важные вопросы аудитора, такие как «Каждый ли запрос записи в базу данных проверяется?». Таким образом, точность не может быть поставлена ​​под угрозу.
  • Удобство использования и расширяемость. Разнообразная экосистема Cassandra требует, чтобы любая часто используемая функция была легко используемой и подключаемой (например, Compaction, Compression, SeedProvider и т. Д.), Поэтому интерфейс Audit Log был разработан с учетом этого контекста с самого начала.

Реализация

Имея в виду эти три цели проектирования, библиотеки OpenHFT были очевидным выбором из-за их надежности и высокой производительности. Ранее в CASSANDRA-13983 библиотека хронических очередей OpenHFT была представлена ​​как утилита BinLog для базы кода Apache Cassandra. Производительность Full Query Logging (FQL) была превосходной, но она только обеспечивала мутацию и чтение путей запросов. В нем отсутствовало много важных данных, например, когда запросы не выполнялись, откуда они пришли и какой пользователь отправил запрос. FQL также была единственной целью: предпочитать отбрасывать сообщения, а не задерживать процесс (что имеет смысл для FQL, но не для ведения журнала аудита). Наконец, FQL не допускает возможности подключения, что затруднит его адаптацию в кодовой базе.

Как показано на рисунке 10, разработчики смогли объединить функцию FQL с функцией AuditLog с помощью абстракций AuditLogManager и IAuditLogger. Используя эту архитектуру, возможно поддерживать любой выходной формат: журналы, файлы, базы данных и т. д. По умолчанию реализация BinAuditLogger поставляется вместе с продуктом для поддержания производительности. Пользователи могут выбрать пользовательскую реализацию журнала аудита, поместив файл jar на путь к классам Cassandra и настроив параметры конфигурации в файле cassandra.yaml. [Источник 3]

От автора

Автор предлагает интересные по его мнению ссылки для дальнейшего ознакомления:

  • Страница на apache.org cassandra.apache.org [Литература 1]
  • Документация на datastax.com [Литература 2]
  • Про BigTable [Литература 3]
  • Про Amazon Dynamo [Литература 4]

Установка

Установка Apache Cassandra на Debian 7 Single node.

Добавление узла в Apache Cassandra.

Установка на Ubuntu 16.04

Источник: ru.bmstu.wiki


Добавить комментарий

Ваш e-mail не будет опубликован. Обязательные поля помечены *

Этот сайт использует Akismet для борьбы со спамом. Узнайте, как обрабатываются ваши данные комментариев.