Данные передового моделирования для баз данных Cassandra

Я новичок в Cassandra и ищу лучший метод моделирования данных, которые имеют следующую общую структуру:

Данные основаны на «пользователях» (для каждого клиента), каждый из них предоставляет большой файл данных, содержащий около 500–2 млн записей (периодически обновляется несколько раз в день - иногда полное обновление, а иногда только дельты).

Каждый файл данных имеет определенные обязательные поля данных (~ 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:

  1. Создайте «глобальное» пространство ключей
  2. Создайте большую таблицу "данных", содержащую все
  3. Объедините столбец user_id со всеми другими столбцами большой таблицы (включая необязательные столбцы). Первичный ключ становится user_id + "column_1" (column_1 уникален для user_id)

                                     Keyspace
    +--------------------------------------------------------------------------+
    |                                                                          |
    |                                                                          |
    |                                      Data_Table                          |
    |                +  +--------+-------+--------------------------+-----+    |
    |                |  |        |       |                          |     |    |
    |                |  +-------------------------------------------------+    |
    |                |  |        |       |                          |     |    |
    |    many rows   |  +-------------------------------------------------+    |
    |                |  |        |       |                          |     |    |
    |                |  |        |       |                          |     |    |
    |                |  |        |       |                          |     |    |
    |                |  |        |       |     Many columns         |     |    |
    |                |  |        |       +------------------------> |     |    |
    |                |  |        |       |                          |     |    |
    |                |  +-------------------------------------------------+    |
    |                v  +-------------------------------------------------+    |
    |                                                                          |
    +--------------------------------------------------------------------------+
    

Несколько вещей, которые я замечаю сразу:

  1. User_id повторяется столько раз, сколько записей для каждого пользователя.
  2. Строки очень редки для дополнительных столбцов (пустые нулевые значения), так как пользователи не обязательно разделяют их.
  3. Количество пользователей относительно невелико, поэтому количество дополнительных столбцов невелико (максимум 10К столбцов)
  4. Я мог бы сжать данные дополнительных столбцов для каждого пользователя в один столбец под названием «метаданные» и поделиться ими для всех пользователей.

Вариант 2:

Создать пространство ключей для каждого User_id

Создайте таблицу "данных" для каждого пространства ключей

+-----------------------------------------------------------------------------------+
| column_1 | column_2 | ... | column_n | additional_column_1 | additional_column_n  |
+-----------------------------------------------------------------------------------+

keyspace_user1         keyspace_user2                     keyspace_user_n
+----------------+    +---------------+                  +---------------+
|                |    |               |                  |               |
|                |    |               |                  |               |
|   +-+-+--+-+   |    |    +-+--+--+  |                  |   +--+--+---+ |
|   | | |  | |   |    |    | |  |  |  |   many keyspaces |   |  |  |   | |
|   | | |  | |   |    |    | |  |  |  | +------------->  |   |  |  |   | |
|   | | |  | |   |    |    | |  |  |  |                  |   |  |  |   | |
|   | | |  | |   |    |    | |  |  |  |                  |   |  |  |   | |
|   +--------+   |    |    +-------+  |                  |   +---------+ |
+----------------+    +---------------+                  +---------------+

Примечания:

  1. Множество пространств ключей (пространство ключей на пользователя)
  2. Избегает добавления значения user_id для каждой строки (я могу использовать имя ключевого пространства в качестве идентификатора пользователя)
  3. Очень мало таблиц на пространство ключей (в этом примере только 1 таблица на пространство ключей)

Вариант 3:

1) Создайте глобальное пространство ключей 2) Создайте таблицу для user_id (обязательные столбцы, а также их дополнительные столбцы для их таблицы)

+---------------------------------------------------------------+
|                            Keyspace                           |
|                                                               |
|       user_1        user_2                         user_n     |
|    +--+---+--+   +--+--+--+                      +--+--+--+   |
|    |  |   |  |   |  |  |  |                      |  |  |  |   |
|    |  |   |  |   |  |  |  |                      |  |  |  |   |
|    |  |   |  |   |  |  |  |                      |  |  |  |   |
|    |  |   |  |   |  |  |  |                      |  |  |  |   |
|    |  |   |  |   |  |  |  |                      |  |  |  |   |
|    +--+---+--+   +--+--+--+                      +--+--+--+   |
|                                                               |
|                                                               |
+---------------------------------------------------------------+

