Blog What we do Support Community
Login Sign up

Building With Workers KV, a Fast Distributed Key-Value Store

by Stephen Pinkerton, Zack Bloom.

Your Workers now have access to a low-latency key-value data store which lives inside our network all around the world!

For those who don’t know, Cloudflare Workers is a new type of compute platform, built on top of our global network of 152+ data centers around the world. It allows you to write serverless code which runs in the fabric of the Internet itself, allowing you to engage with your users faster than other platforms can even get a packet to where your code is running. It’s built on a new architecture which eliminates cold starts and dramatically reduces the memory overhead of keeping your code running when compared to a platform like Amazon Lambda.

As powerful as this is, compute is just one component of what you need to build an application, you also need the ability to store data. We evaluated many of the available open source data stores on the market, but ultimately nothing was designed for a world with quite as many distributed nodes as our network. Instead, we have begun releasing our own vision for distributed storage, beginning today.

The Workers KV is a highly distributed, eventually-consistent, key value store. It will allow you to store up to a billion keys and values, and read them with ultra low latency anywhere in the world. It makes it possible to build entire applications with the performance traditionally associated with static content cached by a CDN.

What can I do with Workers KV?

First and foremost, you can build the same types of applications you build today, but in a more fault tolerant and performant way. Reading values from Workers KV is designed to have the same reliability as reading static files, making it much less likely to become unavailable than a traditional database. It’s designed to have the same performance as reading a file cached within our network, close to your users, giving it the speed of serving a static file as well.

That said, we do have some applications which we commonly see as a good entry point into writing code on the network itself. These use-cases combine a serious need for speed with a clean separation from the legacy components of your application which are stuck in a central location (for now!).

Here are just a few of those examples:

API Gateway and Access Tokens

An API Gateway sits between your visitors and your API. It commonly handles tasks which would be redundant, time consuming, or slow to implement in each and every service in your system. This includes tasks like rate limiting, access token validation, and routing. They work together to deliver only authenticated requests directly to the appropriate components of your system. It’s also the perfect entry point to developing code which runs everywhere. When you use Cloudflare Workers as an API Gateway, your access tokens get validated at the Cloudflare data center closest to the customer before the request is securely forwarded to your origin.

In this example your authentication system will store a token when a user logs in. I'm using curl, but the backend code for your system is more likely to use whatever interface it has for making HTTPS requests. This request stores a blob of JSON identifying this token in a Worker KV namespace $NAMESPACE_ID with a key of $TOKEN_ID:

curl https://api.cloudflare.com/client/v4/accounts/$ACCOUNT_ID/storage/kv/$NAMESPACE_ID/values/$TOKEN_ID \
-X PUT \
-H "X-Auth-Key: $CLOUDFLARE_AUTH_KEY" \
-H "X-Auth-Email: $CLOUDFLARE_AUTH_EMAIL" \
-d '{
  "userId": "bob",
  "expires": "2018-07-11T03:44:12Z"
}'

Your Worker code, which runs on every request, will check if the token the user provides matches one you have stored. A single line of code (TOKEN_STORE.get()) pulls the JSON stored above from the Worker KV

addEventListener('fetch', event => {
 event.respondWith(handleRequest(event.request))
})

async function handleRequest(request) {
 const token = request.headers.get('Authorization')
 if (!token)
   return new Response("An Authorization header is required", {status: 401})

 const tokenInfo = await TOKEN_STORE.get(token, "json")
 if (!tokenInfo)
   return new Response("Invalid token", {status: 403})
 
 if (Date.parse(tokenInfo.expires) < Date.now())
   return new Response(“Token expired”, {status: 403}) 

 request = new Request(request)
 request.headers.set("User-Id", tokenInfo.userId)
 return fetch(request)
}

A secure authentication system which adds virtually no latency at 1/7th the cost of Amazon API Gateway (with so much more flexibility and power)!

Dynamic Data

Traditionally you’ve had to decide between showing a super fast static site to your visitors, or being able to include dynamic data customizing your site for each visitor. That customization could be showing different products based on the customer’s profile, A/B testing site variants, or even including the customer’s current shopping cart and account information. Rather than waiting for this information to be served from a central database, it’s now possible to store it close to every visitor, delivering a custom page as quickly as you could a static resource.

