Моделирование пользовательских данных без SQL (Cassandra)

Как вы моделируете пользовательские данные в Cassandra?

  1. Единая таблица для пользовательских данных, разделенная по идентификатору пользователя, с разными компонентами, читающими / записывающими в разные столбцы?
  2. Несколько таблиц (по одной на компонент) с одинаковой ключевой структурой, которые иногда необходимо «объединить» вместе с помощью ключа раздела?

У нас есть различные данные и метаданные, связанные с клиентом, которые в настоящее время хранятся в отдельных таблицах с одинаковыми ключами разделения и кластеризации.

Это приводит к извлечению для пользователя битов информации из разных таблиц (например, в аналитику), эффективно «объединяя» две или более таблиц Cassandra на их ключах разделов.

С положительной стороны, вставка в таблицы выполняется независимо.

Существует ли состояние гонки при одновременном обновлении данных под одним и тем же ключом раздела, но в разных столбцах? Или дельты изящно сливаются на SSTables?

Наличие нескольких таблиц с одинаковыми ключами разделов (и кластеризации) - обычное дело или анти-шаблон?

Чтобы сделать это более конкретным, предположим, что у нас есть:

CREATE TABLE example (
  pk text PRIMARY KEY
  col_a text
  col_b text
)

Предположим, что для данного ключа раздела (pk) изначально и col_a, и col_b имеют некоторое значение (т.е. не ноль). и две одновременные вставки обновляют каждую из них. Возможны ли там какие-либо условия гонки? Потеряете одно из двух обновлений, несмотря на запись в отдельные столбцы?

Установка Apache Cassandra на Mac OS
Установка Apache Cassandra на Mac OS
Это краткое руководство по установке Apache Cassandra.
8
0
114
1

Ответы 1

Резюме

Конфликты записи - это то, о чем вам не нужно беспокоиться. Все ВСТАВКИ / ОБНОВЛЕНИЯ / УДАЛЕНИЯ - это обновления в Кассандре. Все в Cassandra основано на столбцах.

Кассандра использует стратегию «последняя запись побеждает» для управления конфликтами. Как вы можете видеть на примере ниже, всякий раз, когда вы меняете значение, обновляется временная метка, связанная с этим столбцом. Поскольку вы выполняете одновременные обновления, один поток будет обновлять col_a, а другой - col_b.


Пример

Начальная вставка

cqlsh:test_keyspace> insert into race_condition_test (pk, col_a, col_b ) VALUES ( '1', 'deckard', 'Blade Runner');
cqlsh:test_keyspace> select * from race_condition_test ;

 pk | col_a   | col_b
----+---------+--------------
  1 | deckard | Blade Runner

(1 rows)

Метки времени такие же в исходной вставке

cqlsh:test_keyspace> select pk, col_a, writetime(col_a), col_b, writetime(col_b) from race_condition_test ;

 pk | col_a   | writetime(col_a) | col_b        | writetime(col_b)
----+---------+------------------+--------------+------------------
  1 | Deckard | 1526916970412357 | Blade Runner | 1526916970412357

(1 rows)

После обновления col_b его временная метка изменяется, чтобы отразить это изменение.

cqlsh:test_keyspace> insert into race_condition_test (pk, col_b ) VALUES ( '1', 'Rick');
cqlsh:test_keyspace> select pk, col_a, writetime(col_a), col_b, writetime(col_b) from race_condition_test ;

 pk | col_a   | writetime(col_a) | col_b | writetime(col_b)
----+---------+------------------+-------+------------------
  1 | Deckard | 1526916970412357 |  Rick | 1526917272641682

(1 rows)

После обновления col_a его метка времени также обновляется до нового значения.

cqlsh:test_keyspace> insert into race_condition_test (pk, col_a) VALUES ( '1', 'bounty hunter');
cqlsh:test_keyspace> select pk, col_a, writetime(col_a), col_b, writetime(col_b) from race_condition_test ;

 pk | col_a         | writetime(col_a) | col_b | writetime(col_b)
----+---------------+------------------+-------+------------------
  1 | bounty hunter | 1526917323082217 |  Rick | 1526917272641682

(1 rows)

Рекомендация

Я рекомендую вам использовать одну единственную таблицу, которая удовлетворяет вашим запросам. Если вам нужно выполнить запрос по pk, создайте одну единую таблицу со всеми необходимыми столбцами. Таким образом, у вас будет одна широкая строка, которую можно будет эффективно прочитать как часть одного запроса.

Модель данных, которую вы описываете в варианте 2, немного реляционная и не оптимальна для Cassandra. Вы не можете выполнять соединения изначально в cassandra, и вам следует избегать предварительного формирования соединений на стороне клиента.

Правила режима данных:

Правило 1: Равномерно распределять данные по кластеру Вам нужно будет создать ключ раздела, который обеспечит равномерное распределение данных по кластеру и отсутствие горячих точек.

Правило 2: Минимизировать количество разделов на чтение Каждый раздел может находиться на разных узлах, поэтому вы должны попытаться создать сценарий, в котором ваши запросы в идеале направляются только на один узел для повышения производительности.

Правило 3: Модель вокруг ваших запросов

  1. Определите, какие запросы нужно поддерживать
  2. Создайте таблицу, которая удовлетворяет вашему запросу (это означает, что вы должны использовать одну таблицу для каждого шаблона запроса).
  3. Если вам нужно поддерживать больше шаблонов запросов, денормализуйте данные в дополнительных таблицах, которые обслуживают эти запросы. Избегайте вторичных индексов и материализованных представлений, поскольку они нестабильны в настоящий момент, и первый из них может создать серьезные проблемы с производительностью, когда вы начнете увеличивать свой кластер.

Если вы хотите прочитать об этом немного больше, я предлагаю эту страницу с данными о данных: Основные правила моделирования данных Cassandra

Другие вопросы по теме