aleksey yeschenko "Моделирование данных с помощью cql3"....
TRANSCRIPT
@AYeschenko
Aleksey YeschenkoApache Cassandra Committer, Engineer @DataStax
Моделирование данных с CQL3: типичные паттерны и анти-паттерны
Кто я такой• Backend Ruby web-dev (давно и неправда) • Telecom Erlang dev (недавно и правда) •Принуждают использовать Java+Python (cqlsh) в DataStax с июля 2012 • Apache Cassandra committer с ноября 2012 •@AYeschenko
Когда использовать C*•Необходима Active-Active репликация между несколькими дата-центрами •Необходим (около) 100% аптайм •Данные больше не умещаются в PostgreSQL/MySQL/etc. • PostgreSQL/MySQL/etc. больше не справляются с объёмом записи •Модель Cassandra изначально идеально подходит для приложения •Нет необходимости в ad-hoc запросах •Любое другое решение стоит $$$$$
* 1 и более пунктов из списка == true
Когда не использовать C*• Во всех остальных случаях
Как использовать, если использовать
Корни С*
Dynamo
BigTable
• http://www.allthingsdistributed.com/2007/10/amazons_dynamo.html • http://research.google.com/archive/bigtable.html
Как хранятся данные + терминология
Имя ячейки ... Имя ячейки
Значение ячейки Значение ячейки
Timestamp Timestamp
TTL TTL
Partition ключ
1 2 Миллиарда
• Partition ключ и множество ячеек (до 2 миллиардов) • Хэш partition ключа определяет ноды, к которым принадлежит partition • Быстрое чтение по partition ключу •На диске и в памяти, ячейки всегда отсортированы по имени • Компаратор для сортировки задаётся пользователем
Идеальная нагрузка• Запись доминирует •Перезапись/удаление ячеек отсутствуют или не очень часты • Чтение по ключу + (имени ячейки или небольшим отрезкам ячеек в порядке компаратора)
Идеальная нагрузка•Логи •Данные с сенсоров • Финансовые данные (ticks) • Ads - клики и отображения, аналитика •Необязательно именно временные ряды (в порядке timestamp); любые данные, естественно упорядоченные по последовательному значению •Нужны последние N значений
Паттерн #1: временной ряд
CREATE TABLE temperature (
weatherstation_id text,
event_time timestamp,
temp int,
PRIMARY KEY (weatherstation_id, event_time)
) WITH CLUSTERING ORDER BY (event_time DESC);
partition ключ
кластеринговая колонка
составной ключобратный порядок сортировки ячеек
• Каждая станция регулярно собирает показания •Один partition == одна станция •Одно показание == одна ячейка (здесь две, технически) •Потенциально очень широкий partition
Паттерн #1: временной ряд
CREATE TABLE temperature (
weatherstation_id text,
event_time timestamp,
temp int,
PRIMARY KEY (weatherstation_id, event_time)
) WITH CLUSTERING ORDER BY (event_time DESC);
partition ключ
кластеринговая колонка
составной ключобратный порядок сортировки ячеек
1386475781 1386475781:temp 1386475780 1386475780:temp 1386475779 1386475779:temp …
* -6 * -6 * -5 …SVO
weatherstation_id event_time temp * маркеры CQL3 ряда
Паттерн #1: временной ряд
CREATE TABLE temperature (
weatherstation_id text,
date text,
event_time timestamp,
temp int,
PRIMARY KEY ((weatherstation_id, date), event_time)
) WITH COMPACT STORAGE AND CLUSTERING ORDER BY (event_time DESC);
составной partition ключ
•Один partition для пары {weatherstation_id, date} •Отсутствие оверхеда маркера ряда и имени колонки
Паттерн #1: временной ряд
CREATE TABLE temperature (
weatherstation_id text,
date text,
event_time timestamp,
temp int,
PRIMARY KEY ((weatherstation_id, date), event_time)
) WITH COMPACT STORAGE AND CLUSTERING ORDER BY (event_time DESC);
1386475781 1386475780 1386475779 …
-6 -6 -5 …SVO:2013-12-09
weatherstation_iddate
timestamp temp
составной partition ключ
Паттерн #1: временной ряд
Анти-паттерн #1: неуместное использование встроенных индексов• RDBMS опыт с индексацией НЕ переносится в C*
Анти-паттерн #1: неуместное использование встроенных индексов• RDBMS опыт с индексацией НЕ переносится в C* • Встроенные индексы в C* != RDBMS индексы
Анти-паттерн #1: неуместное использование встроенных индексов• RDBMS опыт с индексацией НЕ переносится в C* • Встроенные индексы в C* != RDBMS индексы • Встроенные индексы в C* != RDBMS индексы
Анти-паттерн #1: неуместное использование встроенных индексов• RDBMS опыт с индексацией НЕ переносится в C* • Встроенные индексы в C* != RDBMS индексы • Встроенные индексы в C* != RDBMS индексы • Встроенные индексы в C* != RDBMS индексы
Анти-паттерн #1: неуместное использование встроенных индексов• RDBMS опыт с индексацией НЕ переносится в C* • Встроенные индексы в C* != RDBMS индексы • Встроенные индексы в C* != RDBMS индексы • Встроенные индексы в C* != RDBMS индексы •Для удобства, не для улучшения производительности •Достаточно ограничены в плане удобства • Следует избегать, по большей части
cqlsh:nope> CREATE TABLE users (
... username varchar,
... email varchar,
... fullname varchar,
... last_login timestamp,
... PRIMARY KEY (username)
... );
!
cqlsh:nope> INSERT INTO users (username, email, fullname) VALUES ('ay', '[email protected]', 'Aleksey Yeschenko');
!
cqlsh:nope> SELECT * FROM users WHERE email = '[email protected]';
Bad Request: No indexed columns present in by-columns clause with Equal operator
Анти-паттерн #1: неуместное использование встроенных индексов
Анти-паттерн #1: неуместное использование встроенных индексов
cqlsh:nope> CREATE INDEX ON users (email);
!
cqlsh:nope> SELECT * FROM users WHERE email = '[email protected]';
username | email | fullname | last_login
----------+----------------------+-------------------+------------
ay | [email protected] | Aleksey Yeschenko | null
!
(1 rows)
Анти-паттерн #1: неуместное использование встроенных индексов
Анти-паттерн #1: неуместное использование встроенных индексов•При высокой и средней кардинальности колонки решается ‘ручным’ вторичным индексом •Отдельная таблица для обратного лукапа
cqlsh:yep> CREATE TABLE users (
... username varchar,
... email varchar,
... fullname varchar,
... last_login timestamp,
... PRIMARY KEY (username)
... );
cqlsh:yep> CREATE TABLE users_by_email_idx (
... email varchar,
... username varchar,
... PRIMARY KEY (email, username)
... );
Анти-паттерн #1: неуместное использование встроенных индексов
cqlsh:yep> BEGIN BATCH
... INSERT INTO users (username, email, fullname) VALUES ('ay', '[email protected]', 'Aleksey Yeschenko');
... INSERT INTO users_by_email_idx (email, username) VALUES ('[email protected]', 'ay');
... APPLY BATCH;
!cqlsh:yep> SELECT username FROM users_by_email_idx WHERE email = '[email protected]';
username
----------
ay
!cqlsh:yep> SELECT * FROM users WHERE username = 'ay';
username | email | fullname | last_login
----------+----------------------+-------------------+------------
ay | [email protected] | Aleksey Yeschenko | null
Анти-паттерн #1: неуместное использование встроенных индексов
•При средней/низкой кардинальности свой, отдельный набор проблем • Главная - большое количество random i/o • Решается правильной моделью/денормализацией вместо использования встроенных индексов
Анти-паттерн #1: неуместное использование встроенных индексов
•Q: Когда вообще следует использовать встроенные индексы? • A: Для поиска рядов с 1+ кластеринговыми колонками внутри определённого partition (зная partition ключ)
Анти-паттерн #1: неуместное использование встроенных индексов
Анти-паттерн #1: неуместное использование встроенных индексовCREATE TABLE example (
partition_key varchar,
clustering_col0 varchar,
clustering_col1 varchar,
val varchar
);
!
CREATE INDEX ON example (val);
!
SELECT * FROM example WHERE partition_key = ‘some_pk’ AND val = ‘some_val’;
Анти-паттерн #1: неуместное использование встроенных индексов
Анти-паттерн #1: неуместное использование встроенных индексовАнти-паттерн #1: неуместное использование встроенных индексов• Всё вышесказанное распространяется на индексы коллекций в 2.1 •Обращайтесь со встроенными индексами как с expert-level feature
Паттерн #2: денормализация
username firstname lastname email
tcodd Edgar Codd [email protected] Raymond Boyce [email protected]
videoid videoname username description tags99051fe9 My funny cat tcodd My cat plays
the pianocats,piano,lol
b3a76c6b Math tcodd Now my dog plays
dogs,piano,lol
Users
Videos
•Отношения без реляционности • Без использования foreign key • Users 1-M Videos
CREATE TABLE videos (! videoid uuid,! videoname varchar,! username varchar,! description varchar, ! tags varchar,! upload_date timestamp,! PRIMARY KEY(videoid)!);
CREATE TABLE users (! username varchar,! firstname varchar,! lastname varchar,! email varchar, ! PRIMARY KEY(username)!);
Паттерн #2: денормализация
CREATE TABLE username_video_index (! username varchar,! videoid uuid,! upload_date timestamp,! video_name varchar,! PRIMARY KEY (username, videoid)!);
SELECT video_name!FROM username_video_index!WHERE username = ‘ctodd’!AND videoid = ‘99051fe9’
•Лукап видео по username • Запись в две таблицы сразу • Без встроенных индексов
Паттерн #2: денормализация
username videoid commenttcodd 99051fe9 Sweet!rboyce b3a76c6b Boring :(
• Users 1-M Comments • Videos 1-M Comments • Без встроенных индексов
username firstname lastname email
tcodd Edgar Codd [email protected] Raymond Boyce [email protected]
videoid videoname username description tags99051fe9 My funny cat tcodd My cat plays
the pianocats,piano,lol
b3a76c6b Math tcodd Now my dog plays
dogs,piano,lol
Users
Videos
Comments
Паттерн #2: денормализация
CREATE TABLE comments_by_video (! videoid uuid,! username varchar,! comment_ts timestamp,! comment varchar,! PRIMARY KEY (videoid,username)!);
CREATE TABLE comments_by_user (! username varchar,! videoid uuid,! comment_ts timestamp,! comment varchar,! PRIMARY KEY (username,videoid)!);
•Две таблицы для выборки по видео и по пользователям • Запись в три таблицы сразу (comments, comments_by_video, comments_by_user) •Не жалеть запись
Анти-паттерн #2: очереди и очереде-подобные нагрузки
Анти-паттерн #2: очереди и очереде-подобные нагрузки• Cassandra не лучшее решение для очередей •Можно, но требует гимнастики •Лучше использовать что-нибудь вроде Kafka/RabbitMQ •Любой паттерн, где в partition удаляются ячейки и выполняются slice сканы, включающие удалённые ячейки •Пример - нарочно упрощён, ради иллюстрации
Анти-паттерн #2: очереди и очереде-подобные нагрузки—— naive путь
CREATE TABLE queues (
name text,
enqueued_at timeuuid,
payload blob,
PRIMARY KEY (name, enqueued_at) —— ячейки упорядочены по времени вставки, ASC
) WITH COMPACT STORAGE;
Анти-паттерн #2: очереди и очереде-подобные нагрузки—— 1000x добавить в очередь:
INSERT INTO queues (name, enqueued_at, payload) VALUES (‘queue-1’, now(), 0x…);
!
—— 999x убрать из очереди, индивидуально:
DELETE FROM queues WHERE name = ‘queue-1’ AND enqueued_at = ?;
!
—— достать последний элемент из очереди:
SELECT enqueued_at, payload
FROM queues
WHERE name = 'queue-1'
LIMIT 1;
Анти-паттерн #2: очереди и очереде-подобные нагрузки
51c220e0-60… 58940a00-60.. 61163b30-60.. 995 x … 6e1ac030-60… ab36fa10-60..
X X X X X <some blob>queue-1
Анти-паттерн #2: очереди и очереде-подобные нагрузкиactivity | source | elapsed
-------------------------------------------+-----------+--------
execute_cql3_query | 127.0.0.3 | 0
Message received from /127.0.0.3 | 127.0.0.1 | 42
Sending message to /127.0.0.1 | 127.0.0.3 | 718
Executing single-partition query on queues | 127.0.0.1 | 145
Acquiring sstable references | 127.0.0.1 | 158
Merging memtable contents | 127.0.0.1 | 189
Merging data from memtables and 0 sstables | 127.0.0.1 | 235
Read 1 live and 9999 tombstoned cells | 127.0.0.1 | 251102
Enqueuing response to /127.0.0.3 | 127.0.0.1 | 252976
Sending message to /127.0.0.3 | 127.0.0.1 | 253052
Message received from /127.0.0.1 | 127.0.0.3 | 324314
Processing response from /127.0.0.1 | 127.0.0.3 | 324535
Request complete | 127.0.0.3 | 324812
Анти-паттерн #2: очереди и очереде-подобные нагрузки• partition сканируется до тех пор пока LIMIT неудалённых ячеек не будет собран, или • пока не закончились ячейки в partition, или • пока не встретится ячейка вне заданных границ (если указано в where)
Анти-паттерн #2: очереди и очереде-подобные нагрузки—— зная последний удалённый элемент
SELECT enqueued_at, payload
FROM queues
WHERE name = 'queue-1'
AND enqueued_at > 6e1ac030-60b9-11e3-949a-0800200c9a66
LIMIT 1;
Анти-паттерн #2: очереди и очереде-подобные нагрузки
51c220e0-60… 58940a00-60.. 61163b30-60.. 995 x … 6e1ac030-60… ab36fa10-60..
X X X X X <some blob>queue-1
Анти-паттерн #2: очереди и очереде-подобные нагрузкиactivity | source | elapsed
-------------------------------------------+-----------+--------
execute_cql3_query | 127.0.0.3 | 0
Sending message to /127.0.0.1 | 127.0.0.3 | 965
Message received from /127.0.0.3 | 127.0.0.1 | 34
Executing single-partition query on queues | 127.0.0.1 | 339
Acquiring sstable references | 127.0.0.1 | 355
Merging memtable contents | 127.0.0.1 | 461
Partition index lookup over for sstable 3 | 127.0.0.1 | 1122
Merging data from memtables and 1 sstables | 127.0.0.1 | 2268
Read 1 live and 0 tombstoned cells | 127.0.0.1 | 4404
Message received from /127.0.0.1 | 127.0.0.3 | 6109
Enqueuing response to /127.0.0.3 | 127.0.0.1 | 4492
Sending message to /127.0.0.3 | 127.0.0.1 | 4606
Processing response from /127.0.0.1 | 127.0.0.3 | 6608
Request complete | 127.0.0.3 | 6901
Анти-паттерн #2: очереди и очереде-подобные нагрузки• partitioning по дате • хинты для чтения • то же относится к TTL • худший сценарий - death by OOM • https://issues.apache.org/jira/browse/CASSANDRA-6117 (Avoid death-by-tombstone by default) 10k warning, 50k error • http://www.datastax.com/dev/blog/cassandra-anti-patterns-queues-and-queue-like-datasets
Что дальше - вебинары по моделированию• http://youtu.be/px6U2n74q3g - The Data Model is Dead, Long Live the Data Model • http://youtu.be/qphhxujn5Es - Become a Super Modeler • http://youtu.be/T_WRC_GjRd0 - The World's Next Top Data Model • http://youtu.be/UP74jC1kM3w - Understanding How CQL3 Maps to Cassandra's Internal Data Structure
Вопросы?