Моделирование данных с CQL3:
типичные паттерны и анти-паттерны
Aleksey Yeschenko

Apache Cassandra Committer, Engineer @DataStax
@AYeschenko
Кто я такой
• 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
Как хранятся данные + терминология
• Partition ключ и множество ячеек (до 2 миллиардов)
• Хэш partition ключа определяет ноды, к которым принадлежит
partition
• Быстрое чтение по partition ключу
• На диске и в памяти, ячейки всегда отсортированы по имени
• Компаратор для сортировки задаётся пользователем
1
Имя ячейки

2 Миллиарда
...

Имя ячейки

Значение ячейки

Значение ячейки

Timestamp

Timestamp

TTL

TTL

Partition ключ
Идеальная нагрузка
• Запись доминирует
• Перезапись/удаление ячеек отсутствуют или не очень часты
• Чтение по ключу + (имени ячейки или небольшим отрезкам
ячеек в порядке компаратора)
Идеальная нагрузка
• Логи
• Данные с сенсоров
• Финансовые данные (ticks)
• Ads - клики и отображения, аналитика
• Необязательно именно временные ряды (в порядке timestamp);
любые данные, естественно упорядоченные по
последовательному значению
• Нужны последние N значений
Паттерн #1: временной ряд
Паттерн #1: временной ряд
CREATE TABLE temperature (	
weatherstation_id text,	

partition ключ

event_time timestamp,	

кластеринговая колонка

temp int,	
PRIMARY KEY (weatherstation_id, event_time)	
) WITH CLUSTERING ORDER BY (event_time DESC);

составной ключ
обратный порядок
сортировки ячеек

• Каждая станция регулярно собирает показания
• Один partition == одна станция
• Одно показание == одна ячейка (здесь две, технически)
• Потенциально очень широкий partition
Паттерн #1: временной ряд
CREATE TABLE temperature (	
weatherstation_id text,	

partition ключ

event_time timestamp,	

кластеринговая колонка

temp int,	
PRIMARY KEY (weatherstation_id, event_time)	

составной ключ
обратный порядок
сортировки ячеек

) WITH CLUSTERING ORDER BY (event_time DESC);

SVO

weatherstation_id

1386475781 1386475781:temp 1386475780 1386475780:temp 1386475779 1386475779:temp …
*

-6

*

event_time

-6

*

temp

-5

…

* маркеры CQL3 ряда
Паттерн #1: временной ряд
CREATE TABLE temperature (	
weatherstation_id text,	
date text,	

составной partition ключ

event_time timestamp,	
temp int,	
PRIMARY KEY ((weatherstation_id, date), event_time)	
) WITH COMPACT STORAGE AND CLUSTERING ORDER BY (event_time DESC);

• Один partition для пары {weatherstation_id, date}
• Отсутствие оверхеда маркера ряда и имени колонки
Паттерн #1: временной ряд
CREATE TABLE temperature (	
weatherstation_id text,	
date text,	

составной partition ключ

event_time timestamp,	
temp int,	
PRIMARY KEY ((weatherstation_id, date), event_time)	
) WITH COMPACT STORAGE AND CLUSTERING ORDER BY (event_time DESC);

SVO:2013-12-09

1386475781

1386475780

1386475779

…

-6

-6

-5

…

date
weatherstation_id

timestamp

temp
Анти-паттерн #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 индексы
• Для удобства, не для улучшения производительности
• Достаточно ограничены в плане удобства
• Следует избегать, по большей части
Анти-паттерн #1: неуместное использование
встроенных индексов
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',
'aleksey@datastax.com', 'Aleksey Yeschenko');	

!
cqlsh:nope> SELECT * FROM users WHERE email = 'aleksey@datastax.com';	
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 = 'aleksey@datastax.com';	
username | email

| fullname

| last_login	

----------+----------------------+-------------------+------------	
ay | aleksey@datastax.com | Aleksey Yeschenko |

