denoland / denoland/deploy_feedback

[KV Feedback]: Consistency with interdependent `get`s

Open
#408 1 comment 0 reactions 0 assignees View on GitHub
kv
Dominant language
No language data
Stars
79
Forks
5
PR merge metrics
No merged PRs in 30d

Description

### 🔍

- [x] Did you search for existing issues?

### Type of feedback

Documentation improvement

### Description

What's the best way to maintain consistency when `get`ing two interdependent values?

For example, let's store a book and its author.

```js
const db = await Deno.openKv(":memory:");

const authorKey = ["authors", 11];
await db.set(authorKey, { name: "William Writer" });

const bookKey = ["books", 1];
await db.set(bookKey, { title: "Tale of Tales", authorId: 11 });
```

The book stores the id of the author, such that given only the id of a book, we can read the book and its author.

```js
const bookId = 1;

const book = await db.get(["books", bookId]);

const authorId = book.value.authorId;

const author = await db.get(["authors", authorId]);
```

But this isn't atomically consistent. If a concurrent write happens in between the two `get` calls, we might not get what we expected. How can we ensure consistency here?

It seems there are two types of consistency here, "backwards" and "forwards" consistency. A concurrent write in between the two `get` calls could either change the book after we read it, or the author before we read it. Ideally, we want "batch consistency" like with `getMany`, where we get both how they are at a given time.

It seems we can ensure "backwards" consistency using `atomic().check(...)`. This way, if a concurrent write changes the book after we read it but before we read its author, we can abort and retry to get the updated book.

```js
const bookId = 1;

let author;
let book;
let check = { ok: false };

while (!check.ok) {
book = await db.get(["books", bookId]);

const authorId = book.value.authorId;
author = await db.get(["authors", authorId]);

check = await db
.atomic()
.check(author)
.check(book)
.commit();
}

// assuming the loop terminates, we should have consistent `book` and `author` here…
```

How can we ensure "forward" consistency? How can we detect if a concurrent write changes the author after we read the book but before we read its author? Maybe we don't need to. It could be just a very recent ("last minute", or rather millisecond) update to author, similar to if we had updated it yesterday. It would only be an issue, if there would be a second write to book to match the new author inconsistently afterwards. But assuming all writes are done consistently, a change to author without a change to book means it was just to author, and it's fine if it was yesterday or one millisecond ago.

Is this the right way to think about this?

Filing this as "documentation improvement" since I couldn't find this covered anywhere.

### Steps to reproduce (if applicable)

_No response_

### Expected behavior (if applicable)

_No response_

### Possible solution (if applicable)

_No response_

### Additional context

_No response_

Contributor guide

No contributing guide indexed for this repository

Research direction

Start with the Deno KV entry points shown in the report: get, getMany, and atomic().check(). Review the existing documentation and API behavior for interdependent reads, then document the supported consistency model and a clear example of the recommended approach.

Written by the indexing model from the issue text.

Assessment

Tech stack
deno, javascript
Domain
database, documentation
Issue type
Documentation
Difficulty
4/5
Estimated time
3-5 days
Activity status
Stale
Clarity
Needs clarification
Newbie friendliness
28/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.