The Storage team here at Cloudflare shipped Workers KV, our global, low-latency, key-value store, earlier this year. As people have started using it, we’ve gotten some feature requests, and have shipped some new features in response! In this post, we’ll talk about some of these use cases and how these new features enable them.
New KV APIs
We’ve shipped some new APIs, both via api.cloudflare.com
, as well as inside of a Worker. The first one provides the ability to upload and delete more than one key/value pair at once. Given that Workers KV is great for read-heavy, write-light workloads, a common pattern when getting started with KV is to write a bunch of data via the API, and then read that data from within a Worker. You can now do these bulk uploads without needing a separate API call for every key/value pair. This feature is available via api.cloudflare.com
, but is not yet available from within a Worker.
For example, say we’re using KV to redirect legacy URLs to their new homes. We have a list of URLs to redirect, and where they should redirect to. We can turn this list into JSON that looks like this:
[
{
"key": "/old/post/1",
"value": "/new-post-slug-1"
},
{
"key": "/old/post/2",
"value": "/new-post-slug-2"
}
]
And then POST this JSON to the new bulk endpoint, /storage/kv/namespaces/:namespace_id/bulk
. This will add both key/value pairs to our namespace.
Likewise, if we wanted to drop support for these redirects, we could issue a DELETE that has this body:
[
"/old/post/1",
"/old/post/2"
]
to /storage/kv/namespaces/:namespace_id/bulk
, and we’d delete both key/value pairs in a single call to the API.
The bulk upload API has one more trick up its sleeve: not all data is a string. For example, you may have an image as a value, which is just a bag of bytes. if you need to write some binary data, you’ll have to base64 the value’s contents so that it’s valid JSON. You’ll also need to set one more key:
[
{
"key": "profile-picture",
"value": "aGVsbG8gd29ybGQ=",
"base64": true
}
]
Workers KV will decode the value from base64, and then store the resulting bytes.
Beyond bulk upload and delete, we’ve also given you the ability to list all of the keys you’ve stored in any of your namespaces, from both the API and within a Worker. For example, if you wrote a blog powered by Workers + Workers KV, you might have each blog post stored as a key/value pair in a namespace called “contents”. Most blogs have some sort of “index” page that lists all of the posts that you can read. To create this page, we need to get a listing of all of the keys, since each key corresponds to a given post. We could do this from within a Worker by calling list()
on our namespace binding:
const value = await contents.list()
But what we get back isn’t only a list of keys. The object looks like this:
{
keys: [
{ name: "Title 1” },
{ name: "Title 2” }
],
list_complete: false,
cursor: "6Ck1la0VxJ0djhidm1MdX2FyD"
}
We’ll talk about this “cursor” stuff in a second, but if we wanted to get the list of titles, we’d have to iterate over the keys property, and pull out the names:
const keyNames = value.keys.map(e => e.name)
keyNames
would be an array of strings:
[“Title 1”, “Title 2”, “Title 3”, “Title 4”, “Title 5”]
We could take keyNames
and those titles to build our page.
So what’s up with the list_complete
and cursor
properties? Well, imagine that we’ve been a very prolific blogger, and we’ve now written thousands of posts. The list API is paginated, meaning that it will only return the first thousand keys. To see if there are more pages available, you can check the list_complete
property. If it is false, you can use the cursor to fetch another page of results. The value of cursor
is an opaque token that you pass to another call to list:
const value = await NAMESPACE.list()
const cursor = value.cursor
const next_value = await NAMESPACE.list({"cursor": cursor})
This will give us another page of results, and we can repeat this process until list_complete
is true.
Listing keys has one more trick up its sleeve: you can also return only keys that have a certain prefix. Imagine we want to have a list of posts, but only the posts that were made in October of 2019. While Workers KV is only a key/value store, we can use the prefix functionality to do interesting things by filtering the list. In our original implementation, we had stored the titles of keys only:
Title 1
Title 2
We could change this to include the date in YYYY-MM-DD format, with a colon separating the two:
2019-09-01:Title 1
2019-10-15:Title 2
We can now ask for a list of all posts made in 2019:
const value = await NAMESPACE.list({"prefix": "2019"})
Or a list of all posts made in October of 2019:
const value = await NAMESPACE.list({"prefix": "2019-10"})
These calls will only return keys with the given prefix, which in our case, corresponds to a date. This technique can let you group keys together in interesting ways. We’re looking forward to seeing what you all do with this new functionality!
Relaxing limits
For various reasons, there are a few hard limits with what you can do with Workers KV. We’ve decided to raise some of these limits, which expands what you can do.
The first is the limit of the number of namespaces any account could have. This was previously set at 20, but some of you have made a lot of namespaces! We’ve decided to relax this limit to 100 instead. This means you can create five times the number of namespaces you previously could.
Additionally, we had a two megabyte maximum size for values. We’ve increased the limit for values to ten megabytes. With the release of Workers Sites, folks are keeping things like images inside of Workers KV, and two megabytes felt a bit cramped. While Workers KV is not a great fit for truly large values, ten megabytes gives you the ability to store larger images easily. As an example, a 4k monitor has a native resolution of 4096 x 2160 pixels. If we had an image at this resolution as a lossless PNG, for example, it would be just over five megabytes in size.
KV browser
Finally, you may have noticed that there’s now a KV browser in the dashboard! Needing to type out a cURL command just to see what’s in your namespace was a real pain, and so we’ve given you the ability to check out the contents of your namespaces right on the web. When you look at a namespace, you’ll also see a table of keys and values:
The browser has grown with a bunch of useful features since it initially shipped. You can not only see your keys and values, but also add new ones:
edit existing ones:
...and even upload files!
You can also download them:
As we ship new features in Workers KV, we’ll be expanding the browser to include them too.
Wrangler integration
The Workers Developer Experience team has also been shipping some features related to Workers KV. Specifically, you can fully interact with your namespaces and the key/value pairs inside of them.
For example, my personal website is running on Workers Sites. I have a Wrangler project named “website” to manage it. If I wanted to add another namespace, I could do this:
$ wrangler kv:namespace create new_namespace
Creating namespace with title "website-new_namespace"
Success: WorkersKvNamespace {
id: "<id>",
title: "website-new_namespace",
}
Add the following to your wrangler.toml:
kv-namespaces = [
{ binding = "new_namespace", id = "<id>" }
]
I’ve redacted the namespace IDs here, but Wrangler let me know that the creation was successful, and provided me with the configuration I need to put in my wrangler.toml
. Once I’ve done that, I can add new key/value pairs:
$ wrangler kv:key put "hello" "world" --binding new_namespace
Success
And read it back out again:
> wrangler kv:key get "hello" --binding new_namespace
world
If you’d like to learn more about the design of these features, “How we design features for Wrangler, the Cloudflare Workers CLI” discusses them in depth.
More to come
The Storage team is working hard at improving Workers KV, and we’ll keep shipping new stuff every so often. Our updates will be more regular in the future. If there’s something you’d particularly like to see, please reach out!