Building safe atomic transactions with Deno KV

Deno's Key Values

The Deno Team are building a unique product. It's an all-javascript development experience with very simple deployments:

Deno KV is a key-value store built into Deno runtime and Deno Deploy.

Example

const kv = await Deno.openKv();
const { value } = await kv.get(["example_view_counter"]);

// Adds 1 to view counter
await kv.set(["example_view_counter"], value + 1);

console.log("πŸŽ‰");

Deno KV is simple. But imagine two pages read 5 at the same time. They'd both write 6. The counter would only go up by 1 for two views!

Solve with atomic operations

You could use Deno's atomic sum operation.

const kv = await Deno.openKv();

// Adds 1 to view counter
await kv.atomic()
  .mutate({
    type: "sum",
    key: ["example_view_counter"],
    value: new Deno.KvU64(1n),
  })
  .commit();

That covers simple operations, and perhaps Deno KV is meant to be used with many simple read/write ops. But what about updating complex objects?

Introducing Deno KV Plus

Deno KV inspired me to make Deno KV Plus! It's a wrapper around Deno KV that adds atomic operations for complex objects.

Example

import { withSafeAtomics } from "https://deno.land/x/kvp/mod.ts";

// Create a KV instance with atomic support
const kv = withSafeAtomics(await Deno.openKv());

// Let's increment a view counter? Deno KV already has atomic counters.
await kv.setSafeAtomic(["view_counter"], (value) => value + 1);

// So let's try a more complex example
export async function giveGift(userId, giftName) {
  await kv.setSafeAtomic(["users", userId], (userData = { items: [] }) => {
    userData.items.push(giftName);
    return userData;
  });
}

// Now we can call it many times at the same time
await Promise.all([
  giveGift("Shona", "🎁"),
  giveGift("Shona", "πŸŽ‰"),
  giveGift("Shona", "🎊"),
]);

// Deno KV Plus __guarantees__ the user gets all three gifts!

Deno KV Plus simplifies handling race conditions and ensures atomic updates without manual retries. Using setSafeAtomic, it fetches and recalculates the freshest data, guaranteeing accurate calculations. Additionally, it can abort the update process if values change and the update becomes invalid.

Can I just use Deno's native KV?

Yes. Let's say you have an object like { items: [] } living at key ["users", "<username>"].

You could take out the items into key ["users", "<username>", "items"]. Then you could use Deno KV's atomic operations to add items to the array.

const kv = await Deno.openKv();

// Add a new 🎁 to Shona's items. If one is already present, its value will become 2, 3, 4 etc.
await kv.atomic().mutate({
  type: "sum",
  key: ["users", "Shona", "items", "🎁"],
  value: new Deno.KvU64(1n),
}).commit();

// List Shona's items
const items = await kv.list({ prefix: ["users", "Shona", "items"] });

for await (const { value: item } of items) {
  console.log(item); // Prints "🎁"
}

To delete items in that scenario, you could use two atomic operations:

This is because before you send your transaction to take away 1 🎁, someone else could have added 1 🎁. So you need to check how many 🎁 there are before you take one away.

In Deno KV Plus, you can just modify the javascript object.

Deno KV Plus is great for KV databases that store complex or nested objects in one bucket- but that doesn't mean you should keep complex data structures in one bucket. It depends on the context.

You know what's awesome? I only had time to write a library- and thoroughly document it- because I wasn't spending time trying to get Deno KV to work! It's a wonder.

πŸ”— Links

More

Social media image previews with Deno Fresh
When you share a link on Twitter, Facebook, LinkedIn or Discord, you can see an image with a preview of the page you're linking to. This…
Find the closest sum to s
This is the solution for a difficult problem in ECS529U Algorithms and Data Structures (Queen Mary University of London), Lab 10. Try to…
Building safe atomic transactions with Deno KV
The Deno Team are building a unique product. It's an all-javascript development experience with _very_ simple deployments: Deno KV is a…
See more articles
DiscordEmailLinkedInGithub