3. 3
Постановка задачи
• Множество источников сообщений
• Многоступенчатая обработка в реальном
времени
• Длительное хранение
• Выдача данных по запросу
• Подписка сторонних клиентов на события
• Высокая надёжность
5. 5
Зачем искать альтернативы?
• Очереди удаляют сообщения после
обработки
• Гибкие правила обработки – сложно
• Повторная обработка => повторное
добавление в очередь
• Требуется очередь + хранение – иметь
две системы накладно
• Лишние операции копирования
6. 6
Несколько слов про Кассандру
RowKey: { SuperColumnKey: { ColumKey: Value } }
Децентрализованная база (модель BigTable)
с настраиваемой целостностью для чтения и
записи.
7. 7
Общая идея очереди в Кассандре
• Wide rows
• Обработка в один или несколько потоков
• Помечаем обрабатываемые сообщения
• Результат в той же или отдельной таблице
8. 8
Очереди в Кассандре – антипаттерн
• Cassandra создана для хранения
• Выборка с условием не предусмотрена
• Удаление – сложная задача для
распределённого хранилища
• Нет системы подписки на события
(временно)
9. 9
Решение проблемы с удалением
Использовать отложенное удаление (TTL)
INSERT INTO tab (rkey, clkey, val)
VALUES ('rawhash', 'cluster', 42)
USING TTL 86400;
UPDATE tab USING TTL 86400
SET val=42
WHERE rkey='some' AND clkey='any';
10. 10
Полезная вещь: транзакции
INSERT INTO rawdata (row_date, moment, handled, message)
VALUES ('2016-05-15', '2016-05-15 11:23:00', 0, 'message')
IF NOT EXISTS;
UPDATE rawdata SET handled=2
WHERE row_date='2016-05-15' AND moment='2016-05-15 11:23:00'
IF handled=1;
[applied]
true
[applied] | raw_date | moment | handled | message
false | 2016-05-15 | '2016-05-15 11:23:00' | 0 | message
11. 11
Общая идея очереди в Кассандре
• Wide rows
• Обработка в один или несколько потоков
• Помечаем обрабатываемые сообщения
• Результат в той же или отдельной таблице
12. 12
Наполнение с переменной нагрузкой
CREATE TABLE IF NOT EXISTS rawdata (
row_date date, -- дата, определяющая строку, yyyy-mm-dd
moment timeUUID, -- момент поступления в систему, идентификатор
handled tinyint, -- 0 - unhandled, 1 - handling, 2 - handled, 3... – error
message blob, -- данные пакета
PRIMARY KEY (row_date, moment)
) WITH CLUSTERING ORDER BY (moment ASC);
13. 13
Наполнение с переменной нагрузкой
CREATE TABLE IF NOT EXISTS rawdata (
row_date date, -- дата, определяющая строку, yyyy-mm-dd
row_sec int, -- начальная секунда строки, [0..86400)
moment timeUUID, -- момент поступления в систему, идентификатор
handled tinyint, -- 0 - unhandled, 1 - handling, 2 - handled, 3... - error
message blob, -- данные пакета
PRIMARY KEY ((row_date, row_sec), moment)
) WITH CLUSTERING ORDER BY (moment ASC);
CREATE TABLE IF NOT EXISTS rawdata_meta_rows (
row_date date, -- дата строки
row_sec int, -- начальная секунда строки, [0..86400)
PRIMARY KEY ((row_date), row_sec)
) WITH CLUSTERING ORDER BY (row_sec DESC);
CREATE TABLE IF NOT EXISTS rawdata_meta (
mkey text, -- варианты: csec (current second), nsec (next second)
msubkey text, -- ключ ячейки в строке (csec: yyyy-mm-dd)
mval text, -- значение
PRIMARY KEY ((mkey), msubkey)
);
хранит все ключи
партиций (строк)
из rawdata
кэш значений переменных
14. 14
Наполнение с переменной нагрузкой
curS := ТекущаяСекундаДня( ) или 0
nextS := СекундаПерехода( )
ЦИКЛ добавления сообщений
msg := СформироватьСообщение( )
ЕСЛИ сработал таймер ТОГДА
nextS := СекундаПерехода( )
ЕСЛИ ТекущееВремя( ) >= nextS ТОГДА
curS := nextS
ЗаписатьСообщение(msg, curS)
15. 15
Наполнение с переменной нагрузкой
ЦИКЛ проверки
curBlock := НаполняемыйБлок( )
cnt := КоличествоЗаписей(curBlock)
ЕСЛИ cnt > N ТОГДА
nextS := ТекущееВремя( ) + K
ЗадатьСекундуПерехода(nextS)
УснутьНа(Z) // Z <= K
16. 16
Наполнение с переменной нагрузкой
Если не был задан переход nextS
Если был задан переход и посчитали новый
INSERT INTO rawdata_meta (mkey, msubkey, mval)
VALUES ('nsec', '2016-05-15', nextS)
IF NOT EXISTS USING TTL 86400;
UPDATE rawdata_meta USING TTL 86400
SET mval=nextS
WHERE mkey='nsec' AND msubkey='2016-05-15‘
IF mval=oldNextS;
19. 19
Однопоточная обработка: выборка
1. Определение ключа партиции и кластера
по rawdata_meta_rows
2. Выборка из конкретной строки с условием
и сортировкой
3. Пагинация (LIMIT, OFFSET)
CREATE TABLE IF NOT EXISTS rawdata (
row_date date, -- дата, определяющая строку, yyyy-mm-dd
row_sec int, -- начальная секунда строки, [0..86400)
moment timeUUID, -- момент поступления в систему, идентификатор
handled tinyint, -- 0 - unhandled, 1 - handling, 2 - handled, 3... - error
message blob, -- данные пакета
PRIMARY KEY ((row_date, row_sec), moment)
) WITH CLUSTERING ORDER BY (moment ASC);
20. 20
Неочевидные проблемы выборки
Сложности прямых запросов:
• Сортировка доступна только по ключу
• Нет понятия OFFSET
• Нельзя просто взять и применить условия
SELECT * FROM rawdata WHERE row_date='2016-05-10‘
AND row_sec=0 AND handled=0;
No supported secondary index found for the non primary key
columns restrictions
21. 21
Неочевидные проблемы выборки
Можно применить индексы, но индекс по
флагу не эффективен
Each value in the index becomes a single row in the
index, resulting in a huge row for all the false values,
for example. Indexing a multitude of indexed columns
having foo = true and foo = false is not useful.
22. 22
Неочевидные проблемы выборки
Есть в Кассандре materialized views, но их
функционал пока слишком слаб: нет условий
выборки, сортировки и т.д.
CREATE MATERIALIZED VIEW rawdata_unhandled AS
SELECT row_date, row_sec, handled, moment, message
FROM rawdata
WHERE row_date IS NOT NULL AND row_sec IS NOT NULL
AND moment IS NOT NULL AND handled IS NOT NULL
PRIMARY KEY ((row_date, row_sec), handled, moment);
23. 23
Решение проблем с выборкой
Внешнее приложение должно взять полный
контроль на себя и точно знать, что запрашивать
SELECT row_date, row_sec, moment, handled FROM rawdata
WHERE row_date='2016-05-15' AND row_sec=0 LIMIT 100;
row_date | row_sec | moment | handled
...
2016-05-15 | 0 | d6686343-1804-11e6-bebe-87c17418206b | 0
SELECT row_date, row_sec, moment, handled FROM rawdata
WHERE row_date='2016-05-15' AND row_sec=0
AND moment > d6686343-1804-11e6-bebe-87c17418206b
LIMIT 100;
24. 24
Многопоточная обработка
Hadoop, Apache Spark:
• Отлично подходят для Big Data Analytics
• Вся обработка через запуск обработчика на
одно или группу сообщений
• Возврат в очередь, смена
последовательности, отложенная обработка –
затруднены
• Много копирований в процессе разделения
и передачи сообщений
25. 25
Многопоточная обработка
• Простой вариант – сколько угодно потоков,
читаем несколько записей вперёд, ставим
флаг обработки, обрабатываем, ставим флаг
готовности
• Сложный вариант – потоки договариваются
между собой, кто какие записи берёт;
разбивка по миллисекундам
• Сложный вариант улучшенный – очередь
изначально разбивается на уровне ключа,
потоки оповещают друг друга о вхождении в
работу
26. 26
Оповещение о событиях
• Триггеры
• CDC (Change Data Capture)
• Внешний источник оповещений
CREATE TRIGGER IF NOT EXISTS trigger_name
ON table_name
USING 'java_class';
27. 27
Когда Кассандра не подходит
• Низкая нагрузка и малое количество узлов
системы
• Сохранение сообщений не требуется
• Необходима гибкая подписка и система
оповещения
• Привычность решения важнее
производительности и отказоустойчивости
28. 28
Когда решение имеет смысл
• Очень высокая нагрузка
• Децентрализованная распределённая
среда
• Сообщения сохраняются после обработки
• Необходима повторная обработка
сообщений
• Сложная зависимая обработка (например,
цепочки сообщений)