Данные моделирования лучших практик для баз данных Cassandra
Я новичок в Cassandra и ищу лучший метод моделирования данных, который имеет следующую общую структуру:
Данные основаны на "пользователях" (для каждого клиента), каждый из которых предоставляет большой файл данных, содержащий около 500K-2M записей (периодически обновляется несколько раз в день - иногда полное обновление, а иногда только дельты)
Каждый файл данных имеет определенные обязательные поля данных (~20 обязательных), но может добавлять дополнительные столбцы по своему усмотрению (до ~100).
Дополнительные поля данных НЕ обязательно должны быть одинаковыми для разных пользователей (имена полей или типы этих полей)
Пример (формат csv:)
user_id_1.csv
| column1 (unique key per user_id) | column2 | column3 | ... | column10 | additionalColumn1 | ...additionalColumn_n |
|-----------------------------------|-----------|----------|---------|------------|---------------------|------------------------|
| user_id_1_key_1 | value | value | value | value | ... | value |
| user_id_1_key_2 | .... | .... | .... | .... | ... | ... |
| .... | ... | ... | ... | ... | ... | ... |
| user_id_1_key_2Million | .... | .... | .... | .... | ... | ... |
user_id_XXX.csv (notice that the first 10 columns are identical to the other users but the additional columns are different - both the names and their types)
| column1 (unique key per user_id) | column2 | column3 | ... | column10 | additionalColumn1 (different types than user_id_1 and others) | ...additional_column_x |
|-----------------------------------------------------------|-----------|----------|---------|------------|-----------------------------------------------------------------|-------------------------|
| user_id_XXX_key_1 | value | value | value | value | ... | value |
| user_id_XXX_key_2 | .... | .... | .... | .... | ... | ... |
| .... | ... | ... | ... | ... | ... | ... |
| user_id_XXX_key_500_thousand (less rows than other user) | .... | .... | .... | .... | ... | ... |
Несколько вариантов я рассмотрел:
Опция 1:
- Создать "глобальное" пространство ключей
- Создайте большую таблицу "data", содержащую все
Объедините столбец user_id со всеми остальными столбцами большой таблицы (включая необязательные столбцы). Первичный ключ становится user_id + "column_1" (column_1 уникален для user_id)
Keyspace +--------------------------------------------------------------------------+ | | | | | Data_Table | | + +--------+-------+--------------------------+-----+ | | | | | | | | | | | +-------------------------------------------------+ | | | | | | | | | | many rows | +-------------------------------------------------+ | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | Many columns | | | | | | | +------------------------> | | | | | | | | | | | | | +-------------------------------------------------+ | | v +-------------------------------------------------+ | | | +--------------------------------------------------------------------------+
Несколько вещей, которые я сразу заметил:
- User_id повторяется столько раз, сколько записей на пользователя
- Строки очень редки для дополнительных столбцов (пустые нулевые значения), так как пользователи не обязательно разделяют их
- Количество пользователей относительно невелико, поэтому количество дополнительных столбцов невелико (максимум 10 тысяч столбцов).
- Я мог бы сжать данные дополнительных столбцов для каждого пользователя в один столбец, называемый "метаданными", и поделиться им для всех пользователей.
Вариант 2:
Создать пространство ключей для идентификатора пользователя
Создать таблицу "данные" для каждого пространства ключей
+-----------------------------------------------------------------------------------+
| column_1 | column_2 | ... | column_n | additional_column_1 | additional_column_n |
+-----------------------------------------------------------------------------------+
keyspace_user1 keyspace_user2 keyspace_user_n
+----------------+ +---------------+ +---------------+
| | | | | |
| | | | | |
| +-+-+--+-+ | | +-+--+--+ | | +--+--+---+ |
| | | | | | | | | | | | | many keyspaces | | | | | |
| | | | | | | | | | | | | +-------------> | | | | | |
| | | | | | | | | | | | | | | | | | |
| | | | | | | | | | | | | | | | | | |
| +--------+ | | +-------+ | | +---------+ |
+----------------+ +---------------+ +---------------+
заметки:
- Много пространств клавиш (пространство клавиш на пользователя)
- Избегает добавления значения "user_id" для каждой строки (я могу использовать имя пространства ключей в качестве идентификатора пользователя)
- Очень мало таблиц на пространство ключей (в этом примере только 1 таблица на пространство ключей)
Вариант 3:
1) Создать глобальное пространство ключей. 2) Создать таблицу для user_id (обязательные столбцы, а также их дополнительные столбцы для каждой таблицы).
+---------------------------------------------------------------+
| Keyspace |
| |
| user_1 user_2 user_n |
| +--+---+--+ +--+--+--+ +--+--+--+ |
| | | | | | | | | | | | | |
| | | | | | | | | | | | | |
| | | | | | | | | | | | | |
| | | | | | | | | | | | | |
| | | | | | | | | | | | | |
| +--+---+--+ +--+--+--+ +--+--+--+ |
| |
| |
+---------------------------------------------------------------+
Заметки
- Глобальное пространство клавиш
- Таблица для user_id ("много" таблиц)
- Позволяет избежать дублирования идентификатора пользователя в строке
Вариант 4: (имеет ли это смысл?)
Создайте несколько пространств клавиш (например, количество клавиш "x"), каждое из которых содержит диапазон таблиц (таблица на пользователя)
keyspace_1 keyspace_x
+---------------------------------------------------------------+ +---------------------------------------------------------------+
| | | |
| | | |
| user_1 user_2 user_n/x | | user_n-x user_n-x+1 user_n |
| +--+---+--+ +--+--+--+ +--+--+--+ | | +--+------+ +--+--+--+ +--+--+--+ |
| | | | | | | | | | | | | | "X" keyspaces | | | | | | | | | | | | | |
| | | | | | | | | | | | | | +---------------------> | | | | | | | | | | | | | |
| | | | | | | | | | | | | | | | | | | | | | | | | | | |
| | | | | | | | | | | | | | | | | | | | | | | | | | | |
| | | | | | | | | | | | | | | | | | | | | | | | | | | |
| +--+---+--+ +--+--+--+ +--+--+--+ | | +--+---+--+ +--+--+--+ +--+--+--+ |
| | | |
| | | |
+---------------------------------------------------------------+ +---------------------------------------------------------------+
Заметки:
- Несколько клавишных пространств
- Несколько таблиц на пользователя
- Требуется "поиск", чтобы выяснить, какое пространство клавиш содержит требуемую таблицу
Вариант 5:
Разделить данные на несколько таблиц и несколько пространств клавиш
Примечания: 1. В некоторых случаях требуется "объединение" информации из нескольких таблиц. 2. Кажется, что это более сложно
Общие замечания по всем сценариям:
- Количество записей меньше, чем читает
- Многие миллионы чтений в день
- Трафик колеблется в зависимости от user_id - у некоторых user_ids много трафика, а у некоторых user_ids гораздо меньше трафика. Нужно было бы настроить по этой метрике
- Некоторые user_ids обновляются (записываются) чаще других
- У нас есть несколько центров обработки данных в разных регионах, и мы должны синхронизировать
- У каждого первичного ключа длинный хвост (к некоторым ключам обращаются много раз, в то время как к другим ключам редко обращаются)
2 ответа
Этот тип задачи интеграции обычно решается с помощью модели данных EAV (Entity Attribute Value) в реляционных системах (как демонстрирует Ашрафаул). Ключевым фактором при рассмотрении модели EAV является неограниченное количество столбцов. Модель данных EAV, конечно, может быть имитирована в системе CQL, такой как Cassandra или ScyllaDB. Модель EAV прекрасно подходит для записи, но создает проблемы при чтении. Вы не очень подробно изложили свои соображения по поводу чтения. Вам нужны все столбцы назад или вам нужны конкретные столбцы обратно для каждого пользователя?
файлы
Сказав это, есть некоторые дополнительные соображения, присущие Cassandra и ScyllaDB, которые могут указать вам на унифицированную модель EAV по сравнению с некоторыми конструкциями, которые вы описали в своем вопросе. И Cassandra, и ScyllaDB размещают пространства ключей и базы данных в виде файлов на диске. Количество файлов - это, в основном, произведение количества пространств клавиш на количество таблиц. Таким образом, чем больше у вас ключей, таблиц или их комбинаций, тем больше файлов будет на диске. Это может быть проблема с файловыми дескрипторами и другие проблемы с жонглированием файла os. Из-за длинного хвоста доступа, о котором вы упомянули, может быть так, что каждый файл открыт постоянно. Это не очень желательно, особенно при запуске с холодной загрузки.
[отредактируйте для ясности] При прочих равных условиях одно пространство ключей / таблица всегда будет производить меньше файлов, чем множество областей ключей / таблиц. Это не имеет никакого отношения к количеству хранимых данных или стратегии сжатия.
Широкие ряды
Но вернемся к модели данных. Модель Ашрафула имеет первичный ключ (идентификатор пользователя) и другой ключ кластеризации (ключ-> столбец1). Из-за количества "записей" в каждом пользовательском файле (500K-2M) и предположения, что каждая запись представляет собой строку, состоящую из 60 столбцов avg, в основном вы создаете строки столбцов 500k-2m * 60 avg для каждого ключа раздела. создавая очень большие перегородки. Кассандре и Сцилле вообще не нравятся очень большие перегородки. Могут ли они обрабатывать большие перегородки, конечно. На практике большие разделы влияют на производительность, да.
Обновления или управление версиями
Вы упоминаете обновления. Базовая модель EAV будет представлять только самое последнее обновление. Там нет версии. Что вы можете сделать, так это добавить время в качестве ключа кластеризации, чтобы обеспечить сохранение исторических значений ваших столбцов с течением времени.
Читает
Если вы хотите вернуть все столбцы, вы можете просто сериализовать все в объект json и поместить его в один столбец. Но я думаю, что это не то, что вы хотите. В модели первичного ключа (ключа раздела) системы, основанной на ключе / значении, такой как Cassandra и Scylla, вам нужно знать все компоненты ключа, чтобы вернуть ваши данные. Если вы положите column1
уникальный идентификатор строки, в ваш первичный ключ, вам нужно будет знать его заранее, также как и другие имена столбцов, если они также включены в первичный ключ.
Разделы и составные ключи разделов
Количество разделов диктует параллельность вашего кластера. Общее количество разделов, или количество разделов в общем корпусе, влияет на использование оборудования кластера. Больше разделов = лучший параллелизм и более эффективное использование ресурсов.
Что я мог бы сделать здесь, это изменить PRIMARY KEY
включать column1
, Тогда я бы использовал column
в качестве ключа кластеризации (который не только определяет уникальность внутри раздела, но и порядок сортировки - так что учитывайте это в соглашениях об именах столбцов).
В следующем определении таблицы вам необходимо указать userid
а также column1
как равенство в вашем WHERE
пункт.
CREATE TABLE data (
userid bigint,
column1 text,
column text,
value text,
PRIMARY KEY ( (userid, column1), column )
);
Я бы тоже имел отдельную таблицу, может быть columns_per_user
, который записывает все столбцы для каждого userid
, Что-то вроде
CREATE TABLE columns_per_user (
userid bigint,
max_columns int,
column_names text
PRIMARY KEY ( userid )
);
куда max_columns
это общее количество столбцов для этого пользователя и column_names
являются фактическими именами столбцов. Вы также можете иметь столбец для общего количества записей на пользователя, что-то вроде user_entries int
который будет в основном количеством строк в каждом пользовательском CSV-файле.
Попробуйте следующую схему:
CREATE TABLE data (
userid bigint,
key text,
column text,
value text,
PRIMARY KEY (userid, key)
);
Вот
userid -> userid
key -> column1
column -> column name from column2
value -> column value
Пример вставки для данных ниже:
| column1 (unique key per user_id) | column2 | column3 |
|-----------------------------------|---------------|-----------------|
| key_1 | value12 | value13 |
| key_2 | value22 | value23 |
Вставить заявление:
INSERT INTO data (userid , key , column , value ) VALUES ( 1, 'key_1', 'column2', 'value12');
INSERT INTO data (userid , key , column , value ) VALUES ( 1, 'key_1', 'column3', 'value13');
INSERT INTO data (userid , key , column , value ) VALUES ( 1, 'key_2', 'column2', 'value22');
INSERT INTO data (userid , key , column , value ) VALUES ( 1, 'key_2', 'column3', 'value23');