- PVSM.RU - https://www.pvsm.ru -
Статья предназначена для людей пытающихся создать свою первую «таблицу» в БД Cassandra.
За посление несколько релизов Кассандры разработчики взяли правильный вектор направленный на простоту использования этой базы данных. Учитывая её достоинства, такие как скорость работы и отказоустойчиваость, её было сложно как администрировать, так и писать под неё. Сейчас же количество танцев с бубном, которые надо провести прежде чем запустить и начать разрабатывать, свели к минимуму — несколько комманд в bash или один .msi в Windows.
Более того, сильно облегчил жизнь разработчикам недавно обновлённый CQL (язык запросов), вытеснив бинарный и довольно сложный язык Thrift.
Лично я столкнулся с проблемой наличия отсуствия русскоязычных руководств по Кассандре. Самую, на мой взгляд, сложную тему мне бы хотелось поднять в этой статье. Как же дизайнить базу данных то?
Кассандра создавалась как распределённая БД с упором на максимальную скорость записи и чтения. Моделировать «таблицы» нужно в зависимости от SELECT запросов вашего приложения.
В SQL мы привыкли накидать таблиц, связей между ними, и потом уже SELECT ... JOIN ... чего хотим и как хотим. Именно JOIN-ы основная проблема с произвоидтельностью в RDBMS. Их нет в CQL.
У нас есть сотрудники какой-то компании. Создадим таблицу (которые на самом деле называются Column Family, но для простоты перехода с SQL на CQL используют слово table) на CQL и заполним данными:
CREATE TABLE employees (
name text,
age int,
role text,
PRIMARY KEY (name)
);
INSERT INTO employees (name, age, role) VALUES ('john', 37, 'dev');
INSERT INTO employees (name, age, role) VALUES ('eric', 38, 'ceo');
Таблицы в C* обязаны иметь PRIMARY KEY. Он используется для поиска ноды, в которой хранится искомая строка.
Прочитаем данные:
SELECT * FROM employees;
Эта картинка — руками разукрашенный вывод cqlsh.

Выглядит как обычная таблица из реляционной БД. C* создаст две строки.

Внимание! Это две внутренние структуры строк, а не таблицы. Если чуть слукавить, то можно сказать, что каждая строка — это как маленькая таблица. Далее понятней.
Усложняем. Добавим название компании.
CREATE TABLE employees (
company text,
name text,
age int,
role text,
PRIMARY KEY (company,name)
);
INSERT INTO employees (company, name, age, role) VALUES ('OSC', 'john', 37, 'dev');
INSERT INTO employees (company, name, age, role) VALUES ('OSC', 'eric', 38, 'ceo');
INSERT INTO employees (company, name, age, role) VALUES ('RKG', 'anya', 38, 'lead');
INSERT INTO employees (company, name, age, role) VALUES ('RKG', 'ben', 38, 'dev');
INSERT INTO employees (company, name, age, role) VALUES ('RKG', 'chan', 38, 'ops');
Прочитаем данные:
SELECT * FROM employees;

Внимание на PRIMARY KEY. Первый из параметров — company — это главный ключ, именно он будет использоваться для поиска ноды с этих пор. Второй ключ — name — превращается в колонку. Т.е. мы данные превращаем в название колонки. Был 'eric' обычными четырмя байтами, а стал частью названия колонки.
Вот так теперь выглядит внутреняя структура.

Как видите у нас:
OSC и RKG. Здесь создалось всего две строки.eric хранит свой возраст и роль в двух ячейках. Аналогично все остальные.Ещё сложнее. Заглавная буква — название колонки. Строчная — данные.
CREATE TABLE example (
A text,
B text,
C text,
D text,
E text,
F text,
PRIMARY KEY ((A,B), C, D)
);
INSERT INTO example (A, B, C, D, E, F) VALUES ('a', 'b', 'c', 'd', 'e', 'f');
INSERT INTO example (A, B, C, D, E, F) VALUES ('a', 'b', 'c', 'g', 'h', 'i');
INSERT INTO example (A, B, C, D, E, F) VALUES ('a', 'b', 'j', 'k', 'l', 'm');
INSERT INTO example (A, B, C, D, E, F) VALUES ('a', 'n', 'o', 'p', 'q', 'r');
INSERT INTO example (A, B, C, D, E, F) VALUES ('s', 't', 'u', 'v', 'w', 'x');
Прочитаем данные:
SELECT * FROM example;

