Wir freuen uns, heute bekannt geben zu können, dass Workers KV jetzt allgemein verfügbar und einsatzbereit ist!

Was ist Workers KV?

Workers KV ist ein hochverteilter, konsistenter Schlüsselwertspeicher auf der gesamten globalen Edge von Cloudflare. Er gestattet Ihnen, Milliarden von Schlüsselwertpaaren zu speichern und diese überall auf der Welt mit extrem niedriger Latenz zu lesen. Sie können jetzt komplette Anwendungen mit der Leistung eines statischen CDN-Cache erstellen.

Warum haben wir Workers KV entwickelt?

Workers ist eine Plattform, auf der Sie JavaScript auf Cloudflares globaler Edge mit mehr als 175 Rechenzentren ausführen können. Mit nur wenigen Zeilen Code können Sie HTTP-Anfragen weiterleiten, Antworten ändern oder sogar neue Antworten ohne Ursprungsserver erstellen.

// A Worker that handles a single redirect,
// such a humble beginning...
addEventListener("fetch", event => {
  event.respondWith(handleOneRedirect(event.request))
})

async function handleOneRedirect(request) {
  let url = new URL(request.url)
  let device = request.headers.get("CF-Device-Type")
  // If the device is mobile, add a prefix to the hostname.
  // (eg. example.com becomes mobile.example.com)
  if (device === "mobile") {
    url.hostname = "mobile." + url.hostname
    return Response.redirect(url, 302)
  }
  // Otherwise, send request to the original hostname.
  return await fetch(request)
}

Kunden kamen schnell mit Anwendungsfällen zu uns, die die Möglichkeit erforderten, persistente Daten zu speichern. Auf der Basis unseres obigen Beispiel ist es einfach, eine einzelne Umleitung zu bewältigen, aber was ist, wenn Sie Milliarden davon bewältigen wollen? Sie müssten sie in Ihrem Workers-Skript hartcoden, alles in weniger als 1 MB einpassen und jedes Mal erneut bereitstellen, wenn Sie eine Änderung vornehmen wollen – oje! Deshalb haben wir Workers KV entwickelt.

// A Worker that can handle billions of redirects,
// now that's more like it!
addEventListener("fetch", event => {
  event.respondWith(handleBillionsOfRedirects(event.request))
})

async function handleBillionsOfRedirects(request) {
  let prefix = "/redirect"
  let url = new URL(request.url)
  // Check if the URL is a special redirect.
  // (eg. example.com/redirect/<random-hash>)
  if (url.pathname.startsWith(prefix)) {
    // REDIRECTS is a custom variable that you define,
    // it binds to a Workers KV "namespace." (aka. a storage bucket)
    let redirect = await REDIRECTS.get(url.pathname.replace(prefix, ""))
    if (redirect) {
      url.pathname = redirect
      return Response.redirect(url, 302)
    }
  }
  // Otherwise, send request to the original path.
  return await fetch(request)
}

Mit nur wenigen Änderungen gegenüber unserem früheren Beispiel konnten wir von einer einzigen Umleitung auf Milliarden Umleitungen skalieren – und das ist nur ein Vorgeschmack auf das, was Sie mit Workers KV bauen können.

Wie funktioniert es?

Verteilte Datenspeicher werden häufig mit dem CAP-Theoremmodelliert, das besagt, dass verteilte Systeme nur 2 der folgenden 3 Eigenschaften gleichzeitig erfüllen können:

  • Konsistenz – sind meine Daten überall gleich?
  • Verfügbarkeit – sind meine Daten jederzeit verfügbar?
  • Partitionstoleranz – sind meine Daten widerstandsfähig gegen regionale Ausfälle?

Workers KV garantiert Verfügbarkeit und Partitionstoleranz. Diese Kombination wird als Eventual Consistency (schlussendliche Konsistenz) bezeichnet und stattet Workers KV mit zwei einzigartigen Wettbewerbsvorteilen aus:

  • Die Lesevorgänge sind extrem schnell (im Mittelwert 12 ms), da sie von unserer Caching-Technologie gestützt werden.
  • Die Daten sind in mehr als 175 Edge-Rechenzentren verfügbar und widerstehen regionalen Ausfällen.

Trotzdem gibt es Kompromisse bei der Eventual Consistency. Wenn zwei Clients gleichzeitig unterschiedliche Werte in denselben Schlüssel schreiben, „gewinnt“ schlussendlich der letzte Client, der geschrieben hat, und sein Wert wird global konsistent. Das bedeutet auch, dass, wenn ein Client in einen Schlüssel schreibt und derselbe Client denselben Schlüssel liest, die Werte für einen kurzen Zeitraum möglicherweise inkonsistent sind.

