Вторичные индексы
Хранилища пар «ключ-значение», такие как Deno KV, организуют данные как коллекции пар «ключ-значение», где каждый уникальный ключ связан с одним значением. Эта структура обеспечивает лёгкий доступ к значениям по их ключам, но не позволяет выполнять запросы по самим значениям. Чтобы преодолеть это ограничение, можно создать вторичные индексы, которые хранят то же значение под дополнительными ключами, включающими (часть) этого значения.
Поддержание согласованности между первичными и вторичными ключами имеет решающее значение при использовании вторичных индексов. Если значение обновляется по первичному ключу без обновления вторичного ключа, данные, возвращённые по запросу, направленному на вторичный ключ, будут некорректными. Чтобы гарантировать, что первичные и вторичные ключи всегда представляют одни и те же данные, используйте атомарные операции при вставке, обновлении или удалении данных. Этот подход гарантирует, что группа операций мутации выполняется как единица, и либо все операции завершаются успешно, либо все завершаются неуспешно, предотвращая несогласованность.
Уникальные индексы (один к одному)
В уникальных индексах каждый ключ в индексе связан ровно с одним первичным ключом. Например, при хранении данных о пользователях и поиске пользователей по уникальным идентификаторам и адресам электронной почты храните данные о пользователе под двумя отдельными ключами: один для первичного ключа (идентификатора пользователя) и другой для вторичного индекса (адрес электронной почты). Эта настройка позволяет выполнять запросы пользователей по их идентификатору или по их электронной почте. Вторичный индекс также может накладывать ограничения уникальности на значения в хранилище. В случае данных о пользователях используйте индекс для обеспечения того, что каждый адрес электронной почты связан только с одним пользователем — другими словами, что адреса электронной почты уникальны.
Чтобы реализовать уникальный вторичный индекс для этого примера, выполните следующие действия:
-
Создайте
Userинтерфейс, представляющий данные:interface User { id: string; name: string; email: string; } -
Определите
insertUserфункцию, которая хранит данные пользователя как по первичному, так и по вторичному ключам:async function insertUser(user: User) { const primaryKey = ["users", user.id]; const byEmailKey = ["users_by_email", user.email]; const res = await kv.atomic() .check({ key: primaryKey, versionstamp: null }) .check({ key: byEmailKey, versionstamp: null }) .set(primaryKey, user) .set(byEmailKey, user) .commit(); if (!res.ok) { throw new TypeError("User with ID or email already exists"); } }Эта функция выполняет вставку с использованием атомарной операции, которая проверяет, что пользователь с таким же идентификатором или адресом электронной почты уже не существует. Если какое-либо из этих ограничений нарушается, вставка завершается неудачей, и данные не изменяются.
-
Определите
getUserфункцию для получения пользователя по его идентификатору:async function getUser(id: string): Promise<User | null> { const res = await kv.get<User>(["users", id]); return res.value; } -
Определите
getUserByEmailфункцию для получения пользователя по его адресу электронной почты:async function getUserByEmail(email: string): Promise<User | null> { const res = await kv.get<User>(["users_by_email", email]); return res.value; }Эта функция выполняет запрос к хранилищу, используя вторичный ключ (
["users_by_email", email]). -
Определите функцию deleteUser для удаления пользователей по их идентификатору:
async function deleteUser(id: string) { let res = { ok: false }; while (!res.ok) { const getRes = await kv.get<User>(["users", id]); if (getRes.value === null) return; res = await kv.atomic() .check(getRes) .delete(["users", id]) .delete(["users_by_email", getRes.value.email]) .commit(); } }Эта функция сначала получает пользователя по его идентификатору, чтобы получить адрес электронной почты пользователя. Это необходимо для получения адреса электронной почты, который нужен для построения ключа вторичного индекса для этого адреса пользователя. Затем она выполняет атомарную операцию, которая проверяет, что пользователь в базе данных не изменился, а затем удаляет как первичный, так и вторичный ключ, указывающий на значение пользователя. Если это завершается неудачей (пользователь был изменён между запросом и удалением), атомарная операция прерывается. Весь процесс повторяется до тех пор, пока удаление не завершится успешно. Проверка необходима для предотвращения гонок, когда значение может быть изменено между извлечением и удалением. Эта гонка может возникнуть, если обновление изменяет адрес электронной почты пользователя, так как вторичный индекс в этом случае перемещается. Затем удаление вторичного индекса завершается неудачей, поскольку удаление направлено на старый вторичный ключ индекса.
Неуникальные индексы (один ко многим)
Неуникальные индексы — это вторичные индексы, в которых один ключ может быть связан с несколькими первичными ключами, что позволяет выполнять запросы к нескольким элементам на основе общего атрибута. Например, при запросе пользователей по их любимому цвету реализуйте это с помощью неуникального вторичного индекса. Любимый цвет — неуникальный атрибут, так как несколько пользователей могут иметь одинаковый любимый цвет.
Чтобы реализовать неуникальный вторичный индекс для этого примера, выполните следующие действия:
-
Определите
Userинтерфейс:interface User { id: string; name: string; favoriteColor: string; } -
Определите
insertUserфункцию:async function insertUser(user: User) { const primaryKey = ["users", user.id]; const byColorKey = [ "users_by_favorite_color", user.favoriteColor, user.id, ]; await kv.atomic() .check({ key: primaryKey, versionstamp: null }) .set(primaryKey, user) .set(byColorKey, user) .commit(); } -
Определите функцию для получения пользователей по их любимому цвету:
async function getUsersByFavoriteColor(color: string): Promise<User[]> { const iter = kv.list<User>({ prefix: ["users_by_favorite_color", color] }); const users = []; for await (const { value } of iter) { users.push(value); } return users; }
Этот пример демонстрирует использование неуникального вторичного индекса, users_by_favorite_color, который позволяет выполнять запросы к пользователям на основе их любимого цвета. Первичным ключом остаётся пользователь id.
Основное различие в реализации уникальных и неуникальных индексов заключается в структуре и организации вторичных ключей. В уникальных индексах каждый вторичный ключ связан ровно с одним первичным ключом, гарантируя, что индексируемый атрибут уникален во всех записях. В случае неуникальных индексов один вторичный ключ может быть связан с несколькими первичными ключами, поскольку индексируемый атрибут может быть общим для нескольких записей. Чтобы этого добиться, неуникальные вторичные ключи обычно структурируются с дополнительным уникальным идентификатором (например, первичным ключом) как частью ключа, позволяя нескольким записям с одним и тем же атрибутом сосуществовать без конфликтов.
© 2018–2024 the Deno authors
Licensed under the MIT License.
https://docs.deno.com/deploy/kv/manual/secondary_indexes