Статьи

Сортировка нумерации страниц в Кассандре

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

Временные ряды в Кассандре

Временной ряд представляет собой набор данных, связанных с некоторой переменной. График Facebook будет отличным примером. Пользователь со временем напишет серию сообщений. Шаблоны доступа к этим данным будут что-то вроде «вернуть 20 последних сообщений пользователя 1234». DDL таблицы, которая моделирует этот запрос:

1
2
3
4
5
6
7
CREATE TABLE timeline (
    user_id uuid,
    post_id timeuuid,
    content text,
    PRIMARY KEY (user_id, post_id)
)
WITH CLUSTERING ORDER BY (post_id DESC);

В Кассандре Первичные Ключи образованы Ключами Разделения и Ключами Кластеризации. Первичные ключи обеспечивают уникальность некоторых ячеек в отличие от реляционных баз данных. Строгое соблюдение этой уникальности невозможно, если вы попытаетесь вставить ячейку, связанную с уже существующим первичным ключом, которая будет обновлена. Также наоборот: «пропущенное» обновление закончится вставкой. Это называется upsert.

Ключи секционирования гарантируют, в каком узле кластера будут жить данные. Если вы включите хотя бы один ключ кластеризации, ключ разделения будет идентифицировать N строк. Это может сбить с толку кого-то из традиционных реляционных баз данных. Cassandra делает все возможное, чтобы перенести свои концепции в терминологию SQL, но иногда это может быть странно для новичков. Пример таблицы временной шкалы:

1
2
3
4
5
user_id--------------------------------post_id--------content
346e896a-c6b4-4d4e-826d-a5a9eda50636---today----------Hi
346e896a-c6b4-4d4e-826d-a5a9eda50636---yesterday------Hola
346e896a-c6b4-4d4e-826d-a5a9eda50636---one week ago---Bye
346e896a-c6b4-4d4e-826d-a5a9eda50636---two weeks ago--Ciao

Чтобы понять пример, я преобразовал значения post_id во что-то, что имеет смысл для читателя. Как вы можете видеть, есть несколько значений с одним и тем же ключом раздела (user_id), который работает так, как мы определили ключ кластеризации (post_id), который группирует эти значения и сортирует их (в данном случае по убыванию). Помните, что уникальность определяется первичным ключом (раздел и ключ кластеризации), поэтому, если мы вставим строку, обозначенную как «346e896a-c6b4-4d4e-826d-a5a9eda50636» и «сегодня», содержимое будет обновлено. Ничего действительно не обновляется на диске, так как Cassandra работает с неизменяемыми структурами на диске, но во время чтения разные записи с одним и тем же первичным ключом будут разрешаться в порядке убывания.

Давайте посмотрим несколько запросов, чтобы закончить этот пример:

1
2
SELECT * FROM timeline
where user_id = 346e896a-c6b4-4d4e-826d-a5a9eda50636

-> Он вернет четыре строки, отсортированные по post_id DESC

1
2
SELECT content FROM timeline
where user_id = 346e896a-c6b4-4d4e-826d-a5a9eda50636 LIMIT 1

-> Вернется «Привет»

1
2
SELECT content FROM
timeline where user_id = 346e896a-c6b4-4d4e-826d-a5a9eda50636 and post_id > today LIMIT 2

-> Он вернет ‘Hola’ и ‘Bye’

Как вы можете видеть, реализация отсортированной нумерации страниц очень проста при моделировании временных рядов в Cassandra. Кроме того, он будет очень производительным, так как Cassandra хранит все строки, идентифицированные одним ключом секционирования, в одном и том же узле, поэтому для извлечения этих данных потребуется одна поездка туда и обратно (при условии, что уровень согласованности чтения ОДИН)

Давайте посмотрим, что произойдет, когда мы захотим реализовать отсортированную нумерацию страниц в другом варианте использования.

Сортированные наборы в Кассандре

Если мы рассмотрим в предыдущем примере на уровне абстракции структуры данных, то увидим, что мы только что смоделировали Карту со значениями Sorted Sets. Что произойдет, если мы захотим смоделировать что-то вроде отсортированного набора с помощью Cassandra?