For example, let’s say we have translations of our site stored as JSON in the KV store. We can dynamically insert this translation data as Javascript into the HTML of our site. In this example our site has a block which looks like this:

<html>
 <head>
   <script>
     var TRANSLATION_DATA = TRANSLATION DATA HERE
   </script>
 </head>

 ...
</html>

Our worker replaces that text with the content of our translations. You could alternatively use a Javascript templating engine or parse the content of the page to do something like Server-Side Includes.

addEventListener('fetch', event => {
  event.respondWith(handleRequest(event.request))
})

async function handleRequest(request) {
  const token = request.headers.get('Authorization')


  const translationsPromise = TRANSLATION_DATA.get(country)
  const response = await fetch(request)
  const translations = await translationsPromise

  let newBody = await response.text()
  const ct = response.headers.get('content-type')
  if (ct.startsWith('text/html') && response.status === 200){
    newBody = newBody.replace('TRANSLATION DATA HERE', translations)
  }

  return new Response(newBody, response)
}

Workers is a full programming environment, meaning this is just the beginning. We have customers rendering their entire React App inside Workers, fully bootstrapping the data required to render their site.

Configuration

The Workers KV store creates a powerful way to configure your running Workers without having to redeploy them. You might want to implement feature flags to enable or disable features at will, or to dynamically update the data your code uses to make decisions. Workers deploy in under 30 seconds, but it’s common to have more data than can easily fit in Worker code. For example, we have customers interested in using Workers KV to block messages from lost or stolen IoT devices before they ever reach their origin:

addEventListener('fetch', event => {
 event.respondWith(handleRequest(event.request))
})

async function handleRequest(request) {
 const deviceId = request.headers.get('device-id')
 const bannedEntry = await BANNED_DEVICES.get(deviceId)
 if (bannedEntry !== null){
   return new Response("This device has been banned", {status: 403})
 }

 return fetch(request)
}

Cloud Functions

One thing we’re excited about when we think about the Workers KV store is the potential for building beyond web applications. There are many situations where developers are looking for an easy way to execute code without worrying about provisioning or maintaining infrastructure. One of the most common cases we see is joining systems together.

For example, one of our customers is planning on using Workers KV to connect their Point of Sale system with their Delivery Service’s API:

// The endpoint we have bound this to is polled periodically
addEventListener('fetch', event => {
 event.respondWith(handleRequest(event.request))
})

async function handleRequest(request) {
 const orderResp = await fetch(`https://api.pos-system.com/orders/active`, {
   headers: {
     'Authorization': 'POS_API_KEY'
   }
 })

 const orders = await orderResp.json()

 for (var i=0; i < orders.length; i++){
   let order = orders[i]

   const syncedData = await SYNCED_ORDERS.get(order.id, "json")

   // If the order data is newer than the last data we sent to the delivery company,
   // update it.
   if (syncedData.modifiedAt < order.modifiedAt) {
     await fetch(`https://api.delivery-system.com/orders/${ order.id }`, {
       method: 'POST',
       body: JSON.stringify(order),
       headers: {
         'Content-Type': 'application/json',
         'Authorization': 'DELIVERY_API_KEY'
       }
     })

     await SYNCED_ORDERS.put(order.id, JSON.stringify(order))
   }
 }

 return new Response("👍")
}

Limits and Pricing

Workers KV is launching today in a limited beta. As we get feedback and operational experience we will be relaxing our storage limits and granting access to more users. While we may have more restrictive limits during beta, you can design your applications around the following characteristics:

  • Up to 1 billion keys per namespace
  • Keys of up to 2 kB
  • Values of up to 64 kB
  • Eventually consistent, global consistency within 10 seconds
  • 100k+ reads per second per key
  • Up to one write per second per key

We worked hard to make the pricing of Workers KV easy to understand and affordable for virtually any use case. Your $5 monthly Workers compute minimum includes 1 GB of KV storage and up to 10 million KV reads. If you use less than the 10 million included Worker requests now, you can use KV without paying a single cent more.

Beyond the minimums, Workers KV is billed at $0.50 per GB-month of additional storage and $0.50 per million additional KV reads.

To get beta access sign up here, we can’t wait to see what you build!

Subscribe to the blog for daily updates on all of our announcements.

comments powered by Disqus