!
(1 rows)

null
Анти-паттерн #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', 'aleksey@datastax.com', 'Aleksey
Yeschenko');	
... INSERT INTO users_by_email_idx (email, username) VALUES ('aleksey@datastax.com', 'ay');	
... APPLY BATCH;	

!
cqlsh:yep> SELECT username FROM users_by_email_idx WHERE email = 'aleksey@datastax.com';	
username	
----------	
ay	

!
cqlsh:yep> SELECT * FROM users WHERE username = 'ay';	
username | email

| fullname

| last_login	

----------+----------------------+-------------------+------------	
ay | aleksey@datastax.com | Aleksey Yeschenko |

null
Анти-паттерн #1: неуместное использование
встроенных индексов
• При средней/низкой кардинальности свой, отдельный набор
проблем
• Главная - большое количество random i/o
• Решается правильной моделью/денормализацией вместо
использования встроенных индексов
Анти-паттерн #1: неуместное использование
встроенных индексов
• Q: Когда вообще следует использовать встроенные индексы?
• A: Для поиска рядов с 1+ кластеринговыми колонками внутри
определённого partition (зная partition ключ)
Анти-паттерн #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: неуместное использование
встроенных индексов
• Всё вышесказанное распространяется на индексы коллекций в
2.1
• Обращайтесь со встроенными индексами как с expert-level
feature
Паттерн #2: денормализация
• Отношения без реляционности
• Без использования foreign key

CREATE TABLE users (!
username varchar,!
firstname varchar,!
lastname varchar,!
email varchar, !
PRIMARY KEY(username)!
);

• Users 1-M Videos
Users
username firstname

lastname email

tcodd
rboyce

Edgar
Raymond

Codd
Boyce

videoid
99051fe9

videoname
My funny cat

b3a76c6b Math

Videos

tcodd@relational.com
rboyce@relational.com

username description
tcodd
My cat plays
the pianodog
Now my
tcodd
plays

tags
cats,piano,lol
dogs,piano,lol

CREATE TABLE videos (!
videoid uuid,!
videoname varchar,!
username varchar,!
description varchar, !
tags varchar,!
upload_date timestamp,!
PRIMARY KEY(videoid)!
);
Паттерн #2: денормализация
• Лукап видео по username
• Запись в две таблицы сразу
• Без встроенных индексов

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’
Паттерн #2: денормализация
• Users 1-M Comments
• Videos 1-M Comments
• Без встроенных индексов
Users
username firstname

lastname email

tcodd
rboyce

Codd
Boyce

videoid
99051fe9

Edgar
Raymond
videoname
My funny cat

b3a76c6b Math

Videos

tcodd@relational.com
rboyce@relational.com

username description
tcodd
My cat plays
the pianodog
Now my
tcodd
plays

tags
cats,piano,lol
dogs,piano,lol

Comments
username
tcodd

videoid
99051fe9

comment
Sweet!

rboyce

b3a76c6b

Boring :(
Паттерн #2: денормализация
• Две таблицы для выборки по видео и по пользователям
• Запись в три таблицы сразу (comments, comments_by_video,
comments_by_user)
• Не жалеть запись
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)!
);
Анти-паттерн #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: очереди и очередеподобные нагрузки

queue-1

51c220e0-60…

58940a00-60..

61163b30-60..

995 x …

X

X

X

X

6e1ac030-60… ab36fa10-60..
X

<some blob>
Анти-паттерн #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: очереди и очередеподобные нагрузки

queue-1

51c220e0-60…

58940a00-60..

61163b30-60..

995 x …

X

X

X

X

6e1ac030-60… ab36fa10-60..
X

<some blob>
Анти-паттерн #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-patternsqueues-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
Вопросы?

