-
Notifications
You must be signed in to change notification settings - Fork 118
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #29 from denoland/kv_content
quick start changes
- Loading branch information
Showing
8 changed files
with
272 additions
and
142 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,165 +1,234 @@ | ||
# About Deno KV | ||
# Deno KV Quick Start | ||
|
||
Since version 1.32, Deno has a built in key-value store that durably persists | ||
data on disk, allowing for data storage and access across service and system | ||
restarts. | ||
**Deno KV** is a | ||
[key-value database](https://en.wikipedia.org/wiki/Key%E2%80%93value_database) | ||
built directly into the Deno runtime, available in the | ||
[`Deno.Kv` namespace](https://deno.land/api?unstable&s=Deno.Kv). It can be used | ||
for many kinds of data storage use cases, but excels at storing simple data | ||
structures that benefit from very fast reads and writes. Deno KV is available in | ||
the Deno CLI and on [Deno Deploy](./on_deploy.md). | ||
|
||
The key-value store is designed to be fast and easy to use. Keys are sequences | ||
(arrays) of JavaScript types like `string`, `number`, `bigint`, `boolean`, and | ||
`Uint8Array`. Values are arbitrary JavaScript primitives, objects, and arrays. | ||
:::caution Deno KV is currently in beta | ||
|
||
The store supports seven different operations that can be composed together to | ||
support many use-cases and enable persistence for most common patterns in modern | ||
web applications. Atomic operations are available that allow grouping of any | ||
number of modification operations into a single atomic transaction. | ||
Deno KV is currently **experimental** and **subject to change**. While we do our | ||
best to ensure data durability, data loss is possible, especially around Deno | ||
updates. | ||
|
||
All data in the KV store is versioned, which allows atomic operations to be | ||
conditional on versions in storage matching the value that user code expected. | ||
This enables optimistic locking, enabling virtual asynchronous transactions. | ||
Excuting Deno programs that use KV currently requires the `--unstable` flag, as | ||
below: | ||
|
||
All writes to the KV store are strongly consistent and immediately durably | ||
persisted. Reads are strongly consistent by default, but alternative consistency | ||
modes are available to enable different performance tradeoffs. | ||
```sh | ||
deno run -A --unstable my_kv_code.ts | ||
``` | ||
|
||
> ⚠️ Deno KV is currently **experimental** and **subject to change**. While we do | ||
> our best to ensure data durability, data loss is possible, especially around | ||
> Deno updates. We recommend that you backup your data regularly and consider | ||
> storing data in a secondary store for the time being. | ||
::: | ||
|
||
## Getting started | ||
Let's walk through the key features of Deno KV. | ||
|
||
> ⚠️ Because Deno KV is currently **experimental** and **subject to change**, it | ||
> is only available when running with `--unstable` flag in Deno CLI. | ||
## Opening a database | ||
|
||
All operations on the key-value store are performed via the `Deno.Kv` API. | ||
In your Deno program, you can get a reference to a KV database using | ||
[`Deno.openKv()`](https://deno.land/api?unstable=&s=Deno.openKv). You may pass | ||
in an optional file system path to where you'd like to store your database, | ||
otherwise one will be created for you based on the current working directory of | ||
your script. | ||
|
||
A database can be opened using the `Deno.openKv()` function. This function | ||
optionally takes a database path on disk as the first argument. If no path is | ||
specified, the database is persisted in a global directory, bound to the script | ||
that `Deno.openKv()` was called from. Future invocations of the same script will | ||
use the same database. | ||
```ts | ||
const kv = await Deno.openKv(); | ||
``` | ||
|
||
Operations can be called on the `Deno.Kv`. The three primary operations on the | ||
database are `get`, `set`, and `delete`. These allow reading, writing, and | ||
deleting individual keys. | ||
## Creating, updating, and reading a key-value pair | ||
|
||
```tsx | ||
// Open the default database for the script. | ||
Data in Deno KV is stored as key-value pairs, much like properties of a | ||
JavaScript object literal or a | ||
[Map](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Map). | ||
[Keys](./key_space.md) are represented as an array of JavaScript types, like | ||
`string`, `number`, `bigint`, or `boolean`. Values can be arbitrary JavaScript | ||
objects. In this example, we create a key-value pair representing a user's UI | ||
preferences, and save it with | ||
[`kv.set()`](https://deno.land/api?s=Deno.Kv&unstable=&p=prototype.set). | ||
|
||
```ts | ||
const kv = await Deno.openKv(); | ||
|
||
// Persist an object at the users/alice key. | ||
await kv.set(["users", "alice"], { name: "Alice" }); | ||
const prefs = { | ||
username: "ada", | ||
theme: "dark", | ||
language: "en-US", | ||
}; | ||
|
||
// Read back this key. | ||
const res = await kv.get(["users", "alice"]); | ||
console.log(res.key); // [ "users", "alice" ] | ||
console.log(res.value); // { name: "Alice" } | ||
const result = await kv.set(["preferences", "ada"], prefs); | ||
``` | ||
|
||
// Delete the key. | ||
await kv.delete(["users", "alice"]); | ||
Once a key-value pair is set, you can read it from the database with | ||
[`kv.get()`](https://deno.land/api?s=Deno.Kv&unstable=&p=prototype.get): | ||
|
||
// Reading back the key now returns null. | ||
const res2 = await kv.get(["users", "alice"]); | ||
console.log(res2.key); // [ "users", "alice" ] | ||
console.log(res2.value); // null | ||
```ts | ||
const entry = await kv.get(["preferences", "ada"]); | ||
console.log(savedPrefs.key); | ||
console.log(savedPrefs.value); | ||
console.log(savedPrefs.versionstamp); | ||
``` | ||
|
||
The `list` operation can be used to list out all keys matching a specific | ||
selector. In the below example all keys starting with some prefix are selected. | ||
Both `get` and `list` [operations](./operations.md) return a | ||
[KvEntry](https://deno.land/api?s=Deno.KvEntry&unstable=) object with the | ||
following properties: | ||
|
||
- `key` - the array key you used to set the value | ||
- `value` - the JavaScript object you set for this key | ||
- `versionstamp` - a generated value used to determine if a key has been | ||
updated. | ||
|
||
The `set` operation is also used to update objects that already exist for a | ||
given key. When a key's value is updated, its `versionstamp` will change to a | ||
new generated value. | ||
|
||
## Listing several key-value pairs | ||
|
||
To get values for a finite number of keys, you may use | ||
[`kv.getMany()`](https://deno.land/api?s=Deno.Kv&unstable=&p=prototype.getMany). | ||
Pass in several keys as arguments, and you'll receive an array of values for | ||
each key. Note that **values and versionstamps can be `null`** if no value | ||
exists for the given key(s). | ||
|
||
```tsx,ignore | ||
await kv.set(["users", "alice"], { birthday: "January 1, 1990" }); | ||
await kv.set(["users", "sam"], { birthday: "February 14, 1985" }); | ||
await kv.set(["users", "taylor"], { birthday: "December 25, 1970" }); | ||
```ts | ||
const kv = await Deno.openKv(); | ||
const result = await db.getMany([ | ||
["preferences", "ada"], | ||
["preferences", "grace"], | ||
]); | ||
result[0].key; // ["preferences", "ada"] | ||
result[0].value; // { ... } | ||
result[0].versionstamp; // "00000000000000010000" | ||
result[1].key; // ["preferences", "grace"] | ||
result[1].value; // null | ||
result[1].versionstamp; // null | ||
``` | ||
|
||
Often, it is useful to retrieve a list of key-value pairs from all keys that | ||
share a given prefix. This type of operation is possible using | ||
[`kv.list()`](https://deno.land/api?s=Deno.Kv&unstable=&p=prototype.list). In | ||
this example, we get a list of key-value pairs that share the `"preferences"` | ||
prefix. | ||
|
||
// List out all entries with keys starting with `["users"]` | ||
for await (const entry of kv.list({ prefix: ["users"] })) { | ||
console.log(entry.key); | ||
console.log(entry.value); | ||
```ts | ||
const kv = await Deno.openKv(); | ||
const entries = db.list({ prefix: ["preferences"] }); | ||
for await (const entry of entries) { | ||
console.log(entry.key); // ["preferences", "ada"] | ||
console.log(entry.value); // { ... } | ||
console.log(entry.versionstamp); // "00000000000000010000" | ||
} | ||
``` | ||
|
||
> Note: in addition to prefix selectors, range selectors, and constrained prefix | ||
> selectors are also available. | ||
Returned keys are ordered lexicographically based on the next component of the | ||
key after the prefix. So KV pairs with these keys: | ||
|
||
- `["preferences", "ada"]` | ||
- `["preferences", "bob"]` | ||
- `["preferences", "cassie"]` | ||
|
||
In addition to individual `get`, `set`, and `delete` operations, the key-value | ||
store supports `atomic` operations that allow multiple modifications to take | ||
place at once, optionally conditional on the existing data in the store. | ||
Will be returned in that order by `kv.list()`. | ||
|
||
In the below example, we insert a new user only if it does not yet exist by | ||
performing an atomic operation that has a check that there is no existing value | ||
for the given key: | ||
Read operations can either be performed in | ||
[**strong or eventual consistency mode**](./operations.md). Strong consistency | ||
mode guarantees that the read operation will return the most recently written | ||
value. Eventual consistency mode may return a stale value, but is faster. By | ||
contrast, writes are always performed in strong consistency mode. | ||
|
||
## Deleting key-value pairs | ||
|
||
You can delete a key from the database using | ||
[`kv.delete()`](https://deno.land/api?s=Deno.Kv&unstable=&p=prototype.delete). | ||
No action is taken if no value is found for the given key. | ||
|
||
```ts | ||
const kv = await Deno.openKv(); | ||
await db.delete(["preferences", "alan"]); | ||
``` | ||
|
||
## Atomic transactions | ||
|
||
Deno KV is capable of executing [atomic transactions](./transactions.md), which | ||
enables you to conditionally execute one or many data manipulation operations at | ||
once. In the following example, we create a new preferences object only if it | ||
hasn't been created already. | ||
|
||
```ts | ||
const kv = await Deno.openKv(); | ||
|
||
const key = ["preferences", "alan"]; | ||
const value = { | ||
username: "alan", | ||
theme: "light", | ||
language: "en-GB", | ||
}; | ||
|
||
```tsx,ignore | ||
const key = ["users", "alice"]; | ||
const value = { birthday: "January 1, 1990" }; | ||
const res = await kv.atomic() | ||
.check({ key, versionstamp: null }) // `null` versionstamps mean 'no value' | ||
.set(key, value) | ||
.commit(); | ||
if (res.ok) { | ||
console.log("User did not yet exist. Inserted!"); | ||
console.log("Preferences did not yet exist. Inserted!"); | ||
} else { | ||
console.log("User already exists."); | ||
console.error("Preferences already exist."); | ||
} | ||
``` | ||
|
||
## Reference | ||
|
||
- [API Reference](https://deno.land/api?unstable&s=Deno.Kv) | ||
- [Key Space](./key_space.md) | ||
- [Operations](./operations.md) | ||
|
||
## Patterns | ||
|
||
- [Transactions](./transactions.md) | ||
- [Secondary Indexes](./secondary_indexes.md) | ||
Learn more about transactions in Deno KV [here](./transactions.md). | ||
|
||
<!-- | ||
## Improve querying with secondary indexes | ||
|
||
## Patterns | ||
- Real-time data (TODO) | ||
- Counters (TODO) | ||
[Secondary indexes](./secondary_indexes.md) store the same data by multiple | ||
keys, allowing for simpler quries of the data you need. Let's say that we need | ||
to be able to access user preferences by both username AND email. To enable | ||
this, you could provide a function that wraps the logic to save the preferences | ||
to create two indexes. | ||
|
||
### Pagination | ||
- How to use cursors | ||
- Size limits on cursors | ||
### Real-time data | ||
- BroadcastChannel | ||
- Versionstamp can be compared for staleness | ||
```ts | ||
const kv = await Deno.openKv(); | ||
|
||
### Counters | ||
async function savePreferences(prefs) { | ||
const key = ["preferences", prefs.username]; | ||
|
||
- Using KvU64 to count things | ||
// Set the primary key | ||
const r = await kv.set(key, prefs); | ||
|
||
## Reference | ||
// Set the secondary key's value to be the primary key | ||
await kv.set(["preferencesByEmail", prefs.email], key); | ||
|
||
### Consistency | ||
return r; | ||
} | ||
|
||
- What is eventual vs strong consistency | ||
- When to use which | ||
async function getByUsername(username) { | ||
// Use as before... | ||
const r = await kv.get(["preferences", username]); | ||
return r; | ||
} | ||
|
||
### Limits | ||
async function getByEmail(email) { | ||
// Look up the key by email, then second lookup for actual data | ||
const r1 = await kv.get(["preferencesByEmail", email]); | ||
const r2 = await kv.get(r1.value); | ||
return r2; | ||
} | ||
``` | ||
|
||
- Key size limits | ||
- Value size limits | ||
- Transaction size limits | ||
- Throughput limits | ||
- Database size limits on Deploy | ||
Learn more about [secondary indexes in the manual here](./secondary_indexes.md). | ||
|
||
### Performance | ||
## Production usage | ||
|
||
- Latency (consistency) | ||
- Max throughput per key | ||
Deno KV is available for use in live applications on | ||
[Deno Deploy](./on_deploy.md). In production, Deno KV is backed by | ||
[FoundationDB](https://www.foundationdb.org/), the open source key-value store | ||
created by Apple. | ||
|
||
### Backups | ||
**No additional configuration is necessary** to run your Deno programs that use | ||
KV on Deploy - a new Deploy database will be provisioned for you when required | ||
by your code. Learn more about Deno KV on Deno Deploy [here](./on_deploy.md). | ||
|
||
- For CLI: | ||
- uses sqlite | ||
- where is this stored | ||
- how to restore | ||
## Next steps | ||
|
||
--> | ||
At this point, you're just beginning to scratch the surface with Deno KV. Be | ||
sure to check out our guide on the [Deno KV key space](./key_space.md), and a | ||
collection of [tutorials and example applications](../tutorials/index.md) here. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.