Um dieses Szenario zu visualisieren, hier ein Beispiel aus dem wirklichen Leben mit drei Freunden:

  • Nehmen wir an, Matthew, Michelle und Lee planen ihr wöchentliches Mittagessen.
  • Matthew beschließt, dass sie Sushi essen werden.
  • Matthew teilt Michelle seine Sushi-Pläne mit und Michelle stimmt zu.
  • Lee, der die Pläne nicht kennt, sagt Michelle, dass sie tatsächlich Pizza essen werden.

Eine Stunde später warten Michelle und Lee in der Pizzeria, während Matthew allein im Sushi-Restaurant sitzt – was ist schief gelaufen? Wir können es der Eventual Consistency ankreiden, denn nachdem Matthew einige Minuten lang gewartet hat, schaut er auf seinen aktualisierten Kalender und findet schlussendlich die neue Wahrheit heraus: dass sie Pizza essen.

Während es im wirklichen Leben Minuten dauern kann, ist Workers KV viel schneller. Es kann die globale Konsistenz in weniger als 60 Sekunden erreichen. Darüber hinauskann ein Worker, wenn er in einen Schlüssel schreibt und dann sofort denselben Schlüssel liest, erwarten, dass die Werte konsistent sind, wenn beide Vorgänge vom gleichen Speicherort stammen.

Wann sollte ich das anwenden?

Jetzt, da Sie die Vorteile und Kompromisse der Verwendung der Eventual Consistency verstehen, bleibt die Frage, wie Sie feststellen können, ob es die richtige Speicherlösung für Ihre Anwendung ist. Einfach formuliert: Wenn Sie globale Verfügbarkeit mit ultraschnellen Lesevorgängen wollen, ist Workers KV genau das Richtige für Sie.

Wenn Ihre Anwendung jedoch häufig in denselben Schlüssel schreibt, gibt es eine zusätzliche Überlegung. Wir nennen es „die Matthew-Frage“: Macht es Ihnen etwas aus, wenn die Matthews dieser Welt gelegentlich ins falsche Restaurant gehen?

Sie können sich Anwendungsfälle (wie unser Worker-Umleitungsbeispiel) vorstellen, bei denen es keinen wesentlichen Unterschied macht. Aber Sie den Kontostand eines Benutzers verfolgen wollen, möchten Sie nicht, dass die Möglichkeit des gleichzeitigen Vorhandenseins von zwei verschiedenen Salden besteht, da der Benutzer etwas mit Geld kaufen könnte, das er bereits ausgegeben hat.

Was kann ich damit bauen?

Hier sind einige Beispiele für Anwendungen, die mit KV erstellt wurden:

  • Massenumleitungen – Bewältigung von Milliarden von HTTP-Umleitungen.
  • Benutzerauthentifizierung – Überprüfung von Benutzeranfragen an Ihre API.
  • Übersetzungsschlüssel – Dynamische Lokalisierung Ihrer Webseiten.
  • Konfigurationsdaten – Verwaltung des Zugriffs auf Ihren Ursprungsserver.
  • Step-Funktionen – Synchronisierung von Statusdaten zwischen den Funktionen mehrerer APIs.
  • Edge-Dateispeicher – Hosten von großen Mengen kleiner Dateien.

Wir haben mehrere dieser Anwendungsfälle in unserem vorherigen Blogbeitrag hervorgehoben. Wir haben auch einige ausführlichere Codedurchläufe veröffentlicht, einschließlich eines kürzlich erschienenen Blogbeitrags zum Erstellen einer Online-To-Do-Liste mit Workers KV.

Was ist neu seit der Beta-Version?

Der bei weitem häufigste Wunsch war, das Schreiben von Daten in Workers KV zu vereinfachen. Deshalb veröffentlichen wir drei neue Möglichkeiten zur weiteren Verbesserung dieser Erfahrung:

1. Bulk-Schreiben

Beim Importieren Ihrer vorhandenen Daten in Workers KV wollen Sie sich nicht dem Senden einer HTTP-Anfrage für jedes Schlüsselwertpaar herumärgern müssen. Aus diesem Grund haben wir der Cloudflare-API einen Bulk-Endpunkt hinzugefügt. Jetzt können Sie bis zu 10.000 Paare (bis zu 100 MB Daten) in einer einzigen PUT-Anfrage hochladen.

curl "https://api.cloudflare.com/client/v4/accounts/ \
     $ACCOUNT_ID/storage/kv/namespaces/$NAMESPACE_ID/bulk" \
  -X PUT \
  -H "X-Auth-Key: $CLOUDFLARE_AUTH_KEY" \
  -H "X-Auth-Email: $CLOUDFLARE_AUTH_EMAIL" \
  -d '[
    {"key": "built_by",    value: "kyle, alex, charlie, andrew, and brett"},
    {"key": "reviewed_by", value: "joaquin"},
    {"key": "approved_by", value: "steve"}
  ]'