Aleksey Yeschenko "Моделирование данных с помощью CQL3". Выступление на Cassandra Conf 2013

  • 1.
    Моделирование данных сCQL3: типичные паттерны и анти-паттерны Aleksey Yeschenko
 Apache Cassandra Committer, Engineer @DataStax @AYeschenko
  • 2.
    Кто я такой •Backend Ruby web-dev (давно и неправда) • Telecom Erlang dev (недавно и правда) • Принуждают использовать Java+Python (cqlsh) в DataStax с июля 2012 • Apache Cassandra committer с ноября 2012 • @AYeschenko
  • 3.
    Когда использовать C* •Необходима Active-Active репликация между несколькими датацентрами • Необходим (около) 100% аптайм • Данные больше не умещаются в PostgreSQL/MySQL/etc. • PostgreSQL/MySQL/etc. больше не справляются с объёмом записи • Модель Cassandra изначально идеально подходит для приложения • Нет необходимости в ad-hoc запросах • Любое другое решение стоит $$$$$ * 1 и более пунктов из списка == true
  • 4.
    Когда не использоватьC* • Во всех остальных случаях
  • 5.
  • 6.
  • 7.
    Как хранятся данные+ терминология • Partition ключ и множество ячеек (до 2 миллиардов) • Хэш partition ключа определяет ноды, к которым принадлежит partition • Быстрое чтение по partition ключу • На диске и в памяти, ячейки всегда отсортированы по имени • Компаратор для сортировки задаётся пользователем 1 Имя ячейки 2 Миллиарда ... Имя ячейки Значение ячейки Значение ячейки Timestamp Timestamp TTL TTL Partition ключ
  • 8.
    Идеальная нагрузка • Записьдоминирует • Перезапись/удаление ячеек отсутствуют или не очень часты • Чтение по ключу + (имени ячейки или небольшим отрезкам ячеек в порядке компаратора)
  • 9.
    Идеальная нагрузка • Логи •Данные с сенсоров • Финансовые данные (ticks) • Ads - клики и отображения, аналитика • Необязательно именно временные ряды (в порядке timestamp); любые данные, естественно упорядоченные по последовательному значению • Нужны последние N значений
  • 10.
  • 11.
    Паттерн #1: временнойряд CREATE TABLE temperature ( weatherstation_id text, partition ключ event_time timestamp, кластеринговая колонка temp int, PRIMARY KEY (weatherstation_id, event_time) ) WITH CLUSTERING ORDER BY (event_time DESC); составной ключ обратный порядок сортировки ячеек • Каждая станция регулярно собирает показания • Один partition == одна станция • Одно показание == одна ячейка (здесь две, технически) • Потенциально очень широкий partition
  • 12.
    Паттерн #1: временнойряд CREATE TABLE temperature ( weatherstation_id text, partition ключ event_time timestamp, кластеринговая колонка temp int, PRIMARY KEY (weatherstation_id, event_time) составной ключ обратный порядок сортировки ячеек ) WITH CLUSTERING ORDER BY (event_time DESC); SVO weatherstation_id 1386475781 1386475781:temp 1386475780 1386475780:temp 1386475779 1386475779:temp … * -6 * event_time -6 * temp -5 … * маркеры CQL3 ряда
  • 13.
    Паттерн #1: временнойряд CREATE TABLE temperature ( weatherstation_id text, date text, составной partition ключ event_time timestamp, temp int, PRIMARY KEY ((weatherstation_id, date), event_time) ) WITH COMPACT STORAGE AND CLUSTERING ORDER BY (event_time DESC); • Один partition для пары {weatherstation_id, date} • Отсутствие оверхеда маркера ряда и имени колонки
  • 14.
    Паттерн #1: временнойряд CREATE TABLE temperature ( weatherstation_id text, date text, составной partition ключ event_time timestamp, temp int, PRIMARY KEY ((weatherstation_id, date), event_time) ) WITH COMPACT STORAGE AND CLUSTERING ORDER BY (event_time DESC); SVO:2013-12-09 1386475781 1386475780 1386475779 … -6 -6 -5 … date weatherstation_id timestamp temp
  • 15.
    Анти-паттерн #1: неуместноеиспользование встроенных индексов • RDBMS опыт с индексацией НЕ переносится в C*
  • 16.
    Анти-паттерн #1: неуместноеиспользование встроенных индексов • RDBMS опыт с индексацией НЕ переносится в C* • Встроенные индексы в C* != RDBMS индексы
  • 17.
    Анти-паттерн #1: неуместноеиспользование встроенных индексов • RDBMS опыт с индексацией НЕ переносится в C* • Встроенные индексы в C* != RDBMS индексы • Встроенные индексы в C* != RDBMS индексы
  • 18.
    Анти-паттерн #1: неуместноеиспользование встроенных индексов • RDBMS опыт с индексацией НЕ переносится в C* • Встроенные индексы в C* != RDBMS индексы • Встроенные индексы в C* != RDBMS индексы • Встроенные индексы в C* != RDBMS индексы
  • 19.
    Анти-паттерн #1: неуместноеиспользование встроенных индексов • RDBMS опыт с индексацией НЕ переносится в C* • Встроенные индексы в C* != RDBMS индексы • Встроенные индексы в C* != RDBMS индексы • Встроенные индексы в C* != RDBMS индексы • Для удобства, не для улучшения производительности • Достаточно ограничены в плане удобства • Следует избегать, по большей части
  • 20.
    Анти-паттерн #1: неуместноеиспользование встроенных индексов 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', 'aleksey@datastax.com', 'Aleksey Yeschenko'); ! cqlsh:nope> SELECT * FROM users WHERE email = 'aleksey@datastax.com'; Bad Request: No indexed columns present in by-columns clause with Equal operator
  • 21.
    Анти-паттерн #1: неуместноеиспользование встроенных индексов
  • 22.
    Анти-паттерн #1: неуместноеиспользование встроенных индексов cqlsh:nope> CREATE INDEX ON users (email); ! cqlsh:nope> SELECT * FROM users WHERE email = 'aleksey@datastax.com'; username | email | fullname | last_login ----------+----------------------+-------------------+------------ ay | aleksey@datastax.com | Aleksey Yeschenko | ! (1 rows) null
  • 23.
    Анти-паттерн #1: неуместноеиспользование встроенных индексов • При высокой и средней кардинальности колонки решается ‘ручным’ вторичным индексом • Отдельная таблица для обратного лукапа
  • 24.
    Анти-паттерн #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) ... );
  • 25.
    Анти-паттерн #1: неуместноеиспользование встроенных индексов cqlsh:yep> BEGIN BATCH ... INSERT INTO users (username, email, fullname) VALUES ('ay', 'aleksey@datastax.com', 'Aleksey Yeschenko'); ... INSERT INTO users_by_email_idx (email, username) VALUES ('aleksey@datastax.com', 'ay'); ... APPLY BATCH; ! cqlsh:yep> SELECT username FROM users_by_email_idx WHERE email = 'aleksey@datastax.com'; username ---------- ay ! cqlsh:yep> SELECT * FROM users WHERE username = 'ay'; username | email | fullname | last_login ----------+----------------------+-------------------+------------ ay | aleksey@datastax.com | Aleksey Yeschenko | null
  • 26.
    Анти-паттерн #1: неуместноеиспользование встроенных индексов • При средней/низкой кардинальности свой, отдельный набор проблем • Главная - большое количество random i/o • Решается правильной моделью/денормализацией вместо использования встроенных индексов
  • 27.
    Анти-паттерн #1: неуместноеиспользование встроенных индексов • Q: Когда вообще следует использовать встроенные индексы? • A: Для поиска рядов с 1+ кластеринговыми колонками внутри определённого partition (зная partition ключ)
  • 28.
    Анти-паттерн #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’;
  • 29.
    Анти-паттерн #1: неуместноеиспользование встроенных индексов • Всё вышесказанное распространяется на индексы коллекций в 2.1 • Обращайтесь со встроенными индексами как с expert-level feature
  • 30.
    Паттерн #2: денормализация •Отношения без реляционности • Без использования foreign key CREATE TABLE users (! username varchar,! firstname varchar,! lastname varchar,! email varchar, ! PRIMARY KEY(username)! ); • Users 1-M Videos Users username firstname lastname email tcodd rboyce Edgar Raymond Codd Boyce videoid 99051fe9 videoname My funny cat b3a76c6b Math Videos tcodd@relational.com rboyce@relational.com username description tcodd My cat plays the pianodog Now my tcodd plays tags cats,piano,lol dogs,piano,lol CREATE TABLE videos (! videoid uuid,! videoname varchar,! username varchar,! description varchar, ! tags varchar,! upload_date timestamp,! PRIMARY KEY(videoid)! );
  • 31.
    Паттерн #2: денормализация •Лукап видео по username • Запись в две таблицы сразу • Без встроенных индексов 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’
  • 32.
    Паттерн #2: денормализация •Users 1-M Comments • Videos 1-M Comments • Без встроенных индексов Users username firstname lastname email tcodd rboyce Codd Boyce videoid 99051fe9 Edgar Raymond videoname My funny cat b3a76c6b Math Videos tcodd@relational.com rboyce@relational.com username description tcodd My cat plays the pianodog Now my tcodd plays tags cats,piano,lol dogs,piano,lol Comments username tcodd videoid 99051fe9 comment Sweet! rboyce b3a76c6b Boring :(
  • 33.
    Паттерн #2: денормализация •Две таблицы для выборки по видео и по пользователям • Запись в три таблицы сразу (comments, comments_by_video, comments_by_user) • Не жалеть запись 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)! );
  • 34.
    Анти-паттерн #2: очередии очередеподобные нагрузки
  • 35.
    Анти-паттерн #2: очередии очередеподобные нагрузки • Cassandra не лучшее решение для очередей • Можно, но требует гимнастики • Лучше использовать что-нибудь вроде Kafka/RabbitMQ • Любой паттерн, где в partition удаляются ячейки и выполняются slice сканы, включающие удалённые ячейки • Пример - нарочно упрощён, ради иллюстрации
  • 36.
    Анти-паттерн #2: очередии очередеподобные нагрузки —— naive путь CREATE TABLE queues ( name text, enqueued_at timeuuid, payload blob, PRIMARY KEY (name, enqueued_at) —— ячейки упорядочены по времени вставки, ASC ) WITH COMPACT STORAGE;
  • 37.
    Анти-паттерн #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;
  • 38.
    Анти-паттерн #2: очередии очередеподобные нагрузки queue-1 51c220e0-60… 58940a00-60.. 61163b30-60.. 995 x … X X X X 6e1ac030-60… ab36fa10-60.. X <some blob>
  • 39.
    Анти-паттерн #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
  • 40.
    Анти-паттерн #2: очередии очередеподобные нагрузки • partition сканируется до тех пор пока LIMIT неудалённых ячеек не будет собран, или • пока не закончились ячейки в partition, или • пока не встретится ячейка вне заданных границ (если указано в where)
  • 41.
    Анти-паттерн #2: очередии очередеподобные нагрузки —— зная последний удалённый элемент SELECT enqueued_at, payload FROM queues WHERE name = 'queue-1' AND enqueued_at > 6e1ac030-60b9-11e3-949a-0800200c9a66 LIMIT 1;
  • 42.
    Анти-паттерн #2: очередии очередеподобные нагрузки queue-1 51c220e0-60… 58940a00-60.. 61163b30-60.. 995 x … X X X X 6e1ac030-60… ab36fa10-60.. X <some blob>
  • 43.
    Анти-паттерн #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
  • 44.
    Анти-паттерн #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-patternsqueues-and-queue-like-datasets
  • 45.
    Что дальше -вебинары по моделированию • 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
  • 46.