Примечания

  1. Глобальное пространство ключей
  2. Таблица на user_id ("много" таблиц)
  3. Избегает дублирования идентификатора пользователя в строке

Вариант 4: (Есть ли в этом смысл?)

Создайте несколько пространств ключей (например, количество пространств ключей "x"), каждое из которых содержит диапазон таблиц (таблица для каждого пользователя)

                      keyspace_1                                                                                keyspace_x
+---------------------------------------------------------------+                         +---------------------------------------------------------------+
|                                                               |                         |                                                               |
|                                                               |                         |                                                               |
|       user_1        user_2                        user_n/x    |                         |     user_n-x      user_n-x+1                       user_n     |
|    +--+---+--+   +--+--+--+                      +--+--+--+   |                         |    +--+------+   +--+--+--+                      +--+--+--+   |
|    |  |   |  |   |  |  |  |                      |  |  |  |   |        "X" keyspaces    |    |  |   |  |   |  |  |  |                      |  |  |  |   |
|    |  |   |  |   |  |  |  |                      |  |  |  |   | +---------------------> |    |  |   |  |   |  |  |  |                      |  |  |  |   |
|    |  |   |  |   |  |  |  |                      |  |  |  |   |                         |    |  |   |  |   |  |  |  |                      |  |  |  |   |
|    |  |   |  |   |  |  |  |                      |  |  |  |   |                         |    |  |   |  |   |  |  |  |                      |  |  |  |   |
|    |  |   |  |   |  |  |  |                      |  |  |  |   |                         |    |  |   |  |   |  |  |  |                      |  |  |  |   |
|    +--+---+--+   +--+--+--+                      +--+--+--+   |                         |    +--+---+--+   +--+--+--+                      +--+--+--+   |
|                                                               |                         |                                                               |
|                                                               |                         |                                                               |
+---------------------------------------------------------------+                         +---------------------------------------------------------------+

Примечания:

  1. Несколько пространств клавиш
  2. Несколько таблиц на пользователя
  3. Требуется «поиск», чтобы выяснить, какое пространство ключей содержит требуемую таблицу.

Вариант 5:

Разделение данных на несколько таблиц и несколько пространств ключей

Примечания: 1. В некоторых случаях требуется "объединение" информации из нескольких таблиц 2. Кажется более сложным


Общие примечания для всех сценариев:

  1. Записи намного меньше, чем чтения
  2. Многие миллионы чтений в день
  3. Трафик колеблется в зависимости от user_id - у некоторых user_ids много трафика, а у некоторых user_ids гораздо меньше трафика. Потребуется настроить по этому показателю
  4. Некоторые user_ids обновляются (записываются) чаще, чем другие
  5. У нас есть несколько центров обработки данных в разных регионах, и мы должны синхронизировать
  6. У каждого первичного ключа есть длинный хвост (к некоторым ключам обращаются много раз, к другим - редко)

person Avner Barr    schedule 25.09.2017    source источник
comment
Я сам новичок в cassandra, но вариант 1 для меня наиболее логичен. Кассандра построена для разреженных колонн. Также обратите внимание на составные первичные ключи - PRIMARY KEY (key_part_one, key_part_two). посмотрите здесь: stackoverflow.com/a/24953331/1277048. это дает некоторую гибкость при поиске по сравнению с подходом конкатенации: вы можете прочитать ВСЕ строки с key_part_one в одном запросе ИЛИ только строку, которая соответствует (key_part_one, key_part_two).   -  person FuzzyAmi    schedule 25.09.2017
comment
Составьте список всех выбранных вами запросов, а затем спроектируйте свою модель данных в соответствии с вашим запросом.   -  person Ashraful Islam    schedule 25.09.2017


Ответы (2)