Lassen Sie uns eine Beispielanwendung durchgehen: Sie wollen die Übersetzung Ihrer Website zu Workers auslagern. Da Sie die Übersetzungsschlüssel häufig lesen, aber nur gelegentlich aktualisieren, funktioniert diese Anwendung mit dem Modell der Eventual Consistency von Workers KV gut.

In diesem Beispiel haken wir uns in Crowdin, eine beliebte Plattform zum Verwalten von Übersetzungsdaten, ein. Dieser Worker reagiert auf einen /translate-Endpunkt, lädt alle Übersetzungsschlüssel herunter und schreibt sie als Bulk in Workers KV, damit Sie sie später auf unserer Edge lesen können:

addEventListener("fetch", event => {
  if (event.request.url.pathname === "/translate") {
    event.respondWith(uploadTranslations())
  }
})

async function uploadTranslations() {
  // Ask crowdin for all of our translations.
  var response = await fetch(
    "https://api.crowdin.com/api/project" +
    "/:ci_project_id/download/all.zip?key=:ci_secret_key")
  // If crowdin is responding, parse the response into
  // a single json with all of our translations.
  if (response.ok) {
    var translations = await zipToJson(response)
    return await bulkWrite(translations)
  }
  // Return the errored response from crowdin.
  return response
}

async function bulkWrite(keyValuePairs) {
  return fetch(
    "https://api.cloudflare.com/client/v4/accounts" +
    "/:cf_account_id/storage/kv/namespaces/:cf_namespace_id/bulk",
    {
      method: "PUT",
      headers: {
        "Content-Type": "application/json",
        "X-Auth-Key": ":cf_auth_key",
        "X-Auth-Email": ":cf_email"
      },
      body: JSON.stringify(keyValuePairs)
    }
  )
}

async function zipToJson(response) {
  // ... omitted for brevity ...
  // (eg. https://stuk.github.io/jszip)
  return [
    {key: "hello.EN", value: "Hello World"},
    {key: "hello.ES", value: "Hola Mundo"}
  ]
}

Wenn Sie nun eine Seite übersetzen wollen, müssen Sie nur noch aus Workers KV auslesen:

async function translate(keys, lang) {
  // You bind your translations namespace to the TRANSLATIONS variable.
  return Promise.all(keys.map(key => TRANSLATIONS.get(key + "." + lang)))
}

2. Ablaufende Schlüssel

Standardmäßig sind Schlüsselwertpaare, die in Workers KV gespeichert werden, dauerhaft. Manchmal wollen Sie jedoch, dass Ihre Daten nach einer bestimmten Zeit automatisch gelöscht werden. Aus diesem Grund führen wir für Schreibvorgänge die Optionen expiration undexpiration Ttl ein.

// Key expires 60 seconds from now.
NAMESPACE.put("myKey", "myValue", {expirationTtl: 60})

// Key expires if the UNIX epoch is in the past.
NAMESPACE.put("myKey", "myValue", {expiration: 1247788800})

# You can also set keys to expire from the Cloudflare API.
curl "https://api.cloudflare.com/client/v4/accounts/ \
     $ACCOUNT_ID/storage/kv/namespaces/$NAMESPACE_ID/ \
     values/$KEY?expiration_ttl=$EXPIRATION_IN_SECONDS"
  -X PUT \
  -H "X-Auth-Key: $CLOUDFLARE_AUTH_KEY" \
  -H "X-Auth-Email: $CLOUDFLARE_AUTH_EMAIL" \
  -d "$VALUE"

Angenommen, Sie wollen Benutzer blockieren, die von Ihrer Website als ungeeignet gekennzeichnet wurden, aber nur für eine Woche. Mit einem ablaufenden Schlüssel können Sie die Ablaufzeit festlegen und müssen sich nicht um ein späteres Löschen kümmern.

In diesem Beispiel gehen wir davon aus, dass Benutzer- und IP-Adressen identisch sind. Wenn Ihre Anwendung über eine Authentifizierung verfügt, können Sie Zugriffstoken als Schlüsselkennzeichen verwenden.

addEventListener("fetch", event => {
  var url = new URL(event.request.url)
  // An internal API that blocks a new user IP.
  // (eg. example.com/block/1.2.3.4)
  if (url.pathname.startsWith("/block")) {
    var ip = url.pathname.split("/").pop()
    event.respondWith(blockIp(ip))
  } else {
    // Other requests check if the IP is blocked.
   event.respondWith(handleRequest(event.request))
  }
})

async function blockIp(ip) {
  // Values are allowed to be empty in KV,
  // we don't need to store any extra information anyway.
  await BLOCKED.put(ip, "", {expirationTtl: 60*60*24*7})
  return new Response("ok")
}

