From e6fac5cf85adde905f7955ace7c34a4c7d5c018a Mon Sep 17 00:00:00 2001
From: "Jiaxiao Zhou (Mossaka)"
Date: Thu, 30 Nov 2023 15:42:18 -0800
Subject: [PATCH 01/14] feat(*): updated comments and changed signatures of
get, get-many and get-keys
This commit has changed many function signatures to account for the scenario
where the key does not exist. Instead of returning an error for non-existance keys,
the expected behvaior should be returning a none for the client to handle.
The other changes are for providing more clarity to the behavior of the APIs
ine `wasi:keyvalue` interfaces. It explained what it means to have batch operations
without guarantees to atomicity and what are atomic operations like CAS.
It also changed the portability criteria to follow the newest WASI proposal template,
and added a new goal regarding keyvalue operation performance in this proposal.
Signed-off-by: Jiaxiao Zhou (Mossaka)
---
.github/workflows/main.yml | 4 +-
README.md | 321 ++++++-------------------------------
keyvalue-handle-watch.md | 114 ++++++++++---
keyvalue.md | 114 ++++++++++---
wit/atomic.wit | 19 ++-
wit/batch.wit | 72 +++++++--
wit/error.wit | 12 +-
wit/handle-watch.wit | 11 +-
wit/readwrite.wit | 23 ++-
wit/types.wit | 2 +-
wit/world.wit | 14 ++
11 files changed, 357 insertions(+), 349 deletions(-)
diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml
index 6fb0ad1..72454f6 100644
--- a/.github/workflows/main.yml
+++ b/.github/workflows/main.yml
@@ -13,10 +13,10 @@ jobs:
- uses: actions/checkout@v3
- name: ensure `./wit/deps` are in sync
run: |
- curl -Lo 'wit-deps' https://github.com/bytecodealliance/wit-deps/releases/download/v0.3.3/wit-deps-x86_64-unknown-linux-musl
+ curl -Lo 'wit-deps' https://github.com/bytecodealliance/wit-deps/releases/download/v0.3.5/wit-deps-x86_64-unknown-linux-musl
chmod +x wit-deps
./wit-deps lock --check
- uses: WebAssembly/wit-abi-up-to-date@v16
with:
worlds: 'keyvalue keyvalue-handle-watch'
- wit-bindgen: '0.13.0'
\ No newline at end of file
+ wit-bindgen: '0.15.0'
\ No newline at end of file
diff --git a/README.md b/README.md
index 69a0f25..febe46a 100644
--- a/README.md
+++ b/README.md
@@ -12,22 +12,19 @@ Phase 1
- David Justice
- Jiaxiao Zhou
-### Phase 4 Advancement Criteria
+### Portability Criteria
-At least two independent production implementations.
-
-At least two cloud provider implementations.
-
-Implementations available for at least Windows, Linux & MacOS.
+`wasi:keyvalue` must have at least two complete independent implementations demonstrating
+embeddability in a production key-value store context. The implementations must be able to run
+on at least two different operating systems.
A testsuite that passes on the platforms and implementations mentioned above.
## Table of Contents
-
- [WASI Key-Value Store](#wasi-key-value-store)
- [Current Phase](#current-phase)
- [Champions](#champions)
- - [Phase 4 Advancement Criteria](#phase-4-advancement-criteria)
+ - [Portability Criteria](#portability-criteria)
- [Table of Contents](#table-of-contents)
- [Introduction](#introduction)
- [Goals](#goals)
@@ -47,300 +44,76 @@ A testsuite that passes on the platforms and implementations mentioned above.
### Introduction
-WASI Key-Value Store is a WASI API primarily for accessing a key-value datastore. It has functions to:
+This `wasi:keyvalue` proposal defines a collection of [interfaces] for
+interacting with key-value stores. It additionally defines a [world],
+`wasi:keyvalue/keyvalue`, that groups together common interfaces including
-1. retrieve the value stored and associated with a key,
-2. delete a key-value pair, and
-3. create and update a key-value pair.
+1. CRUD operations on single key-value pairs
+2. Atomic operations on single key-value pairs
+3. Batch operations on multiple key-value pairs
The API is designed to hide data-plane differences between different key-value stores. The control-plane behavior/functionality (e.g., including cluster management, data consistency, replication, and sharding) are not specified, and are provider specific.
+[Interfaces]: https://github.com/WebAssembly/component-model/blob/main/design/mvp/WIT.md#wit-interfaces
+[World]: https://github.com/WebAssembly/component-model/blob/main/design/mvp/WIT.md#wit-worlds
+
### Goals
-The primary goal of this API is to allow users to use WASI programs to access key-value stores. It abstracts away the complexity of building stateful services that need to access key-value stores. In other words, it decouples the application code from the specific key-value store APIs. This allows WASI programs to be portable across different key-value store that supports this interface, be it on-prem, in the cloud, or in edge devices.
+The primary goal of this API is to allow WASI programs to access a wide variety of key-value stores. It meant the `wasi:keyvalue` interfaces to be implementable by a wide variety of key-value stores, including but not limited to: in-memory key-value stores, on-disk key-value stores, document databases, relational databases, and either single-node or distributed key-value stores.
+
+The second goal of `wasi:keyvalue` interfaces is to abstract away the network stack, allowing applications that need to access key-value stores to not worry about what network protocol is used to access the key-value store.
-The second goal of this API is to abstract away the network stack, allowing applications that need to access key-value stores to not worry about what network protocol is used to access the key-value store.
+A third design goal of `wasi:keyvalue` interfaces is to allow performance optimizations without compromising the portability of the API. This includes minimizing the number of copies of data, minimizing the number of requests and round-trips, and support for asynchronous operations.
### Non-goals
-- Cover all application requirements for a key-value store. Instead, it focuses on the most common use cases. It allows providers to extend the API to support more use cases.
-- Transactional semantics.
-- Data consistency.
-- Data replication.
-- Data sharding.
-- Cluster management.
-- Monitoring
+- The `wasi:keyvalue` interfaces only focus on the most common use cases for key-value stores. It does not attempt to cover all possible use cases.
+- The `wasi:keyvalue` interfaces do not guarantee data consistency. Data consistency models (eventual, strong, casual etc.) are diverse and are specific to each key-value store's architecture. The implication is that components importing `wasi:keyvalue` interfaces would need to be aware of the data consistency model of the key-value store they are using.
+- The `wasi:keyvalue` interfaces do not handle data replication and sharding. These are control-plane behaviors that are specific to each key-value store's architecture. Replication needs and sharding management are left to the outside of the `wasi:keyvalue` interfaces.
+- No cluster management is provided. Operational aspects like scaling, node management, and cluster health are to be managed separately from the wasi:keyvalue interface `wasi:keyvalue` interfaces.
+- No built-in monitoring. Monitoring of key-value store performance and health should be done using external tools and not be expected as part of the `wasi:keyvalue` interfaces.
### API walk-through
-#### Use case 1
-
-Imagine you have an HTTP handler that needs to persist some data to a key-value store. The handler needs to be able to retrieve, delete, and update the data in the key-value store. The following Rust and Go code shows how you can use the WASI Key-Value Store API to in the handler.
+The proposal can be understood by first reading the comments of `world.wit`, then `readwrite.wit`, `atomic.wit`, `batch.wit`, `caching.wit` and finally `types.wit`
-```rust
-wit_bindgen::generate!("outbound-keyvalue" in "wit/world.wit");
-use types::{ open_bucket, drop_bucket, payload_consume_sync };
-use readwrite::{ get, set, exists };
-{
- let my_bucket = open_bucket("my-bucket")?;
- set(my_bucket, "my-key", "my-value")?;
- if exists(my_bucket, "my-key")? {
- let payload = get(my_bucket, "my-key")?;
- let body: Vec = payload_consume_sync(payload)?;
- let body = String::from_utf8(body)?;
- println!("body: {}", body);
- }
- drop_bucket(my_bucket);
-}
-```
-
-```go
-bucket, err := types.OpenBucket("my-bucket")
-if err != nil {
- panic(err)
-}
-defer types.DropBucket(bucket)
+[`world.wit`](./wit/world.wit)
+[`readwrite.wit`](./wit/readwrite.wit)
+[`atomic.wit`](./wit/atomic.wit)
+[`batch.wit`](./wit/batch.wit)
+[`caching.wit`](./wit/caching.wit)
+[`types.wit`](./wit/types.wit)
-err := readwrite.Set(bucket, "my-key", "my-value")
-if err != nil {
- panic(err)
-}
+### Working with the WIT
-exists, err := readwrite.Exists(bucket, "my-key")
-if exists {
- payload, err := readwrite.Get(bucket, "my-key")
- if err != nil {
- panic(err)
- }
- body, err := types.PayloadConsumeSync(payload)
- if err != nil {
- panic(err)
- }
- fmt.Println("body: ", string(body))
-}
+Bindings can be generated from the `wit` directory via:
```
-
-#### Use case 2
-
-If you want to watch for changes to a key-value store, you can write a wasm component that uses the inbound-keyvalue interface this API provides. The following Rust code shows how you can use the WASI Key-Value Store API to watch for changes to a key-value store.
-
-```rust
-wit_bindgen::generate!("inbound-keyvalue" in "wit/world.wit");
-struct Handler;
-
-impl inbound_keyvalue::HandleWatch for Handler {
- fn on_set(&mut self, bucket: Bucket, key: Key, value: Payload) {
- let payload = get(my_bucket, "my-key").unwrap();
- let body: Vec = payload_consume_sync(payload).unwrap();
- let body = String::from_utf8(body).unwrap();
- println!("bucket: {}, key: {}, value: {}", bucket, key, body);
- }
- fn on_delete(&mut self, bucket: Bucket, key: Key) {
- println!("bucket: {}, key: {}", bucket, key);
- }
-}
-
+wit-bindgen c wit/ --world proxy
```
-
-### Detailed design discussion
-
-```go
-/// A keyvalue interface that provides simple read and write operations.
-interface readwrite {
- /// A keyvalue interface that provides simple read and write operations.
- use types.{bucket, error, incoming-value, key, outgoing-value}
-
- /// Get the value associated with the key in the bucket. It returns a incoming-value
- /// that can be consumed to get the value.
- ///
- /// If the key does not exist in the bucket, it returns an error.
- get: func(bucket: bucket, key: key) -> result
-
- /// Set the value associated with the key in the bucket. If the key already
- /// exists in the bucket, it overwrites the value.
- ///
- /// If the key does not exist in the bucket, it creates a new key-value pair.
- /// If any other error occurs, it returns an error.
- set: func(bucket: bucket, key: key, outgoing-value: outgoing-value) -> result<_, error>
-
- /// Delete the key-value pair associated with the key in the bucket.
- ///
- /// If the key does not exist in the bucket, it returns an error.
- delete: func(bucket: bucket, key: key) -> result<_, error>
-
- /// Check if the key exists in the bucket.
- ///
- /// If the key does not exist in the bucket, it returns an error.
- exists: func(bucket: bucket, key: key) -> result
-}
-/// A keyvalue interface that provides atomic operations.
-interface atomic {
- /// A keyvalue interface that provides atomic operations.
- use types.{bucket, error, key}
-
- /// Atomically increment the value associated with the key in the bucket by the
- /// given delta. It returns the new value.
- ///
- /// If the key does not exist in the bucket, it creates a new key-value pair
- /// with the value set to the given delta.
- ///
- /// If any other error occurs, it returns an error.
- increment: func(bucket: bucket, key: key, delta: u64) -> result
-
- /// Atomically compare and swap the value associated with the key in the bucket.
- /// It returns a boolean indicating if the swap was successful.
- ///
- /// If the key does not exist in the bucket, it returns an error.
- compare-and-swap: func(bucket: bucket, key: key, old: u64, new: u64) -> result
-}
-
-/// A keyvalue interface that provides batch operations.
-interface batch {
- /// A keyvalue interface that provides batch get operations.
- use types.{bucket, error, key, keys, incoming-value, outgoing-value}
-
- /// Get the values associated with the keys in the bucket. It returns a list of
- /// incoming-values that can be consumed to get the values.
- ///
- /// If any of the keys do not exist in the bucket, it returns an error.
- get-many: func(bucket: bucket, keys: keys) -> result, error>
-
- /// Get all the keys in the bucket. It returns a list of keys.
- get-keys: func(bucket: bucket) -> keys
-
- /// Set the values associated with the keys in the bucket. If the key already
- /// exists in the bucket, it overwrites the value.
- ///
- /// If any of the keys do not exist in the bucket, it creates a new key-value pair.
- /// If any other error occurs, it returns an error.
- set-many: func(bucket: bucket, key-values: list>) -> result<_, error>
-
- /// Delete the key-value pairs associated with the keys in the bucket.
- ///
- /// If any of the keys do not exist in the bucket, it skips the key.
- /// If any other error occurs, it returns an error.
- delete-many: func(bucket: bucket, keys: keys) -> result<_, error>
-}
-
-// A generic keyvalue interface for WASI.
-interface types {
- /// A bucket is a collection of key-value pairs. Each key-value pair is stored
- /// as a entry in the bucket, and the bucket itself acts as a collection of all
- /// these entries.
- ///
- /// It is worth noting that the exact terminology for bucket in key-value stores
- /// can very depending on the specific implementation. For example,
- /// 1. Amazon DynamoDB calls a collection of key-value pairs a table
- /// 2. Redis has hashes, sets, and sorted sets as different types of collections
- /// 3. Cassandra calls a collection of key-value pairs a column family
- /// 4. MongoDB calls a collection of key-value pairs a collection
- /// 5. Riak calls a collection of key-value pairs a bucket
- /// 6. Memcached calls a collection of key-value pairs a slab
- /// 7. Azure Cosmos DB calls a collection of key-value pairs a container
- ///
- /// In this interface, we use the term `bucket` to refer to a collection of key-value
- // Soon: switch to `resource bucket { ... }`
- type bucket = u32
- drop-bucket: func(bucket: bucket)
- open-bucket: func(name: string) -> result
-
- /// A key is a unique identifier for a value in a bucket. The key is used to
- /// retrieve the value from the bucket.
- type key = string
-
- /// A list of keys
- type keys = list
-
- use wasi:io/streams.{input-stream, output-stream}
- use wasi-cloud-error.{ error }
- /// A value is the data stored in a key-value pair. The value can be of any type
- /// that can be represented in a byte array. It provides a way to write the value
- /// to the output-stream defined in the `wasi-io` interface.
- // Soon: switch to `resource value { ... }`
- type outgoing-value = u32
- drop-outgoing-value: func(outgoing-value: outgoing-value)
- new-outgoing-value: func() -> outgoing-value
- outgoing-value-write-body: func(outgoing-value: outgoing-value) -> result
-
- /// A incoming-value is a wrapper around a value. It provides a way to read the value
- /// from the input-stream defined in the `wasi-io` interface.
- ///
- /// The incoming-value provides two ways to consume the value:
- /// 1. `incoming-value-consume-sync` consumes the value synchronously and returns the
- /// value as a list of bytes.
- /// 2. `incoming-value-consume-async` consumes the value asynchronously and returns the
- /// value as an input-stream.
- // Soon: switch to `resource incoming-value { ... }`
- type incoming-value = u32
- type incoming-value-async-body = input-stream
- type incoming-value-sync-body = list
- drop-incoming-value: func(incoming-value: incoming-value)
- incoming-value-consume-sync: func(incoming-value: incoming-value) -> result
- incoming-value-consume-async: func(incoming-value: incoming-value) -> result
- size: func(incoming-value: incoming-value) -> u64
-}
+and can be validated and otherwise manipulated via:
```
-
-- The `get-many` and `set-many` are atomic.
-- The `increment` is atomic, in a way that it is a small transaction of get, increment, and set operations on the same key.
-
-The following interfaces are still under discussion:
-
-```go
-interface transaction {
- // transaction is an atomic operation that groups multiple operations together.
- // If any operation fails, all operations in the transaction are rolled back.
- use types.{ Error, payload, key }
-
- get-multi: func(keys: keys) -> result, Error>
-
- set-multi: func(key_values: list<(key, list)>) -> result<_, Error>
-}
-
-interface ttl {
- use types.{ Error, key }
-
- set-with-ttl: func(key: key, value: list, ttl: u64) -> result<_, Error>
-}
-
-interface "wasi:kv/data/query" {
- ...
-}
+wasm-tools component wit wit/ ...
```
-#### Tricky design choice 1
-
-TODO
-
-#### Tricky design choice 2
-
-TODO
-
-### Considered alternatives
-
-TODO
-
-#### Alternative 1
-
-TODO
-#### Alternative 2
-
-TODO
-
-### Stakeholder Interest & Feedback
-
-TODO before entering Phase 3.
+The `wit/deps` directory contains a live snapshot of the contents of several
+other WASI proposals upon which this proposal depends. It is automatically
+updated by running [`wit-deps update`](https://crates.io/crates/wit-deps-cli)
+in the root directory, which fetches the live contents of the `main` branch of
+each proposal. As things stabilize, `wit/deps.toml` will be updated to refer to
+versioned releases.
### References & acknowledgements
-Many thanks for valuable feedback and advice from:
+This proposal was inspired by Dapr's [State API]
-- [Person 1]
-- [Person 2]
-- [etc.]
+[State API](https://docs.dapr.io/developing-applications/building-blocks/state-management/)
### Change log
+- 2023-11-30:
+ - Changed the `get` and `get-many` and `get-keys` signatures
+ - Updated comments in all the interfaces.
+ - Renamed `wasi-cloud-error` to `wasi-keyvalue-error`
- 2023-05-17: Updated batch example to use one interface instead of 2
- 2023-05-25: Change the WITs to the newest syntax.
- 2023-02-13: The following changes were made to the API:
diff --git a/keyvalue-handle-watch.md b/keyvalue-handle-watch.md
index 6393a98..7c714b3 100644
--- a/keyvalue-handle-watch.md
+++ b/keyvalue-handle-watch.md
@@ -5,7 +5,7 @@
An error resource type for keyvalue operations.
-Currently, this provides only one function to return a string representation
+
An error resource type for keyvalue operations.
+
Common errors:
+
+
Connectivity errors (e.g. network errors): when the client cannot establish
+a connection to the keyvalue service.
+
Autnetication and Authorization errors: when the client fails to authenticate
+or does not have the required permissions to perform the operation.
+
Data errors: when the client sends incompatible or corrupted data.
+
Resource errors: when the system runs out of resources (e.g. memory).
+
Internal errors: unexpected errors on the server side.
+
+
Currently, this provides only one function to return a string representation
of the error. In the future, this will be extended to provide more information
about the error.
Functions
@@ -582,9 +592,11 @@ value as an input-stream.
----
Get the value associated with the key in the bucket. It returns a incoming-value
-that can be consumed to get the value.
-
If the key does not exist in the bucket, it returns an error.
+
Get the value associated with the key in the bucket.
+
The value is returned as an option. If the key-value pair exists in the
+bucket, it returns ok with the value. If the key does not exist in the
+bucket, it returns ok with none.
A keyvalue interface that provides atomic operations.
+
Atomic operations are single, indivisible operations. When a fault causes
+an atomic operation to fail, it will appear to the invoker of the atomic
+operation that the action either completed successfully or did nothing
+at all.
Atomically compare and swap the value associated with the key in the bucket.
-It returns a boolean indicating if the swap was successful.
-
If the key does not exist in the bucket, it returns an error.
+
Compare-and-swap (CAS) atomically updates the value associated with the key
+in the bucket if the value matches the old value. This operation returns
+Ok(true) if the swap was successful, Ok(false) if the value did not match,
+
A successful CAS operation means the current value matched the old value
+and was replaced with the new value.
+
If the key does not exist in the bucket, it returns Ok(false).
A keyvalue interface that provides batch operations.
+
A batch operation is an operation that operates on multiple keys at once.
+
Batch operations are useful for reducing network round-trip time. For example,
+if you want to get the values associated with 100 keys, you can either do 100 get
+operations or you can do 1 batch get operation. The batch operation is
+faster because it only needs to make 1 network call instead of 100.
+
A batch operation does not guarantee atomicity, meaning that if the batch
+operation fails, some of the keys may have been modified and some may not.
+Transactional operations are being worked on and will be added in the future to
+provide atomicity.
+
Data consistency in a key value store refers to the gaurantee that once a
+write operation completes, all subsequent read operations will return the
+value that was written.
+
The level of consistency in batch operations can vary depending on the
+implementation. This interface does not guarantee strong consistency, meaning
+that if a write operation completes, subsequent read operations may not return
+the value that was written.
Set the values associated with the keys in the bucket. If the key already
exists in the bucket, it overwrites the value.
-
If any of the keys do not exist in the bucket, it creates a new key-value pair.
-If any other error occurs, it returns an error.
+
Note that the key-value pairs are not guaranteed to be set in the order
+they are provided.
+
If any of the keys do not exist in the bucket, it creates a new key-value pair.
+
If any other error occurs, it returns an error. When an error occurs, it
+does not rollback the key-value pairs that were already set. Thus, this batch operation
+does not guarantee atomicity, implying that some key-value pairs could be
+set while others might fail.
+
Other concurrent operations may also be able to see the partial results.
Delete the key-value pairs associated with the keys in the bucket.
-
If any of the keys do not exist in the bucket, it skips the key.
-If any other error occurs, it returns an error.
+
Note that the key-value pairs are not guaranteed to be deleted in the order
+they are provided.
+
If any of the keys do not exist in the bucket, it skips the key.
+
If any other error occurs, it returns an error. When an error occurs, it
+does not rollback the key-value pairs that were already deleted. Thus, this batch operation
+does not guarantee atomicity, implying that some key-value pairs could be
+deleted while others might fail.
+
Other concurrent operations may also be able to see the partial results.
Handle the set event for the given bucket and key.
-It returns a incoming-value that can be consumed to get the value.
+
Handle the set event for the given bucket and key.
+It returns a incoming-value that represents the new value being set.
+The new value can be consumed by the handler.
An error resource type for keyvalue operations.
-Currently, this provides only one function to return a string representation
+
An error resource type for keyvalue operations.
+
Common errors:
+
+
Connectivity errors (e.g. network errors): when the client cannot establish
+a connection to the keyvalue service.
+
Autnetication and Authorization errors: when the client fails to authenticate
+or does not have the required permissions to perform the operation.
+
Data errors: when the client sends incompatible or corrupted data.
+
Resource errors: when the system runs out of resources (e.g. memory).
+
Internal errors: unexpected errors on the server side.
+
+
Currently, this provides only one function to return a string representation
of the error. In the future, this will be extended to provide more information
about the error.
Get the value associated with the key in the bucket. It returns a incoming-value
-that can be consumed to get the value.
-
If the key does not exist in the bucket, it returns an error.
+
Get the value associated with the key in the bucket.
+
The value is returned as an option. If the key-value pair exists in the
+bucket, it returns ok with the value. If the key does not exist in the
+bucket, it returns ok with none.
A keyvalue interface that provides atomic operations.
+
Atomic operations are single, indivisible operations. When a fault causes
+an atomic operation to fail, it will appear to the invoker of the atomic
+operation that the action either completed successfully or did nothing
+at all.
Atomically compare and swap the value associated with the key in the bucket.
-It returns a boolean indicating if the swap was successful.
-
If the key does not exist in the bucket, it returns an error.
+
Compare-and-swap (CAS) atomically updates the value associated with the key
+in the bucket if the value matches the old value. This operation returns
+Ok(true) if the swap was successful, Ok(false) if the value did not match,
+
A successful CAS operation means the current value matched the old value
+and was replaced with the new value.
+
If the key does not exist in the bucket, it returns Ok(false).
A keyvalue interface that provides batch operations.
+
A batch operation is an operation that operates on multiple keys at once.
+
Batch operations are useful for reducing network round-trip time. For example,
+if you want to get the values associated with 100 keys, you can either do 100 get
+operations or you can do 1 batch get operation. The batch operation is
+faster because it only needs to make 1 network call instead of 100.
+
A batch operation does not guarantee atomicity, meaning that if the batch
+operation fails, some of the keys may have been modified and some may not.
+Transactional operations are being worked on and will be added in the future to
+provide atomicity.
+
Data consistency in a key value store refers to the gaurantee that once a
+write operation completes, all subsequent read operations will return the
+value that was written.
+
The level of consistency in batch operations can vary depending on the
+implementation. This interface does not guarantee strong consistency, meaning
+that if a write operation completes, subsequent read operations may not return
+the value that was written.
Set the values associated with the keys in the bucket. If the key already
exists in the bucket, it overwrites the value.
-
If any of the keys do not exist in the bucket, it creates a new key-value pair.
-If any other error occurs, it returns an error.
+
Note that the key-value pairs are not guaranteed to be set in the order
+they are provided.
+
If any of the keys do not exist in the bucket, it creates a new key-value pair.
+
If any other error occurs, it returns an error. When an error occurs, it
+does not rollback the key-value pairs that were already set. Thus, this batch operation
+does not guarantee atomicity, implying that some key-value pairs could be
+set while others might fail.
+
Other concurrent operations may also be able to see the partial results.
Delete the key-value pairs associated with the keys in the bucket.
-
If any of the keys do not exist in the bucket, it skips the key.
-If any other error occurs, it returns an error.
+
Note that the key-value pairs are not guaranteed to be deleted in the order
+they are provided.
+
If any of the keys do not exist in the bucket, it skips the key.
+
If any other error occurs, it returns an error. When an error occurs, it
+does not rollback the key-value pairs that were already deleted. Thus, this batch operation
+does not guarantee atomicity, implying that some key-value pairs could be
+deleted while others might fail.
+
Other concurrent operations may also be able to see the partial results.
diff --git a/wit/atomic.wit b/wit/atomic.wit
index 1819219..9dfd304 100644
--- a/wit/atomic.wit
+++ b/wit/atomic.wit
@@ -1,10 +1,15 @@
/// A keyvalue interface that provides atomic operations.
+///
+/// Atomic operations are single, indivisible operations. When a fault causes
+/// an atomic operation to fail, it will appear to the invoker of the atomic
+/// operation that the action either completed successfully or did nothing
+/// at all.
interface atomic {
/// A keyvalue interface that provides atomic operations.
use types.{bucket, error, key};
/// Atomically increment the value associated with the key in the bucket by the
- /// given delta. It returns the new value.
+ /// given delta. It returns the new value. This is single, indivisible operation.
///
/// If the key does not exist in the bucket, it creates a new key-value pair
/// with the value set to the given delta.
@@ -12,9 +17,15 @@ interface atomic {
/// If any other error occurs, it returns an error.
increment: func(bucket: borrow, key: key, delta: u64) -> result;
- /// Atomically compare and swap the value associated with the key in the bucket.
- /// It returns a boolean indicating if the swap was successful.
+ /// Compare-and-swap (CAS) atomically updates the value associated with the key
+ /// in the bucket if the value matches the old value. This operation returns
+ /// Ok(true) if the swap was successful, Ok(false) if the value did not match,
+ ///
+ /// A successful CAS operation means the current value matched the `old` value
+ /// and was replaced with the `new` value.
///
- /// If the key does not exist in the bucket, it returns an error.
+ /// If the key does not exist in the bucket, it returns Ok(false).
+ ///
+ /// If any other error occurs, it returns an error.
compare-and-swap: func(bucket: borrow, key: key, old: u64, new: u64) -> result;
}
\ No newline at end of file
diff --git a/wit/batch.wit b/wit/batch.wit
index 258009a..99d8fe8 100644
--- a/wit/batch.wit
+++ b/wit/batch.wit
@@ -1,27 +1,81 @@
/// A keyvalue interface that provides batch operations.
+///
+/// A batch operation is an operation that operates on multiple keys at once.
+///
+/// Batch operations are useful for reducing network round-trip time. For example,
+/// if you want to get the values associated with 100 keys, you can either do 100 get
+/// operations or you can do 1 batch get operation. The batch operation is
+/// faster because it only needs to make 1 network call instead of 100.
+///
+/// A batch operation does not guarantee atomicity, meaning that if the batch
+/// operation fails, some of the keys may have been modified and some may not.
+/// Transactional operations are being worked on and will be added in the future to
+/// provide atomicity.
+///
+/// Data consistency in a key value store refers to the gaurantee that once a
+/// write operation completes, all subsequent read operations will return the
+/// value that was written.
+///
+/// The level of consistency in batch operations can vary depending on the
+/// implementation. This interface does not guarantee strong consistency, meaning
+/// that if a write operation completes, subsequent read operations may not return
+/// the value that was written.
interface batch {
/// A keyvalue interface that provides batch get operations.
use types.{bucket, error, key, keys, incoming-value, outgoing-value};
/// Get the values associated with the keys in the bucket. It returns a list of
- /// incoming-values that can be consumed to get the values.
+ /// incoming-value that can be consumed to get the value associated with the key.
///
- /// If any of the keys do not exist in the bucket, it returns an error.
- get-many: func(bucket: borrow, keys: keys) -> result, error>;
+ /// If any of the keys do not exist in the bucket, it returns an None value for
+ /// that key in the list.
+ ///
+ /// Note that the key-value pairs are guaranteed to be returned in the same order
+ ///
+ /// MAY show an out-of-date value if there are concurrent writes to the bucket.
+ ///
+ /// If any other error occurs, it returns an error.
+ get-many: func(bucket: borrow, keys: keys) -> result>, error>;
/// Get all the keys in the bucket. It returns a list of keys.
- get-keys: func(bucket: borrow) -> keys;
+ ///
+ /// Note that the keys are not guaranteed to be returned in any particular order.
+ ///
+ /// If the bucket is empty, it returns an empty list.
+ ///
+ /// MAY show an out-of-date list of keys if there are concurrent writes to the bucket.
+ ///
+ /// If any error occurs, it returns an error.
+ get-keys: func(bucket: borrow) -> result;
/// Set the values associated with the keys in the bucket. If the key already
- /// exists in the bucket, it overwrites the value.
+ /// exists in the bucket, it overwrites the value.
+ ///
+ /// Note that the key-value pairs are not guaranteed to be set in the order
+ /// they are provided.
///
/// If any of the keys do not exist in the bucket, it creates a new key-value pair.
- /// If any other error occurs, it returns an error.
+ ///
+ /// If any other error occurs, it returns an error. When an error occurs, it
+ /// does not rollback the key-value pairs that were already set. Thus, this batch operation
+ /// does not guarantee atomicity, implying that some key-value pairs could be
+ /// set while others might fail.
+ ///
+ /// Other concurrent operations may also be able to see the partial results.
set-many: func(bucket: borrow, key-values: list>>) -> result<_, error>;
/// Delete the key-value pairs associated with the keys in the bucket.
- ///
+ ///
+ /// Note that the key-value pairs are not guaranteed to be deleted in the order
+ /// they are provided.
+ ///
/// If any of the keys do not exist in the bucket, it skips the key.
- /// If any other error occurs, it returns an error.
+ ///
+ /// If any other error occurs, it returns an error. When an error occurs, it
+ /// does not rollback the key-value pairs that were already deleted. Thus, this batch operation
+ /// does not guarantee atomicity, implying that some key-value pairs could be
+ /// deleted while others might fail.
+ ///
+ /// Other concurrent operations may also be able to see the partial results.
delete-many: func(bucket: borrow, keys: keys) -> result<_, error>;
-}
\ No newline at end of file
+}
diff --git a/wit/error.wit b/wit/error.wit
index 79be85c..1caba7e 100644
--- a/wit/error.wit
+++ b/wit/error.wit
@@ -1,5 +1,15 @@
-interface wasi-cloud-error {
+interface wasi-keyvalue-error {
/// An error resource type for keyvalue operations.
+ ///
+ /// Common errors:
+ /// - Connectivity errors (e.g. network errors): when the client cannot establish
+ /// a connection to the keyvalue service.
+ /// - Autnetication and Authorization errors: when the client fails to authenticate
+ /// or does not have the required permissions to perform the operation.
+ /// - Data errors: when the client sends incompatible or corrupted data.
+ /// - Resource errors: when the system runs out of resources (e.g. memory).
+ /// - Internal errors: unexpected errors on the server side.
+ ///
/// Currently, this provides only one function to return a string representation
/// of the error. In the future, this will be extended to provide more information
/// about the error.
diff --git a/wit/handle-watch.wit b/wit/handle-watch.wit
index 082a1a2..0ca7b37 100644
--- a/wit/handle-watch.wit
+++ b/wit/handle-watch.wit
@@ -1,12 +1,17 @@
/// A keyvalue interface that provides handle-watch operations.
+///
+/// This interface is used to provide event-driven mechanisms to handle
+/// keyvalue changes.
interface handle-watch {
/// A keyvalue interface that provides handle-watch operations.
use types.{bucket, key, incoming-value};
- /// Handle the set event for the given bucket and key.
- /// It returns a incoming-value that can be consumed to get the value.
+ /// Handle the `set` event for the given bucket and key.
+ /// It returns a `incoming-value` that represents the new value being set.
+ /// The new value can be consumed by the handler.
on-set: func(bucket: bucket, key: key, incoming-value: borrow);
- /// Handle the delete event for the given bucket and key.
+ /// Handle the `delete` event for the given bucket and key.
+ /// It returns a `key` that represents the key being deleted.
on-delete: func(bucket: bucket, key: key);
}
\ No newline at end of file
diff --git a/wit/readwrite.wit b/wit/readwrite.wit
index 14b6f66..637cde9 100644
--- a/wit/readwrite.wit
+++ b/wit/readwrite.wit
@@ -3,24 +3,35 @@ interface readwrite {
/// A keyvalue interface that provides simple read and write operations.
use types.{bucket, error, incoming-value, key, outgoing-value};
- /// Get the value associated with the key in the bucket. It returns a incoming-value
- /// that can be consumed to get the value.
+ /// Get the value associated with the key in the bucket.
+ ///
+ /// The value is returned as an option. If the key-value pair exists in the
+ /// bucket, it returns ok with the value. If the key does not exist in the
+ /// bucket, it returns ok with none.
///
- /// If the key does not exist in the bucket, it returns an error.
- get: func(bucket: borrow, key: key) -> result;
+ /// If any other error occurs, it returns an error.
+ get: func(bucket: borrow, key: key) -> result