Наш сценарий следующий. Пользователи нашей системы могут быть приостановлены или приостановлены через некоторый административный портал. Администраторы хотели бы взглянуть на последних пользователей, которые были приостановлены по причине приостановки, чтобы проверить это решение или отменить его. Это очень похоже на наши предыдущие постраничные запросы, поэтому давайте посмотрим, как мы можем смоделировать это с помощью Cassandra.

1
2
3
4
5
CREATE TABLE suspended_users (
    user_id uuid,
    occurred_on timestamp,
    reason text
)

Я намеренно пропустил первичный ключ из этого DDL, чтобы мы могли обсудить различные варианты.

Понимание ключей кластеризации

Ранее мы использовали ключи кластеризации, чтобы обеспечить некоторый порядок в наших данных. Давайте пойдем с этим вариантом:

1
PRIMARY KEY (user_id, occurred_on)

Вы видите, что с этим не так? Забудьте о деталях реализации на секунду и ответьте на этот вопрос, сколько раз пользователь появится в этой таблице? Как ваш избранный владелец продукта, я скажу, что только один. Когда пользователь не будет приостановлен, я хотел бы удалить пользователя из этой таблицы, и пользователь, которого приостановили, не может быть снова приостановлен. Следующий вопрос: где мы хотим сохранить порядок? Не внутри пользователей (даже меньше в этом случае, поскольку наш единственный пользователь всегда будет «заказан»), но среди пользователей. Этот дизайн не будет работать.

Понимание разделов и разделителей

У меня есть новая информация, которая может вам помочь. Эта таблица будет обновляться в режиме реального времени, что означает, что в этой таблице должен сохраняться некоторый логический порядок вставки. Поскольку мы не вдавались в детали Кассандры, мы могли подумать, что будет работать следующее:

1
PRIMARY KEY (user_id)

Давайте посмотрим, как этот логический порядок вставки отображается на физический. Кассандра хранит свои данные в кольце узлов. Каждому узлу назначается один токен (или несколько, если мы используем vnodes). Когда вы CRUD некоторые данные, Кассандра вычислит, где в кольце живут эти данные, используя Partitioner, который будет хэшировать ключ разделения. При использовании рекомендуемых разделителей строки Cassandra упорядочены по хеш-значениям, и, следовательно, порядок строк не имеет смысла , так что порядок логической вставки будет логичным, и ничего более. Это означает, что этот запрос вернет 20 пользователей без какого-либо значимого порядка:

1
SELECT * FROM suspended_users LIMIT 20;

Используя функцию токена, мы могли разбивать большие массивы данных на страницы, как было объяснено здесь .

1
SELECT * FROM suspended_users where token(user_id) > token([Last user_id received]) LIMIT 20;

Однако мы хотим разбить отсортированный набор по времени приостановки и убыванию.

Представление обратного поиска

Денормализация — это нечто обычное в Кассандре. Чтобы преодолеть ограничения, налагаемые реализацией Cassandra, денормализация наших данных является предлагаемым подходом. Благодаря нашему предыдущему примеру мы поняли, что для поддержания порядка между данными нам нужно кластеризовать их. Никто не заставляет нас использовать таблицу suspen_users, даже если наш домен говорит об этом. Поскольку нам нужна некоторая фиксированная переменная для создания временной серии, мы перейдем к состоянию:

1
2
3
4
5
6
7
CREATE TABLE users_by_status (
  status text,
  occurred_on timestamp,
  user_id uuid
  reason text,
  PRIMARY KEY (status, occurred_on, user_id)
) WITH CLUSTERING ORDER BY (occurred_on DESC);

Ключи разделов и кластеров могут быть составлены. В этом конкретном ключе «status» будет ключом раздела, а «seen_on» / «user_id» ключом кластеризации. Порядок по умолчанию — ASC, поэтому мы указали DESC «происшедшее» внутри CLUSTERING ORDER BY. Важно отметить, что «user_id» будет использоваться для целей уникальности в этом проекте, даже если он упорядочит строки в маловероятном случае, когда два пользователя будут приостановлены в очень точное время.