async function handleRequest(request) {
  var ip = request.headers.get("CF-Connecting-IP")
  if (ip) {
    var blocked = await BLOCKED.get(ip)
    // If we detect an IP and its blocked, respond with a 403 error.
    if (blocked) {
      return new Response({status: 403, statusText: "You are blocked!"})
    }
  }
  // Otherwise, passthrough the original request.
  return fetch(request)
}

3. Größere Werte

Wir haben unsere Größenbeschränkung für Werte von 64 kB auf 2 MB erhöht. Das ist sehr nützlich, wenn Sie pufferbasierte oder Dateidaten in Workers KV speichern müssen.

Betrachten Sie folgendes Szenario: Sie wollen Ihren Benutzern das Hochladen ihres Lieblings-GIF für ihr Profil ermöglichen, ohne dass Sie diese GIFs als Binärdateien in Ihrer Datenbank speichern oder noch ein Speicher-Bucket in der Cloud verwalten müssen.

Workers KV passt hervorragend für diesen Anwendungsfall! Sie können einen Workers KV-Namepace für die GIFs Ihrer Benutzer erstellen, der schnell und zuverlässig ist, wo auch immer sich Ihre Kunden befinden.

In diesem Beispiel laden Benutzer einen Link zu ihrem bevorzugten GIF hoch, dann lädt ein Worker ihn herunter und speichert ihn in Workers KV.

addEventListener("fetch", event => {
  var url = event.request.url
  var arg = request.url.split("/").pop()
  // User sends a URI encoded link to the GIF they wish to upload.
  // (eg. example.com/api/upload_gif/<encoded-uri>)
  if (url.pathname.startsWith("/api/upload_gif")) {
    event.respondWith(uploadGif(arg))
    // Profile contains link to view the GIF.
    // (eg. example.com/api/view_gif/<username>)
  } else if (url.pathname.startsWith("/api/view_gif")) {
    event.respondWith(getGif(arg))
  }
})

async function uploadGif(url) {
  // Fetch the GIF from the Internet.
  var gif = await fetch(decodeURIComponent(url))
  var buffer = await gif.arrayBuffer()
  // Upload the GIF as a buffer to Workers KV.
  await GIFS.put(user.name, buffer)
  return gif
}

async function getGif(username) {
  var gif = await GIFS.get(username, "arrayBuffer")
  // If the user has set one, respond with the GIF.
  if (gif) {
    return new Response(gif, {headers: {"Content-Type": "image/gif"}})
  } else {
    return new Response({status: 404, statusText: "User has no GIF!"})
  }
}

Abschließend möchten wir uns noch bei allen Beta-Kunden bedanken. Es war Ihr wertvolles Feedback, das uns zur Entwicklung dieser Änderungen von Workers KV veranlasst hat. Bleiben Sie in Kontakt mit uns. Wir sind immer auf der Suche nach neuen Möglichkeiten und wir freuen uns, von Ihnen zu hören!

Preis

Wir sind auch in der Lage, unsere GA-Preise bekannt zu geben. Wenn Sie einer unserer Enterprise-Kunden sind, bleiben Ihre Preise offensichtlich unverändert.

  • 0,50 USD / GB gespeicherte Daten, 1 GB enthalten
  • 0,50 USD / Millionen Lesevorgänge, 10 Millionen enthalten
  • 5 USD / Millionen Schreib-, Auflist- und Löschvorgänge, 1 Million enthalten

Während der Beta-Phase haben wir erfahren, dass Kunden nicht nur Werte auf unserer Edge lesen, sondern auch Werte von unserer Edge schreiben wollen. Da die Nachfrage nach diesen teureren Edge-Vorgängen groß ist, haben wir damit begonnen, Nicht-Lese-Vorgänge pro Monat abzurechnen.

Beschränkungen

Wie bereits erwähnt, haben wir unsere Größenbeschränkung von 64 kB auf 2 MB erhöht. Wir haben auch unsere Obergrenze für die Anzahl der Schlüssel pro Namespace aufgehoben – sie ist jetzt unbegrenzt. Hier sind unsere GA-Beschränkungen:

  • Bis zu 20 Namespaces pro Konto, jeder mit unbegrenzt vielen Schlüsseln
  • Schlüssel bis zu 512 Bytes und Werte bis zu 2 MB
  • Unbegrenzt viele Schreibvorgänge pro Sekunde für verschiedene Schlüssel
  • Ein Schreibvorgang pro Sekunde für denselben Schlüssel
  • Unbegrenzt viele Lesevorgänge pro Sekunde pro Schlüssel

Probieren Sie es jetzt aus!

Workers KV ist jetzt für alle Kunden zugänglich und Sie können noch heute in Ihrem Cloudflare-Dashboard über die Registerkarte „Workers“ mit der Verwendung beginnen. Sie können auch unsere aktualisierte Dokumentation einsehen.

Wir sind sehr gespannt, was Sie mit Workers KV bauen werden!

Serverlos Workers Workers KV JavaScript Bash