Проблема интеграции этого типа обычно решается с помощью EAV (Entity Attribute Value) модель данных в реляционных системах (например, та, что демонстрирует Ашрафаул). Ключевым моментом при рассмотрении модели EAV является неограниченное количество столбцов. Модель данных EAV, конечно, может быть имитирована в системе CQL, такой как Cassandra или ScyllaDB. Модель EAV хорошо подходит для записи, но создает проблемы при чтении. Вы не совсем подробно изложили свои соображения по чтению. Вам нужно вернуть все столбцы или вам нужно вернуть определенные столбцы для каждого пользователя?

Файлы

Сказав это, есть некоторые дополнительные соображения, присущие Cassandra и ScyllaDB, которые могут указать вам на унифицированную модель EAV по некоторым проектам, которые вы описываете в своем вопросе. И Cassandra, и ScyllaDB размещают пространства ключей и базы данных в виде файлов на диске. Количество файлов в основном является произведением количества пространств ключей, умноженного на количество таблиц. Таким образом, чем больше у вас будет пространств клавиш, таблиц или их комбинации, тем больше файлов у вас будет на диске. Это может быть проблема с файловыми дескрипторами и другими проблемами манипулирования файлами ОС. Из-за длинного хвоста доступа, о котором вы упомянули, может случиться так, что каждый файл открыт все время. Это не очень желательно, особенно при запуске из холодного состояния.

[редактировать для ясности] При прочих равных условиях одно пространство ключей / таблица всегда будет создавать меньше файлов, чем множество пространств ключей / таблиц. Это не имеет ничего общего с объемом хранимых данных или стратегией сжатия.

Широкие ряды

Но вернемся к модели данных. Модель Ашрафула имеет первичный ключ (идентификатор пользователя) и другой ключ кластеризации (ключ-> столбец1). Из-за количества «записей» в каждом пользовательском файле (500–2M) и при условии, что каждая запись представляет собой строку, состоящую из 60 столбцов в среднем, вы в основном создаете 500–2 млн * 60 строк столбцов в среднем на каждый ключ раздела. создание очень больших перегородок. Кассандра и Сцилла вообще не любят очень большие перегородки. Конечно, они могут справиться с большими перегородками. Да, на практике большие разделы влияют на производительность.

Обновления или версии

Вы упоминаете обновления. Базовая модель 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-файле пользователя.

person siculars    schedule 27.09.2017
comment
Это хороший ответ. спасибо, что нашли время написать это. Мне интересно, почему (или, другими словами - какова цель) сохранение количества столбцов (max_columns). - person FuzzyAmi; 27.09.2017
comment
Отличный ответ. Спасибо, что нашли время. Еще одно соображение, которое у меня есть, заключается в том, что у нас есть несколько систем, имеющих доступ к этим данным. В некоторых случаях все столбцы запрашиваются по ключу, т.е. (выберите * из таблицы, где ключ = xxx), поэтому, возможно, имеет смысл использовать столбец blob, содержащий все, в то время как в других случаях только определенные столбцы извлекаются и группируются по определенным критериям. Также я знаю, что части БД будут очень горячими (в зависимости от user_id), в то время как другие будут меньше. - person Avner Barr; 27.09.2017
comment
@FuzzyAmi Просто удобная функция. В противном случае вам пришлось бы вытащить весь json и выполнить подсчет (цикл по массиву, ключи и т. Д.) - person siculars; 27.09.2017
comment
@AvnerBarr Одна из трудностей, от которых я избежал в модели EAV, - это сторона чтения. В реляционных системах вы вынуждены вращать модель EAV, чтобы получить данные в табличном формате. Этих функций нет в Cass / Scylla. Вам необходимо выполнить преобразование данных на стороне клиента. Вы можете хранить все данные дважды в стиле EAV и в виде сериализованного json blob, но я полагаю, вы не хотите тратить пространство впустую. - person siculars; 27.09.2017
comment
@AvnerBarr (закончилось место) Вы можете select * from table where userid=foo and column1=bar и не включать ключевой компонент кластеризации, который вернет вам все столбцы. Все они будут возвращены в виде строк, и вам нужно будет сшить их вместе в функции перевода, чтобы снова собрать их в одну логическую строку. - person siculars; 27.09.2017

Попробуйте схему ниже:

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');
person Ashraful Islam    schedule 25.09.2017