Skip to content

Implement synchronous version of DO KV storage API.#4895

Merged
kentonv merged 4 commits intomainfrom
kenton/sync-kv
Sep 3, 2025
Merged

Implement synchronous version of DO KV storage API.#4895
kentonv merged 4 commits intomainfrom
kenton/sync-kv

Conversation

@kentonv
Copy link
Copy Markdown
Member

@kentonv kentonv commented Aug 25, 2025

This adds ctx.storage.kv, which has methods get(), put(), list(), and delete(). All four methods have the same signatures as the same-named methods of ctx.storage except:

  1. They return synchronously. No promises.
  2. They require SQLite-backed DOs.
  3. list() returns an iterable rather than a map, so you can process results as they are read and you can cancel the loop early.
  4. The batch versions of get/put/delete are not supported, since the implementation just calls the non-batch versions in a loop anyway. You might as well loop from JavaScript.
  5. They don't have the allowConcurrency nor noCache options since those don't make sense for SQLite. (Nothing stops you from passing these options anyway; they will be ignored.)
  6. The allowUnconfirmed option is also absent, not because it doesn't make sense, but becaues we haven't actually implemented it for SQLite-backed DOs in general. Maybe we will eventually.
  7. Obscure: If you get() multiple keys at once, the returned map now iterates in the order in which the keys were specified, rather than in alphabetical order. The old interface's alphabetical ordering was a historical quirk that required an extra sort operation. It had been maintained for backwards-compatibility, but probably nobody really cares about it, so the new interface takes the opportunity to skip the sort.

The name storage.kv nicely parallels storage.sql while avoiding any backwards-compatibility concerns. In the long term we should "deprecate" (but forever support) the old async methods.

@kentonv kentonv requested a review from joshthoward August 25, 2025 23:40
@kentonv kentonv requested review from a team as code owners August 25, 2025 23:40
@kentonv kentonv force-pushed the kenton/sync-kv branch 3 times, most recently from 47516b5 to c724567 Compare August 26, 2025 14:09
@kentonv
Copy link
Copy Markdown
Member Author

kentonv commented Aug 27, 2025

Hmm, on further thought I think I want to take the opportunity to change list() to return an iterator instead of a map.

It would be more efficient, particularly when you stop iterating early.

With the old storage system, we didn't really have a way to cancel list operations early, so returning a Map made more sense.

@kentonv
Copy link
Copy Markdown
Member Author

kentonv commented Sep 1, 2025

Update:

  • list() now returns an iterable cursor. To avoid leaking SQLite statements, the cursor is invalidated on the next call to list().
  • Since this means we aren't maintaining perfect compatibility anyway, I removed the batch versions of get/put/delete, as the implementation just called the non-batch versions in a loop anyway. Better to run the loop from JS.

@github-actions
Copy link
Copy Markdown

github-actions bot commented Sep 1, 2025

The generated output of @cloudflare/workers-types matches the snapshot in types/generated-snapshot 🎉

Copy link
Copy Markdown
Contributor

@joshthoward joshthoward left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A few open questions, but nothing major.

This adds `ctx.storage.kv`, which has methods `get()`, `put()`, `list()`, and `delete()`. All four methods have the same signatures as the same-named methods of `ctx.storage` except:
1. They return synchronously. No promises.
2. They require SQLite-backed DOs.
3. They don't have the `allowConcurrency` nor `noCache` options since those don't make sense for SQLite. (Nothing stops you from passing these options anyway; they will be ignored.)
4. The `allowUnconfirmed` option is also absent, not because it doesn't make sense, but becaues we haven't actually implemented it for SQLite-backed DOs in general. Maybe we will eventually.
5. Obscure: If you `get()` multiple keys at once, the returned map now iterates in the order in which the keys were specified, rather than in alphabetical order. The old interface's alphabetical ordering was a historical quirk that required an extra sort operation. It had been maintained for backwards-compatibility, but probably nobody really cares about it, so the new interface takes the opportunity to skip the sort.

The name `storage.kv` nicely parallels `storage.sql` while avoiding any backwards-compatibility concerns. In the long term we should "deprecate" (but forever support) the old async methods.
This makes it more memory-efficient and permits the application to cancel the list early if it wants.
Since this code is no longer part of a template, we can move it to the .c++ file. This is a pure cut/paste, no changes except removing the `inline` keyword.
…rface.

The batch versions of these functions just call the single versions in a loop, which is pointless; you might as well perform the loop on the JS side.

Arguably keeping the batch versions could be nice for compatibility, but since list() has changed to be iterable, we aren't really maintaining perfect compatibility anyway. If we decide we do want compatibility later, we can revert this commit.
@kentonv kentonv merged commit eb0de09 into main Sep 3, 2025
21 checks passed
@kentonv kentonv deleted the kenton/sync-kv branch September 3, 2025 17:27
@lmaccherone
Copy link
Copy Markdown

How long before we can use this a released version in Cloudflare, wrangler dev, or vitest-pool-workers?

@kentonv
Copy link
Copy Markdown
Member Author

kentonv commented Sep 8, 2025

@lmaccherone It should work immediately if you override workerd to the latest release, e.g. put this in your package.json:

    "overrides": {
      "workerd": ">=1.20250906.0"
    }

Otherwise, miniflare is released once a week and this change didn't make it into last week's release, but should land in this week's (on ~Thursday).

The API went live in production last Friday.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants