Транзакции
Хранилище Deno KV использует транзакции с оптимистическим контролем конкуретности вместо интерактивных транзакций, как во многих системах SQL, таких как PostgreSQL или MySQL. Этот подход использует метки версии, которые представляют текущую версию значения для данного ключа, для управления одновременным доступом к общим ресурсам без использования блокировок. Когда происходит операция чтения, система возвращает метку версии для соответствующего ключа наряду со значением.
Для выполнения транзакции выполняется атомарная операция, которая может состоять из нескольких действий изменения (например, установка или удаление). Наряду с этими действиями пары ключ+метка версии предоставляются в качестве условия успешного выполнения транзакции. Транзакция с оптимистическим контролем конкуретности будет подтверждена только в том случае, если указанные метки версии совпадают с текущими версиями значений в базе данных для соответствующих ключей. Эта модель транзакций гарантирует согласованность и целостность данных, одновременно позволяя одновременные взаимодействия в хранилище Deno KV.
Поскольку транзакции с оптимистическим контролем конкуретности являются оптимистичными, они могут завершиться неудачей при подтверждении, поскольку условия версии, указанные в атомарной операции, были нарушены. Это происходит, когда агент обновляет ключ, используемый в рамках транзакции, между чтением и подтверждением. В этом случае агент, выполняющий транзакцию, должен повторить её.
Чтобы проиллюстрировать, как использовать транзакции с оптимистическим контролем конкуретности с Deno KV, этот пример демонстрирует реализацию функции transferFunds(from: string, to: string, amount: number) для балансового отчета по счетам. Отчет по счетам хранит баланс для каждого счета в хранилище ключ-значение. Ключи имеют префикс "account", за которым следует идентификатор счета: ["account", "alice"]. Значение, хранящееся для каждого ключа, — число, представляющее баланс счета.
Вот пошаговый пример реализации этой функции transferFunds:
async function transferFunds(sender: string, receiver: string, amount: number) {
if (amount <= 0) throw new Error("Amount must be positive");
// Construct the KV keys for the sender and receiver accounts.
const senderKey = ["account", sender];
const receiverKey = ["account", receiver];
// Retry the transaction until it succeeds.
let res = { ok: false };
while (!res.ok) {
// Read the current balance of both accounts.
const [senderRes, receiverRes] = await kv.getMany([senderKey, receiverKey]);
if (senderRes.value === null) {
throw new Error(`Account ${sender} not found`);
}
if (receiverRes.value === null) {
throw new Error(`Account ${receiver} not found`);
}
const senderBalance = senderRes.value;
const receiverBalance = receiverRes.value;
// Ensure the sender has a sufficient balance to complete the transfer.
if (senderBalance < amount) {
throw new Error(
`Insufficient funds to transfer ${amount} from ${sender}`,
);
}
// Perform the transfer.
const newSenderBalance = senderBalance - amount;
const newReceiverBalance = receiverBalance + amount;
// Attempt to commit the transaction. `res` returns an object with
// `ok: false` if the transaction fails to commit due to a check failure
// (i.e. the versionstamp for a key has changed)
res = await kv.atomic()
.check(senderRes) // Ensure the sender's balance hasn't changed.
.check(receiverRes) // Ensure the receiver's balance hasn't changed.
.set(senderKey, newSenderBalance) // Update the sender's balance.
.set(receiverKey, newReceiverBalance) // Update the receiver's balance.
.commit();
}
}
В этом примере функция transferFunds считывает балансы и метки версии обоих счетов, вычисляет новые балансы после перевода и проверяет, достаточно ли средств на счету А. Затем она выполняет атомарную операцию, устанавливая новые балансы с ограничениями по меткам версии. Если транзакция выполняется успешно, цикл завершается. Если ограничения версии нарушены, транзакция завершается неудачей, и цикл повторяет транзакцию до тех пор, пока она не завершится успешно.
Пределы
В дополнение к максимальному размеру ключа 2 Кбайт и максимальному размеру значения 64 Кбайт, существуют определенные ограничения API транзакций Deno KV:
-
Максимальное количество ключей на
kv.getMany(): 10 -
Максимальный размер пакета на
kv.list(): 1000 - Максимальное количество проверок в атомарной операции: 100
- Максимальное количество изменений в атомарной операции: 1000
- Максимальный общий размер атомарной операции: 800 Кбайт. Это включает все ключи и значения в проверках и изменениях, а также накладные расходы на кодирование учитываются в этом пределе.
- Максимальный общий размер ключей: 90 Кбайт. Это включает все ключи в проверках и изменениях, а также накладные расходы на кодирование учитываются в этом пределе.
-
Максимальное количество отслеживаемых ключей на
kv.watch(): 10
© 2018–2024 the Deno authors
Licensed under the MIT License.
https://docs.deno.com/deploy/kv/manual/transactions