Теперь, когда мы создали «искусственную» кластеризацию, мы можем разбить на страницы отсортированным образом, как в нашем первом примере. Это представляет несколько проблем, хотя. Cassandra не будет разбивать данные внутри строки, и рекомендуемый максимальный размер строк внутри раздела составляет 200 КБ. Если вы предвидите, что ваша система будет расти больше, чем вы, вы можете разделить строки с помощью техники составных ключей разбиения, используя временные сегменты.

1
2
3
4
5
6
7
8
CREATE TABLE users_by_status (
  bucket text,
  status text,
  occurred_on timestamp,
  user_id uuid
  reason text,
  PRIMARY KEY ((bucket, status), occurred_on, user_id)
) WITH CLUSTERING ORDER BY (occurred_on DESC);

Быть чем-то вроде ММ-ГГГГ или каким-то детальным указанием, которое подскажут ваши данные. Здесь я представляю новый кусочек CQL (Cassandra Query Language), который представляет собой составные ключи разделов. Как вы можете видеть, все, что находится внутри этих вложенных скобок, будет ключом раздела.

Следующая проблема — как мы будем удалять или обновлять пользователей, которые должны быть приостановлены. Администратор может иметь user_id и occured_on, и это не будет проблемой, поскольку он может сделать запрос, подобный этому:

1
DELETE FROM users_by_status WHERE status = 'SUSPENDED' and occurred_on = ... and user_id = ...

К сожалению, этот администратор мог получить запрос от некоторых привилегированных менеджеров на приостановку работы пользователя. Менеджер не знает, когда произошло приостановление, он только знает, кто является пользователем. Это означает, что мы не можем получить доступ к конкретному ряду, так как у нас нет ‘seen_on’. Помните, что для запроса в Cassandra вам необходимо предоставить весь ключ раздела (в противном случае Cassandra не будет знать, в какой узел ему нужно перейти для получения данных) и дополнительные части ключа кластеризации (но всегда слева направо).

Чтобы преодолеть эту проблему, мы могли бы создать вторичный индекс в столбце ‘user_id’. В реляционных базах данных индексы позволяют нам быстрее запрашивать некоторые данные, создавая денормализованную структуру. В Cassandra эти вторичные индексы позволяют нам выполнять запросы по столбцам, которые в противном случае будет невозможно использовать. Тем не менее, они разочарованы, так как они сильно ударили по производительности, так как им требуется несколько переходов в разные узлы.

Следующее решение — создание собственного вторичного индекса вручную, что называется обратным поиском. Посмотрим, как это выглядит:

1
2
3
4
5
CREATE TABLE suspended_users (
  user_id uuid,
  occurred_on timestamp,
  PRIMARY KEY (user_id)
);

Эта таблица будет служить нам обратным поиском. Просто имея ‘user_id’, мы сможем получить доступ к значению ‘seen_on’, а затем мы сможем запросить таблицу users_by_status. У этого подхода есть некоторые недостатки. Всякий раз, когда мы вставляем или удаляем пользователя, нам приходится переходить к двум таблицам, но это фиксированное число. Со вторичным индексом нам придется перейти к N узлам в худшем случае. Таким образом, он идет от O (1) к O (N). Наш код также будет более сложным, так как нам придется связываться с двумя разными таблицами.

Это представляет более серьезный недостаток, который заключается в возможной последовательности и транзакциях в Кассандре. Транзакции не встроены в ядро ​​Cassandra (есть такие понятия, как упрощенные транзакции или пакеты, но они тоже неэффективны), так что это означает, что наш код должен вручную заботиться о транзакциях.

Если мы хотим удалить пользователя, мы должны начать с таблицы users_by_status. Если мы начнем с другой стороны, и второе удаление не удастся, мы не сможем удалить эту строку в будущем, так как мы удалили запись обратного просмотра. Мы можем ввести шаблон Saga, который в основном определяет заклятого врага в каждом шаге программной транзакции.

ВЫВОД

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

Ссылка: Сортировка нумерации страниц в Кассандре от нашего партнера JCG Сандро Манкузо в блоге Crafted Software .