Как выбрать оптимальный первичный ключ в Cassandra?

Автор оригинальной публикации: Патрик Макфейдин (Patrick McFadin)

Перевод Станислава Петренко

Первичный ключ (primary key) является основой эффективного моделирования данных в Apache Cassandra. В случае реляционной модели первичный ключ также играет важную роль, но эффективность модели данных в реляционной СУБД в большей степени зависит от взаимосвязи внешних ключей (foreign key) и реляционных ограничений (relational constraint) для таблиц. Поскольку при работе с Cassandra невозможно применять оператор JOIN, возникает намного меньше сложностей при моделировании данных. Обратной стороной медали является необходимость заранее знать, какие будут использоваться запросы и шаблоны доступа к данным. Этот вопрос мы не будем рассматривать в данной статье, поскольку существует отличный курс от DataStax Academy. Мы сфокусируемся на том, как выбрать оптимальный первичный ключ.

Простой первичный ключ

Начнем с простейшей «статической таблицы». Рассмотрим пример из демонстрационного сервиса KillrVideo:

CREATE TABLE videos (

   videoid uuid,

   userid uuid,
   name varchar,

   description varchar,
   location text,

   location_type int,
   preview_thumbnails map<text,text>,

   tags set<varchar>,

   added_date timestamp,

   PRIMARY KEY (videoid)

);

Первичный ключ задается с помощью оператора PRIMARY KEY. В нашем примере в качестве первичного ключа выступает videoid – идентификатор видео, загруженного в систему. Первичный ключ может состоять из нескольких элементов, первый из которых является ключом раздела (partition key). Ключ раздела не только определяет уникальность записи в БД, но также имеет специальное назначение в Cassandra: определяет местоположение данных, что крайне важно в распределенных системах.

При добавлении данных в кластер, ключ раздела обрабатывается хеш-функцией, и результат показывает, какой узел (и реплики) получит данные. Cassandra использует алгоритм Murmur3, принимающий на входе произвольные данные и генерирующий хеш, значение которого находится в интервале значений, принадлежащих одному узлу.

Проще говоря, ключ раздела всегда принадлежит одному узлу, и данные этого раздела всегда можно найти на этом узле.

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

Составной первичный ключ

В Cassandra существуют также таблицы другого типа, называемые «динамическими таблицами». Рассмотрим еще один пример из демонстрационного сервиса KillrVideo:

CREATE TABLE user_videos (
   userid uuid,
   added_date timestamp,
   videoid uuid,
   name text,
   preview_image_location text,
   PRIMARY KEY (userid, added_date, videoid)
);

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

  • Первый элемент (userid) – ключ раздела.
  • Второй элемент (added_date) – первый ключ группировки. Этот ключ является меткой времени, поэтому сортировка выполняется в хронологическом порядке по возрастанию.
  • Третий элемент (videoid) – второй ключ группировки. Этот ключ представляет собой UUID (universally unique identifier, универсально уникальный идентификатор). Мы добавили его, просто чтобы показать, что это часть уникальной записи.

После добавления данных запрос SELECT вернет данные в порядке возрастания added_date.

Задаем направление сортировки

Поскольку ключи группировки определяют порядок данных в разделе, полезно иметь возможность управлять направлением сортировки. Мы можем реализовать это на этапе выполнения, добавив оператор ORDER BY в запрос SELECT:

SELECT *
FROM user_videos
WHERE userid = 522b1fe2-2e36-4cef-a667-cd4237d08b89
ORDER BY added_date DESC;

Мы также можем задать направление сортировки по умолчанию для модели данных. Это можно сделать на этапе создания таблицы с помощью оператора CLUSTERING ORDER BY:

CREATE TABLE user_videos (
   userid uuid,
   added_date timestamp,
   videoid uuid,
   name text,
   preview_image_location text,
   PRIMARY KEY (userid, added_date, videoid)
) WITH CLUSTERING ORDER BY (added_date DESC, videoid ASC);

Теперь при добавлении данных в таблицу user_videos, они будут отсортированы по ключу added_date в порядке убывания.

Возможно, это похоже на предварительную оптимизацию, но на практике этот подход находит широкое применение. Например, в случае временных рядов CLUSTERING ORDER BY позволяет получить быстрый доступ к N последним добавленным элементам:

SELECT *
FROM user_videos
WHERE userid = 522b1fe2-2e36-4cef-a667-cd4237d08b89
LIMIT 10;

Представленный выше запрос вернет 10 последних видео данного пользователя. Таким образом, благодаря CLUSTERING ORDER BY, мы получаем возможность выполнять очень быстрые и эффективные запросы. Этот подход может быть полезен при взаимодействии с пользователем или выявлении мошенничества.

Заключение

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

Добавить комментарий

Ваш e-mail не будет опубликован.

закрыть

Поделиться

Отправить на почту
закрыть

Вход

закрыть

Регистрация

+ =