Получение всех строк для большой группы в CQL, когда идентификатор группы может измениться
В качестве примера, скажем, я хочу хранить книги и в каких библиотеках они находятся. Я мог бы создать такую таблицу:
create table book (
id uuid,
created timestamp,
title text,
library_id uuid,
primary key (id)
);
Если бы я хотел получить список всех книг в данной библиотеке, я мог бы сделать материализованное представление следующим образом:
create materialized view book_per_library as
select *
from book
where library_id is not null
and id is not null
primary key (library_id, id);
Я думаю, что есть некоторые проблемы, которые возникают с этой схемой, и я не уверен, как их решить.
Проблема 1. Слишком много книг для одного узла вместе с медленными запросами
Чтобы сделать этот пример эквивалентным данным, с которыми я на самом деле работаю, я мог бы иметь миллиарды книг в одной библиотеке. Если мой ключ раздела - это library_id (либо в таблице, либо в материализованном представлении), я бы максимально использовал то, что может быть сохранено на узле, который имеет этот ключ раздела. Одним из возможных решений, которое я нашел, могло быть разделение данных на основе созданной временной метки, например:
create table book (
id uuid,
created timestamp,
title text,
library_id uuid,
date text,
primary key (id, library_id)
);
create materialized view book_per_library as
select *
from book
where library_id is not null
and date is not null
and id is not null
primary key ((library_id, date), id);
Это основано на чтениях, таких как https://academy.datastax.com/demos/getting-started-time-series-data-modeling где столбец даты - что-то вроде "2013-04-03". Тогда я должен был бы выполнить отдельный запрос в течение каждого дня. Для моих реальных данных мне, возможно, даже понадобится разделить их на каждый час, чтобы один узел мог хранить все это. Мои данные могут добавляться очень большими пакетами (например, миллионами) за короткий промежуток времени, например час. Или мои данные могут быть добавлены по капле (например, один или несколько за один раз) каждый раз и время. Таким образом, это не постоянный поток данных.
В любом случае, не будет ли запрос здесь медленным процессом? Потенциально мне может потребоваться выполнить тысячи запросов, чтобы охватить многие годы, чтобы получить список книг в этой единственной библиотеке. Кроме того, как мне узнать, какие даты на самом деле есть данные?
Я предполагаю, что в основном пытаюсь найти способ избежать использования чего-то вроде spark для чтения всей таблицы книг, потому что я не хочу читать строки для всех библиотек, только для той, которая мне нужна. Я также пытаюсь найти способ избежать необходимости выполнять запросы для дат, у которых нет данных, потому что это похоже на потерянное время. Одна из идей состоит в том, чтобы найти способ отслеживать, в каких датах есть данные для каждой библиотеки, чтобы я знал, какие ключи разделов можно запрашивать. Может быть, есть таблица счетчиков, где я храню количество книг для каждого значения даты, например "2013-04-03". Это будет двухэтапный процесс, чтобы прочитать все эти значения для данной библиотеки, а затем выполнить основной запрос только для дат с числом> 0. Это жизнеспособное решение? Есть ли лучшие варианты?
Проблема 2. Я должен быть в состоянии переместить книги в другую библиотеку, т.е. я должен быть в состоянии изменить library_id
Как требование, я должен иметь возможность перемещать книги в разные библиотеки. Насколько я понимаю, это означает, что я не могу использовать library_id в качестве столбца ключа раздела или столбца кластеризации. Если бы я это сделал, то "перемещение" книги в другую библиотеку потребовало бы от меня удаления ее из текущей библиотеки, а затем добавления новой записи в новую библиотеку. Это точно? Есть ли лучшие способы связать строки в таблице с группой, но также предоставляют возможность изменить эту группировку?
Общий вопрос
Какова лучшая практика для такой ситуации, когда я хочу получить все строки таблицы, связанные с каким-то "идентификатором группы", например, library_id, и я мог бы потенциально иметь миллиарды из них для каждой группы, и мне нужно иметь возможность изменить это ассоциация?
Я планирую использовать Кассандру 3.x.
2 ответа
Я боюсь, что Cassandra не самый лучший инструмент для таких типов нагрузок. Действительно, Кассандра великолепна, когда получает несколько строк из раздела, даже в огромном наборе данных, но получение миллиардов строк вместе - это действительно анти-паттерн.
Но ничего не потеряно
Ваша начальная модель данных кажется мне неправильной. Вы смоделировали "библиотечную коллекцию", а затем, используя материализованное представление, реализовали справочную таблицу. Я не знаю причины этого, но если вы сами реализуете "материализованную" таблицу, вы получите то же самое:
create table books_by_library_id (
library_id int,
book_id uuid,
book_created timestamp,
book_title text,
book_date text,
primary key (library_id, book_title)
);
Теперь ваша проблема 1 реальна. Вы не должны иметь больше, чем, скажем, 10 000 или 20 000 записей на каждом разделе, потому что запросы к этому разделу создают нагрузку только на один узел. В настоящее время ваш раздел состоит из library_id
только поле Вам нужно найти что-то еще, что разбивает данные на несколько разделов, а затем вернуть ваши данные с помощью нескольких запросов, собирая данные в ваше приложение. Это позволило бы вашему кластеру легко пережить выборку из миллиардов строк, потому что вы выполняете несколько запросов, каждый из которых обрабатывается отдельным узлом. По сути, вы "вручную" организуете кластер, чтобы получить то, что вы хотите.
Что можно сделать, чтобы разбить данные на несколько разделов? Типичный подход заключается в "группировании" ваших данных. В вашем примере вы можете легко "разделить" каждую библиотеку на (как минимум) 26 сегментов, по одному на алфавитную букву от A до Z:
create table books_by_library_id_and_initials (
library_id int,
book_title_initials text,
book_id uuid,
book_created timestamp,
book_title text,
book_date text,
primary key ((library_id, book_title_initials), book_title)
);
Чтобы получить все ваши книги, принадлежащие library_id=2
вам нужно оформить 26 запросов:
SELECT * FROM books_by_library_id_and_initials WHERE library_id=2 AND book_title_initials='a';
SELECT * FROM books_by_library_id_and_initials WHERE library_id=2 AND book_title_initials='b';
...
SELECT * FROM books_by_library_id_and_initials WHERE library_id=2 AND book_title_initials='z';
Более того, это все равно позволит вам получить книгу напрямую с:
SELECT * FROM books_by_library_id_and_initials
WHERE library_id=2
AND book_title_initials='c'
AND book_title = 'Cassandra from Zero to Hero';
Если вы боитесь, что разделить 5 миллиардов строк на 26 сегментов недостаточно, вы можете добавить еще одну букву к ключу раздела, увеличивая до 676 сегментов (а затем выдавая 676 запросов), или еще две буквы, увеличивая до 17576 сегментов (а затем выдав 17576 запросов вздох!).
С этими большими числами имеет смысл отслеживать количество книг в конкретном ведре с выделенной таблицей. Если в этой таблице указано, что в корзине нет книг, запрос не выполняется. Вы запрашиваете раздел в противном случае.
CREATE TABLE my_counters (
library_id int,
book_title_initials text,
books_count counter,
PRIMARY KEY (library_id, book_title_initials)
);
SELECT counter FROM my_counters
WHERE library_id = 2
AND book_title_initials='cas';
Я не думаю, что вы можете пойти дальше, как это.
Что касается вашей Задачи 2, вместо этого вам нужно удалить книгу из одной библиотеки и воссоздать ее в другой. От этого не ускользает, потому что вы не можете обновить значения первичного ключа.
НТН.
То, что вы описываете, выглядит как база данных индексов поверх ваших библиотечных таблиц. Я думаю, что вы можете использовать что-то вроде ElasticSearch. Особенно, если вы хотите иметь возможность изменять правила связывания строк "на лету" - вам следует изменить структуру индекса, но не данные библиотеки.
Кроме того, DataStax Enterprise имеет поддержку Apache Solr. Но это может быть излишним в вашей ситуации, потому что вам не нужен полнотекстовый поиск и прочее.