СоХабр закрыт.

С 13.05.2019 изменения постов больше не отслеживаются, и новые посты не сохраняются.

Моделирование данных в БД Cassandra 2.0 на CQL3 в черновиках Tutorial

Статья предназначена в основном для новичков.

Ликбез


  • Cassandra (далее C*) — распределённая NoSQL БД, поэтому все решения «почему так, а не вот так» всегда принимаются с оглядкой на кластеризацию.
  • CQL — это SQL-подобный язык. Аббревиатура от Cassandra Query Language.
  • Node (нода) — инстанс C*, или java процесс в терминах операционных систем. На одной машине можно запустить несколько нод, например.
  • Основная единица хранения — строка. Строка может динамически раширятся до 2 миллиардов колонок. Это важно.
  • cqlsh — коммандная строка для CQL. Все примеры ниже выполняются именно в ней. Является частью дистрибутива C*.


Первый пример.

У нас есть сотрудники какой-то компании. Создадим таблицу на 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 хранит свой возраст и роль в двух ячейках. Аналогично все остальные.
  • Получается с такой структурой мы можем хранить 1 млрд сотрудников в каждой компании (строке). Помним же, что лимит количества колонок — 2 млрд?
  • Может показаться, что мы лишний раз храним одни и те же данные. Это так, но в C* такой дизайн — правильный паттерн моделирования.
  • Раширять строки — это основная фича при моделировании в С*.


Третий пример.

Ещё сложнее.
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).

Внутрення структура усложнилась:


  • Как видите, теперь каждая уникальная комбинация A и B — это ключ к строке.
  • У нас всего три уникальных ключа — 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 и С*.

Примеры из реальной жизни


У нас есть некие события, которые происходят 1000 раз в секунду. Например с датчиков уровня шума снимаются показатели. 100 датчиков. Каждый из них присылает данные 10 раз в секунду. У нас 3 задачи:
  1. Продолжать записывать, если сервер БД (нода) остановит свою работу.
  2. Предоставлять график любого датчика за любой день за пару-тройку миллисекунд.
  3. Предоставлять график любого датчика за последние несколько часов за пару-тройку миллисекунд.


Первый пункт — легко. Нам нужно установить несколько нод, сделать каждую автономной. Может даже вынесту одну из них в облако.

Второй пункт — леко. Когда будем добавлять данные в таблицу укажем TTL:
INSERT INTO table_name (...) VALUES (...) TTL 3628800; -- 42 days

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

Третий пункт — основная хитрость. Мы будем хранить данные одного дня в одной строке.
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

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

Одновременно создадим вторую таблицу, где будем хранить те же самый данные, но без учета дней.
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;

«Что за бред! Зачем вторую таблицу?» — подумаете вы. Повторюсь: в БД С* хранить одно и то же значение по нескольку раз — это норма, правильная модель. Выигрыши следующие:
  • Запись данных очень и очень быстрая. Тысячи операций в секунду без каких-либо усилий по оптимизации.
  • Чтение данных тоже очень быстрое. В разы превосходит обычную индексированную, нормированную SQL БД.
  • Кластеру достаточно найти одну лишь строку по её ключу, чтобы получить моментальный доступ ко всем данным датчика.


Источники


Рекомендую к просмотру именно в этом порядке.
  1. Вебинар — Understanding How CQL3 Maps to Cassandra's Internal Data Structure.
  2. Вебинар — The Data Model is Dead, Long Live the Data Model
  3. Вебинар — Become a Super Modeler
  4. Вебинар — The World's Next Top Data Model
  5. Полная документация по CQL3- Cassandra Query Language (CQL) v3.1.1

комментарии (12)

+4
Indexator ,  

Ещё!

0
koresar ,  

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

0
Indexator ,  

Да мне, в принципе, все интересно) Рассказывайте о том, что хорошо знаете :)

0
koresar ,  

ok :)

0
rmk ,  

Очень неплохо, спасибо!

+1
rmk ,  
бинарный и довольно сложный язык Thrift.


Thrift скорее rpc протокол, чем язык.
+1
koresar ,  

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

0
Jacetejob ,  

Интересно. Но единственное что не понятно, почему в Касандре пытаются применить ОЧЕНЬ похожий на реляционный TSQL язык CQL.
У NOSQL свой путь и, имхо, не надо работу с ней делать похожим на всем угодный TSQL ;) Это приводит к тому что многие пытаются думать реляционно в nosql средах.

0
kingu ,  

Урезанный SQL гораздо проще воспринимается чем thift api.

0
koresar ,  

С одной стороны всё верно, люди начинают применять SQL модель к Кассандре.
Но с другой стороны переход с SQL на CQL сильно упрощается (thrift имеет высокий порог входа).
Люди продвигающие CQL утверждают, что Кассандра стала более популярной именно благодаря CQL.

0
Yggaz ,  

Уххх ты как интересно! Мне как sql-щику интересно исключительно. Запросы, запросы покажите. Пример с датчиками прекрасен, но он только один. Как я понял, джойнов не бывает (в этом вроде бы и смысл) — а что бывает? Ограничения? Сортировки? Группировки?

Не знаю, получилось у меня внятно высказаться или нет. На всякий случай — у меня и в мыслях нет доказывать, что «Cassandra — это неправильный дизайн данных». Я убеждён, что это *правильный* дизайн, но мне совершенно не знакомый, и хочу узнать больше про эту часть реальности.

В общем, спасибо за статью, пишите ещё. Пошёл смотреть вебинары :).

0
koresar ,  

После просмотра вебинаров вопросы отпадут сами собой. :) Там куча примеров.
Но следующая статья уже запланирована. Надеюсь родить через недельку.