Теперь наш главный ключ составной — (A,B).
Внутрення структура усложнилась. Такие данные как c, d, g, k, o, p, u, v участвуют в названии колонок наравне с E и F:

a:b, a:n и s:t.a:b у нас три уникальных комбинации — c:d, c:g, j:k — которые хранят в колонках E и F собственно данные — e:f, h:i, l:m.Это самый быстрый способ записи и хранения бесконечного количества данных в распределённой БД. C* как раз была разработана с упором на скорость записи/чтения. Вот, например, сравнение скоростей MongoDB, HBase и С* [3].
У нас есть некие события, которые происходят 1000 раз в секунду. Например с датчиков уровня шума снимаются показатели. 10 датчиков. Каждый из них присылает данные 100 раз в секунду. У нас 3 задачи:
Нам нужно установить несколько нод, сделать каждую автономной. Может даже вынести одну из них в облако.
Мы будем хранить данные одного дня в одной строке.
CREATE TABLE temperature_events_by_day (
day text, -- Text of the following format: 'YYYY-MM-DD'
sensor_id uuid,
event_time timestamp,
temperature double,
PRIMARY KEY ((day,sensor_id), event_time)
)
WITH CLUSTERING ORDER BY event_time DESC; -- reverse sort the temperature values
Так как главным ключём является уникальная комбинация день+датчик, то данные за один день будут храниться для каждого датчика в отдельной строке. Благодаря обратной сортировке внутри строки мы получаем самые важные для нас данные (последние) «на кончике пальцев».
Так как поиск главного ключа (дня) — очень быстрая операция в С*, то третий пункт можно считать выполненным.
Конечно, мы можем сделать поиск дня/дней, а внутри дня уже сравнивать timestamp. Но дней может быть очень много.
У нас ведь всего 10 датчиков. Нельзя ли этим воспользоваться? Можно, если представить, что один датчик — одна строка. В этом случае С* закеширует в памяти местоположение всех десяти строк на диске.
Создадим вторую таблицу, где будем хранить те же самый данные, но без учета дней.
CREATE TABLE temperature_events (
sensor_id uuid,
event_time timestamp,
temperature double,
PRIMARY KEY (sensor_id, event_time)
)
WITH CLUSTERING ORDER BY event_time DESC;
И когда будем вставляеть данные, то ограничим время жизни каждой ячейки значением 248 дней чтобы не привысить 2 млрд колонок. 2147483648 / (24*60*60*100) = 248.55. Через 248 дней самые старые данные будут тихо и незаметно самоудаляться.
INSERT INTO temperature_events (sensor_id, event_time, temperature)
VALUES ('12341234-1234-1234-123412', 2535726623061, 36.6)
TTL 21427200; -- 248 days in seconds
В коде приложения нужно будет поставить условие, что если запрашиваемые данные выходят за границы последних 248 дней, то используем таблицу temperature_events_by_day, если нет — temperature_events. Поиск по последней будет на несколько миллисекунд быстрее.
«Что за бред! Зачем вторую таблицу?» — подумаете вы. Повторюсь: в БД С* хранить одно и то же значение по нескольку раз — это норма, правильная модель. Выигрыши следующие:
Рекомендую к просмотру именно в этом порядке.
P.S. Первая статья на хабре. Прошу указать на мои недочеты. Спасибо.
Автор: koresar
Источник [8]
Сайт-источник PVSM.RU: https://www.pvsm.ru
Путь до страницы источника: https://www.pvsm.ru/nosql/49128
Ссылки в тексте:
[1] Cassandra: http://cassandra.apache.org/
[2] CQL: http://cassandra.apache.org/doc/cql3/CQL.html
[3] сравнение скоростей MongoDB, HBase и С*: http://www.datastax.com/wp-content/uploads/2013/02/WP-Benchmarking-Top-NoSQL-Databases.pdf
[4] Understanding How CQL3 Maps to Cassandra's Internal Data Structure: http://youtu.be/UP74jC1kM3w
[5] The Data Model is Dead, Long Live the Data Model: http://youtu.be/px6U2n74q3g
[6] Become a Super Modeler: http://youtu.be/qphhxujn5Es
[7] The World's Next Top Data Model: http://youtu.be/T_WRC_GjRd0
[8] Источник: http://habrahabr.ru/post/203200/
Нажмите здесь для печати.