cassandra design patterns
TRANSCRIPT
Cassandra. Patterns.
Для кого докладДля разработчиков, которые уже знают что такое Cassandra, для чего она нужна и попробовали ее использовать.
Cassandra. Internals.Cassandra имеет внутри структуру хранения, похожую на lsm tree + wal.Верхний уровень - memtable (sorted by row key, btree-like).Нижний уровень - disk (sstable + bloom filter).
Cassandra. Java point of view.
SortedMap<RowKey, SortedMap<ColumnKey, ColumnValue>>
Cassandra. Internals. Write path.
Cassandra. Internals. Read path.
Cassandra. Сильные стороны.Почти линейная масштабируемость
записи и чтенияНе нужно backup, все
восстанавливается на летуЕсть поддержка нескольких ДЦ
Настраиваемая модель (в терминах CAP)Стабильный продукт с первоклассным community
Нет ACID транзакцийЧастое удаление данных это проблемаНет хороших secondary index** Индексы сейчас улучшаются:
https://github.com/xedin/sasi и CASSANDRA-10661)
Cassandra. Слабые стороны.
ЛайкиCREATE TABLE LikedByObject( user_id bigint, object_id bigint, created bigint, PRIMARY KEY (object_id, user_id));
CREATE TABLE LikedByUser( user_id bigint, object_id bigint, created bigint, type_id int, PRIMARY KEY ((user_id, type_id), object_id));
ЛайкиВыбрать всех кто лайкал объектselect * from LikedByObject where object_id = 42
Выбрать все что лайкал пользовательselect * from LikedByUser where user_id = 42 and type_id in (1, 2, 3)
ЛайкиНет secondary index, используем materialized view. Почти всегда копирование данных более предпочтительно, так как самое затратное при чтении данных с диска это seek. Прочитать чуть больше данных, но из одного место быстрее, чем из двух.
Уведомления
УведомленияCREATE TABLE NotificationByUser ( user_id bigint, shard_part text, id timeuuid, type_id int, ..... PRIMARY KEY ((user_id, shard_part), id)) WITH CLUSTERING ORDER BY (id DESC);
CREATE TABLE NotificationByUserAndType ( ..... PRIMARY KEY ((user_id, type_id, shard_part), id)) WITH CLUSTERING ORDER BY (id DESC);
CREATE TABLE NotificationSeen ( user_id bigint, id timeuuid, value boolean, PRIMARY KEY (user_id, id)) WITH CLUSTERING ORDER BY (id DESC);
УведомленияУведомления всегда отсортированы по времени события, к которому они относятся. Используем clustering order, чтобы данные физически хранились в нужном порядке.
Уведомления могут быть непросмотренные и просмотренные. Выделим мутабельную часть данных в отдельную cf. Это упрощает процесс обновления данных и API.
УведомленияCREATE TABLE NotificationByUser ( user_id bigint, shard_part text, id timeuuid, type_id int, ..... PRIMARY KEY ((user_id, shard_part), id)) WITH CLUSTERING ORDER BY (id DESC);
У одного пользователя может быть очень много уведомлений. Таким образом у нас могут появится wide rows. Их нужно избегать (OOM, additional seeks, вот это вот все). Добавим в partition key дату.
Partition keysКак правильно выбрать partition key?Распределение колонок внутри строки должно быть равномерным в идеале. В одной строке не должно быть слишком много данных (wide rows).
Варианты:Сам ключКлюч + timebased частьКлюч + partition (n of partitions fixed or any)
Partition keysЛайки. У одного объекта очень редко бывает слишком много лайков. Object id хороший partition key.
Уведомления. У одного пользователя может быть очень много уведомлений, так как они накапливаются со временем. User id плохой partition key. Нужно добавить что-то еще. User id + date.Можно также сделать предположение, что уведомления более-менее распределены по дням равномерно, поэтому date подходит.
Partition keysЛента постов по тегу.CREATE TABLE TagPosts (
tag text,partition int,post_id bigint,PRIMARY KEY((tag, partition), post_id)
) WITH CLUSTERING ORDER BY (post_id DESC);
Просто tag взять нельзя, потому что распределение имеет выбросы (тренды) и длинный хвост. Date плохая идея, так как хвост и тренды не зависят от даты.
Partition keysНеестественное разбиение данных на любое количество partitions сложнее. При вставке нужно вычислять partition.
CREATE TABLE TagPostsPartitions (tag text,partition int,post_count counter,PRIMARY KEY (tag, partition)
) WITH CLUSTERING ORDER BY (partition DESC);
Partition keysЕсли вы уверены, что кол-во данных по каждому ключу примерно одинаково, то вычисление partition может быть простым:
key % n partitions
Partition keysВопрос: можно ли делать skinny partitions*?*skinny partition - в одной партиции одна строка
Ответ: да, если паттерн доступа random и нет range queries.
CREATE TABLE BlackList ( login text, created bigint, PRIMARY KEY (login));
ВставкаИспользуйте batches, только если вам
действительно нужна атомарностьДля производительности используйте
асинхронные операции (в драйвере) с одиночными запросами*
*http://lostechies.com/ryansvihla/2014/08/28/cassandra-batch-loading-without-the-batch-keyword/
УдалениеCREATE TABLE Queues ( queue_id bigint, enqueued timeuuid, PRIMARY KEY (queue_id, enqueued));
Классический anti-pattern!
УдалениеКак и в любом log-structure engine, данные физически сразу не удаляются. Удаленные данные будут помечены, как tombstone, и через некоторое время (настраивается) будут физически удалены при очередном compaction.
Операция DELETE по ключу ввполняется за O(1).Операция выборки вида:select * from Queues where queue_id = 42 order by enqueued limit 1может выполняться за O(n).
УдалениеДумайте про удаление заранееСтарайтесь удалять партиции целиком
Избегайте RMWДелайте операции идемпотентными и
переписывайте данныеИспользуйте counter columnsИспользуйте транзакции осторожно, они
замедляют производительность и все равно не ACID
Вопросыhttps://facebook.com/denis.gabaydulin (messanger)