From 89c63fc18742470a863456356f45767f58a72887 Mon Sep 17 00:00:00 2001
From: Joel Dice
Data consistency in a key value store refers to the guarantee that once a write operation -completes, all subsequent read operations will return the value that was written.
-Any implementation of this interface must have enough consistency to guarantee "reading your -writes." In particular, this means that the client should never get a value that is older than -the one it wrote, but it MAY get a newer value if one was written around the same time. These -guarantees only apply to the same client (which will likely be provided by the host or an -external capability of some kind). In this context a "client" is referring to the caller or -guest that is consuming this interface. Once a write request is committed by a specific client, -all subsequent read requests by the same client will reflect that write or any subsequent -writes. Another client running in a different context may or may not immediately see the result -due to the replication lag. As an example of all of this, if a value at a given key is A, and -the client writes B, then immediately reads, it should get B. If something else writes C in -quick succession, then the client may get C. However, a client running in a separate context may -still see A or B
+Any implementation of this interface MUST have enough consistency to guarantee "reading your
+writes" for read operations on the same bucket resource instance. Reads from bucket
+resources other than the one used to write are not guaranteed to return the written value
+given that the other resources may be connected to other replicas in a distributed system, even
+when opened using the same bucket identifier.
In particular, this means that a get call for a given key on a given bucket
+resource MUST never return a value that is older than the the last value written to that key
+on the same resource, but it MAY get a newer value if one was written around the same
+time. These guarantees only apply to reads and writes on the same resource; they do not hold
+across multiple resources -- even when those resources were opened using the same string
+identifier by the same component instance.
The following pseudocode example illustrates this behavior. Note that we assume there is +initially no value set for any key and that no other writes are happening beyond what is shown +in the example.
+bucketA = open("foo") +bucketB = open("foo") +bucketA.set("bar", "a") +// The following are guaranteed to succeed: +assert bucketA.get("bar").equals("a") +assert bucketB.get("bar").equals("a") or bucketB.get("bar") is None +// ...whereas this is NOT guaranteed to succeed immediately (but SHOULD eventually): +// assert bucketB.get("bar").equals("a")
+Once a value is set for a given key on a given bucket resource, all subsequent get
+requests on that same resource will reflect that write or any subsequent writes. get requests
+using a different bucket may or may not immediately see the new value due to e.g. cache effects
+and/or replication lag.
Continuing the above example:
+bucketB.set("bar", "b") +bucketC = open("foo") +value = bucketC.get("bar") +assert value.equals("a") or value.equals("b") or value is None
+In other words, the bucketC resource MAY reflect either the most recent write to the bucketA
+resource, or the one to the bucketB resource, or neither, depending on how quickly either of
+those writes reached the replica from which the bucketC resource is reading. However,
+assuming there are no unrecoverable errors -- such that the state of a replica is irretrievably
+lost before it can be propagated -- one of the values ("a" or "b") SHOULD eventually be
+considered the "latest" and replicated across the system, at which point all three resources
+will return that same value.
This interface does not currently make any hard guarantees about the durability of values
+stored. A valid implementation might rely on an in-memory hash table, the contents of which are
+lost when the process exits. Alternatively, another implementation might synchronously persist
+all writes to disk -- or even to a quorum of disk-backed nodes at multiple locations -- before
+returning a result for a set call. Finally, a third implementation might persist values
+asynchronously on a best-effort basis without blocking set calls, in which case an I/O error
+could occur after the component instance which originally made the call has exited.
Future versions of the wasi-keyvalue package may provide ways to query and control the
+durability and consistency provided by the backing implementation.
variant errorbucket to refer to a collection of key-value pairsIn this interface, we use the term bucket to refer to a connection to a collection of
+key-value pairs.
bucket resources using the same identifier MAY result in connections
+to two separate replicas in a distributed database, and that writes to one of those
+resources are not guaranteed to be readable from the other resource promptly (or ever, in
+the case of a replica failure). See the Consistency section of the store interface
+documentation for details.open: funcGet the bucket with the specified identifier.
diff --git a/watch-service.md b/watch-service.md index a65115a..2e17b04 100644 --- a/watch-service.md +++ b/watch-service.md @@ -21,20 +21,55 @@ the common denominator for all data types defined by different key-value stores ensuring compatibility between different key-value stores. Note: the clients will be expecting serialization/deserialization overhead to be handled by the key-value store. The value could be a serialized object from JSON, HTML or vendor-specific data types like AWS S3 objects. -Data consistency in a key value store refers to the guarantee that once a write operation -completes, all subsequent read operations will return the value that was written.
-Any implementation of this interface must have enough consistency to guarantee "reading your -writes." In particular, this means that the client should never get a value that is older than -the one it wrote, but it MAY get a newer value if one was written around the same time. These -guarantees only apply to the same client (which will likely be provided by the host or an -external capability of some kind). In this context a "client" is referring to the caller or -guest that is consuming this interface. Once a write request is committed by a specific client, -all subsequent read requests by the same client will reflect that write or any subsequent -writes. Another client running in a different context may or may not immediately see the result -due to the replication lag. As an example of all of this, if a value at a given key is A, and -the client writes B, then immediately reads, it should get B. If something else writes C in -quick succession, then the client may get C. However, a client running in a separate context may -still see A or B
+Any implementation of this interface MUST have enough consistency to guarantee "reading your
+writes" for read operations on the same bucket resource instance. Reads from bucket
+resources other than the one used to write are not guaranteed to return the written value
+given that the other resources may be connected to other replicas in a distributed system, even
+when opened using the same bucket identifier.
In particular, this means that a get call for a given key on a given bucket
+resource MUST never return a value that is older than the the last value written to that key
+on the same resource, but it MAY get a newer value if one was written around the same
+time. These guarantees only apply to reads and writes on the same resource; they do not hold
+across multiple resources -- even when those resources were opened using the same string
+identifier by the same component instance.
The following pseudocode example illustrates this behavior. Note that we assume there is +initially no value set for any key and that no other writes are happening beyond what is shown +in the example.
+bucketA = open("foo") +bucketB = open("foo") +bucketA.set("bar", "a") +// The following are guaranteed to succeed: +assert bucketA.get("bar").equals("a") +assert bucketB.get("bar").equals("a") or bucketB.get("bar") is None +// ...whereas this is NOT guaranteed to succeed immediately (but SHOULD eventually): +// assert bucketB.get("bar").equals("a")
+Once a value is set for a given key on a given bucket resource, all subsequent get
+requests on that same resource will reflect that write or any subsequent writes. get requests
+using a different bucket may or may not immediately see the new value due to e.g. cache effects
+and/or replication lag.
Continuing the above example:
+bucketB.set("bar", "b") +bucketC = open("foo") +value = bucketC.get("bar") +assert value.equals("a") or value.equals("b") or value is None
+In other words, the bucketC resource MAY reflect either the most recent write to the bucketA
+resource, or the one to the bucketB resource, or neither, depending on how quickly either of
+those writes reached the replica from which the bucketC resource is reading. However,
+assuming there are no unrecoverable errors -- such that the state of a replica is irretrievably
+lost before it can be propagated -- one of the values ("a" or "b") SHOULD eventually be
+considered the "latest" and replicated across the system, at which point all three resources
+will return that same value.
This interface does not currently make any hard guarantees about the durability of values
+stored. A valid implementation might rely on an in-memory hash table, the contents of which are
+lost when the process exits. Alternatively, another implementation might synchronously persist
+all writes to disk -- or even to a quorum of disk-backed nodes at multiple locations -- before
+returning a result for a set call. Finally, a third implementation might persist values
+asynchronously on a best-effort basis without blocking set calls, in which case an I/O error
+could occur after the component instance which originally made the call has exited.
Future versions of the wasi-keyvalue package may provide ways to query and control the
+durability and consistency provided by the backing implementation.
variant errorbucket to refer to a collection of key-value pairsIn this interface, we use the term bucket to refer to a connection to a collection of
+key-value pairs.
bucket resources using the same identifier MAY result in connections
+to two separate replicas in a distributed database, and that writes to one of those
+resources are not guaranteed to be readable from the other resource promptly (or ever, in
+the case of a replica failure). See the Consistency section of the store interface
+documentation for details.open: funcGet the bucket with the specified identifier.
From cf03f8a169093b6cbc903846230059d7aeb6a97f Mon Sep 17 00:00:00 2001 From: Joel DiceAny implementation of this interface MUST have enough consistency to guarantee "reading your
-writes" for read operations on the same bucket resource instance. Reads from bucket
-resources other than the one used to write are not guaranteed to return the written value
-given that the other resources may be connected to other replicas in a distributed system, even
-when opened using the same bucket identifier.
In particular, this means that a get call for a given key on a given bucket
-resource MUST never return a value that is older than the the last value written to that key
-on the same resource, but it MAY get a newer value if one was written around the same
-time. These guarantees only apply to reads and writes on the same resource; they do not hold
-across multiple resources -- even when those resources were opened using the same string
-identifier by the same component instance.
The following pseudocode example illustrates this behavior. Note that we assume there is -initially no value set for any key and that no other writes are happening beyond what is shown -in the example.
-bucketA = open("foo") +
An implementation of this interface MUST be eventually consistent, meaning that, after some time
+with no further updates, all replicas in the (potentially distributed) system will eventually
+converge on a consistent state for all values. This allows replicas to temporarily diverge to
+ensure low latency and high availability. Implementations based on a centralized or local
+backing store may provide a stronger consistency model, but guest components which are intended
+to be portable to any wasi-keyvalue implementation should not rely on anything stronger than
+eventual consistency.
Given that each bucket resource may represent a connection to a different replica in a
+distributed system, values read for a given key from two different buckets may differ, even if
+those bucket resources were opened using the same string identifier. In addition, consecutive
+operations on a single bucket resource may produce temporarily inconsistent results if
+e.g. the implementation is forced to reconnect to a different replica due to a connection
+failure. For example, a write followed by a read may not return the value just written, even if
+no other recent or subsequent writes have occurred.
Consider the following pseudocode example (and assume we start with an empty store and no other +concurrent activity):
+bucketA = open("foo")
bucketB = open("foo")
bucketA.set("bar", "a")
-// The following are guaranteed to succeed:
-assert bucketA.get("bar").equals("a")
+
+// These are guaranteed to succeed:
+assert bucketA.get("bar").equals("a") or bucketA.get("bar") is None
assert bucketB.get("bar").equals("a") or bucketB.get("bar") is None
-// ...whereas this is NOT guaranteed to succeed immediately (but SHOULD eventually):
-// assert bucketB.get("bar").equals("a")
-Once a value is set for a given key on a given bucket resource, all subsequent get
-requests on that same resource will reflect that write or any subsequent writes. get requests
-using a different bucket may or may not immediately see the new value due to e.g. cache effects
-and/or replication lag.
-Continuing the above example:
-bucketB.set("bar", "b")
+
+// This is likely to succeed, but not guaranteed; e.g. `bucketA` might need to reconnect to a
+// different replica which hasn't received the above write yet. It will _eventually_
+// succeed, provided there are no irrecoverable errors which prevent the propagation of the
+// write.
+assert bucketA.get("bar").equals("a")
+
+// Likewise, this will _eventually_ succeed in the absence of irrecoverable errors:
+assert bucketB.get("bar").equals("a")
+
+bucketB.set("bar", "b")
bucketC = open("foo")
value = bucketC.get("bar")
-assert value.equals("a") or value.equals("b") or value is None
+
+// This is guaranteed to succeed:
+assert value.equals("a") or value.equals("b") or value is None
+
In other words, the bucketC resource MAY reflect either the most recent write to the bucketA
resource, or the one to the bucketB resource, or neither, depending on how quickly either of
those writes reached the replica from which the bucketC resource is reading. However,
-assuming there are no unrecoverable errors -- such that the state of a replica is irretrievably
-lost before it can be propagated -- one of the values ("a" or "b") SHOULD eventually be
-considered the "latest" and replicated across the system, at which point all three resources
-will return that same value.
This interface does not currently make any hard guarantees about the durability of values stored. A valid implementation might rely on an in-memory hash table, the contents of which are diff --git a/watch-service.md b/watch-service.md index 2e17b04..fe83324 100644 --- a/watch-service.md +++ b/watch-service.md @@ -22,44 +22,53 @@ ensuring compatibility between different key-value stores. Note: the clients wil serialization/deserialization overhead to be handled by the key-value store. The value could be a serialized object from JSON, HTML or vendor-specific data types like AWS S3 objects.
Any implementation of this interface MUST have enough consistency to guarantee "reading your
-writes" for read operations on the same bucket resource instance. Reads from bucket
-resources other than the one used to write are not guaranteed to return the written value
-given that the other resources may be connected to other replicas in a distributed system, even
-when opened using the same bucket identifier.
In particular, this means that a get call for a given key on a given bucket
-resource MUST never return a value that is older than the the last value written to that key
-on the same resource, but it MAY get a newer value if one was written around the same
-time. These guarantees only apply to reads and writes on the same resource; they do not hold
-across multiple resources -- even when those resources were opened using the same string
-identifier by the same component instance.
The following pseudocode example illustrates this behavior. Note that we assume there is -initially no value set for any key and that no other writes are happening beyond what is shown -in the example.
-bucketA = open("foo") +
An implementation of this interface MUST be eventually consistent, meaning that, after some time
+with no further updates, all replicas in the (potentially distributed) system will eventually
+converge on a consistent state for all values. This allows replicas to temporarily diverge to
+ensure low latency and high availability. Implementations based on a centralized or local
+backing store may provide a stronger consistency model, but guest components which are intended
+to be portable to any wasi-keyvalue implementation should not rely on anything stronger than
+eventual consistency.
Given that each bucket resource may represent a connection to a different replica in a
+distributed system, values read for a given key from two different buckets may differ, even if
+those bucket resources were opened using the same string identifier. In addition, consecutive
+operations on a single bucket resource may produce temporarily inconsistent results if
+e.g. the implementation is forced to reconnect to a different replica due to a connection
+failure. For example, a write followed by a read may not return the value just written, even if
+no other recent or subsequent writes have occurred.
Consider the following pseudocode example (and assume we start with an empty store and no other +concurrent activity):
+bucketA = open("foo")
bucketB = open("foo")
bucketA.set("bar", "a")
-// The following are guaranteed to succeed:
-assert bucketA.get("bar").equals("a")
+
+// These are guaranteed to succeed:
+assert bucketA.get("bar").equals("a") or bucketA.get("bar") is None
assert bucketB.get("bar").equals("a") or bucketB.get("bar") is None
-// ...whereas this is NOT guaranteed to succeed immediately (but SHOULD eventually):
-// assert bucketB.get("bar").equals("a")
-Once a value is set for a given key on a given bucket resource, all subsequent get
-requests on that same resource will reflect that write or any subsequent writes. get requests
-using a different bucket may or may not immediately see the new value due to e.g. cache effects
-and/or replication lag.
-Continuing the above example:
-bucketB.set("bar", "b")
+
+// This is likely to succeed, but not guaranteed; e.g. `bucketA` might need to reconnect to a
+// different replica which hasn't received the above write yet. It will _eventually_
+// succeed, provided there are no irrecoverable errors which prevent the propagation of the
+// write.
+assert bucketA.get("bar").equals("a")
+
+// Likewise, this will _eventually_ succeed in the absence of irrecoverable errors:
+assert bucketB.get("bar").equals("a")
+
+bucketB.set("bar", "b")
bucketC = open("foo")
value = bucketC.get("bar")
-assert value.equals("a") or value.equals("b") or value is None
+
+// This is guaranteed to succeed:
+assert value.equals("a") or value.equals("b") or value is None
+
In other words, the bucketC resource MAY reflect either the most recent write to the bucketA
resource, or the one to the bucketB resource, or neither, depending on how quickly either of
those writes reached the replica from which the bucketC resource is reading. However,
-assuming there are no unrecoverable errors -- such that the state of a replica is irretrievably
-lost before it can be propagated -- one of the values ("a" or "b") SHOULD eventually be
-considered the "latest" and replicated across the system, at which point all three resources
-will return that same value.
This interface does not currently make any hard guarantees about the durability of values
stored. A valid implementation might rely on an in-memory hash table, the contents of which are
diff --git a/wit/store.wit b/wit/store.wit
index 8463fa9..93e751a 100644
--- a/wit/store.wit
+++ b/wit/store.wit
@@ -10,51 +10,58 @@
///
/// ## Consistency
///
-/// Any implementation of this interface MUST have enough consistency to guarantee "reading your
-/// writes" for read operations on the same `bucket` resource instance. Reads from `bucket`
-/// resources other than the one used to write are _not_ guaranteed to return the written value
-/// given that the other resources may be connected to other replicas in a distributed system, even
-/// when opened using the same bucket identifier.
+/// An implementation of this interface MUST be eventually consistent, meaning that, after some time
+/// with no further updates, all replicas in the (potentially distributed) system will eventually
+/// converge on a consistent state for all values. This allows replicas to temporarily diverge to
+/// ensure low latency and high availability. Implementations based on a centralized or local
+/// backing store may provide a stronger consistency model, but guest components which are intended
+/// to be portable to any `wasi-keyvalue` implementation should not rely on anything stronger than
+/// eventual consistency.
///
-/// In particular, this means that a `get` call for a given key on a given `bucket`
-/// resource MUST never return a value that is older than the the last value written to that key
-/// on the same resource, but it MAY get a newer value if one was written around the same
-/// time. These guarantees only apply to reads and writes on the same resource; they do not hold
-/// across multiple resources -- even when those resources were opened using the same string
-/// identifier by the same component instance.
+/// Given that each `bucket` resource may represent a connection to a different replica in a
+/// distributed system, values read for a given key from two different `bucket`s may differ, even if
+/// those `bucket` resources were opened using the same string identifier. In addition, consecutive
+/// operations on a single `bucket` resource may produce temporarily inconsistent results if
+/// e.g. the implementation is forced to reconnect to a different replica due to a connection
+/// failure. For example, a write followed by a read may not return the value just written, even if
+/// no other recent or subsequent writes have occurred.
///
-/// The following pseudocode example illustrates this behavior. Note that we assume there is
-/// initially no value set for any key and that no other writes are happening beyond what is shown
-/// in the example.
+/// Consider the following pseudocode example (and assume we start with an empty store and no other
+/// concurrent activity):
///
+/// ```
/// bucketA = open("foo")
/// bucketB = open("foo")
/// bucketA.set("bar", "a")
-/// // The following are guaranteed to succeed:
-/// assert bucketA.get("bar").equals("a")
+///
+/// // These are guaranteed to succeed:
+/// assert bucketA.get("bar").equals("a") or bucketA.get("bar") is None
/// assert bucketB.get("bar").equals("a") or bucketB.get("bar") is None
-/// // ...whereas this is NOT guaranteed to succeed immediately (but SHOULD eventually):
-/// // assert bucketB.get("bar").equals("a")
///
-/// Once a value is `set` for a given key on a given `bucket` resource, all subsequent `get`
-/// requests on that same resource will reflect that write or any subsequent writes. `get` requests
-/// using a different bucket may or may not immediately see the new value due to e.g. cache effects
-/// and/or replication lag.
+/// // This is likely to succeed, but not guaranteed; e.g. `bucketA` might need to reconnect to a
+/// // different replica which hasn't received the above write yet. It will _eventually_
+/// // succeed, provided there are no irrecoverable errors which prevent the propagation of the
+/// // write.
+/// assert bucketA.get("bar").equals("a")
///
-/// Continuing the above example:
+/// // Likewise, this will _eventually_ succeed in the absence of irrecoverable errors:
+/// assert bucketB.get("bar").equals("a")
///
/// bucketB.set("bar", "b")
/// bucketC = open("foo")
/// value = bucketC.get("bar")
+///
+/// // This is guaranteed to succeed:
/// assert value.equals("a") or value.equals("b") or value is None
+/// ```
///
/// In other words, the `bucketC` resource MAY reflect either the most recent write to the `bucketA`
/// resource, or the one to the `bucketB` resource, or neither, depending on how quickly either of
/// those writes reached the replica from which the `bucketC` resource is reading. However,
-/// assuming there are no unrecoverable errors -- such that the state of a replica is irretrievably
-/// lost before it can be propagated -- one of the values ("a" or "b") SHOULD eventually be
-/// considered the "latest" and replicated across the system, at which point all three resources
-/// will return that same value.
+/// assuming there are no irrecoverable errors -- such that the state of a replica is irretrievably
+/// lost before it can be propagated -- one of the values ("a" or "b") MUST eventually be considered
+/// the "latest" and replicated across the system, at which point all three resources will return
+/// that same value.
///
/// ## Durability
///
From 418d48fcbee746cfe03302302668e0f611b618b2 Mon Sep 17 00:00:00 2001
From: Joel Dice
An implementation of this interface MUST be eventually consistent, meaning that, after some time
-with no further updates, all replicas in the (potentially distributed) system will eventually
-converge on a consistent state for all values. This allows replicas to temporarily diverge to
-ensure low latency and high availability. Implementations based on a centralized or local
-backing store may provide a stronger consistency model, but guest components which are intended
-to be portable to any wasi-keyvalue implementation should not rely on anything stronger than
-eventual consistency.
Given that each bucket resource may represent a connection to a different replica in a
-distributed system, values read for a given key from two different buckets may differ, even if
-those bucket resources were opened using the same string identifier. In addition, consecutive
-operations on a single bucket resource may produce temporarily inconsistent results if
-e.g. the implementation is forced to reconnect to a different replica due to a connection
-failure. For example, a write followed by a read may not return the value just written, even if
-no other recent or subsequent writes have occurred.
Consider the following pseudocode example (and assume we start with an empty store and no other -concurrent activity):
-bucketA = open("foo")
-bucketB = open("foo")
-bucketA.set("bar", "a")
-
-// These are guaranteed to succeed:
-assert bucketA.get("bar").equals("a") or bucketA.get("bar") is None
-assert bucketB.get("bar").equals("a") or bucketB.get("bar") is None
-
-// This is likely to succeed, but not guaranteed; e.g. `bucketA` might need to reconnect to a
-// different replica which hasn't received the above write yet. It will _eventually_
-// succeed, provided there are no irrecoverable errors which prevent the propagation of the
-// write.
-assert bucketA.get("bar").equals("a")
-
-// Likewise, this will _eventually_ succeed in the absence of irrecoverable errors:
-assert bucketB.get("bar").equals("a")
-
-bucketB.set("bar", "b")
-bucketC = open("foo")
-value = bucketC.get("bar")
-
-// This is guaranteed to succeed:
-assert value.equals("a") or value.equals("b") or value is None
-
-In other words, the bucketC resource MAY reflect either the most recent write to the bucketA
-resource, or the one to the bucketB resource, or neither, depending on how quickly either of
-those writes reached the replica from which the bucketC resource is reading. However,
-assuming there are no irrecoverable errors -- such that the state of a replica is irretrievably
-lost before it can be propagated -- one of the values ("a" or "b") MUST eventually be considered
-the "latest" and replicated across the system, at which point all three resources will return
-that same value.
An implementation of this interface MUST be eventually consistent, but is not required to +provide any consistency guaranteeds beyond that. Practically speaking, eventual consistency is +among the weakest of consistency models, guaranteeing only that values will not be produced +"from nowhere", i.e. any value read is guaranteed to have been written to that key at some +earlier time. Beyond that, there are no guarantees, and thus a portable component must neither +expect nor rely on anything else.
+In the future, additional interfaces may be added to wasi:key-value with stronger guarantees,
+which will allow components to express their requirements by importing whichever interface(s)
+provides matching (or stronger) guarantees. For example, a component requiring strict
+serializability might import a (currently hypothetical) strict-serializable-store interface
+with a similar signature to store but with much stronger semantic guarantees. On the other
+end, a host might either support implementations of both the store and
+strict-serializable-store or just the former, in which case the host would immediately reject
+a component which imports the unsupported interface.
Here are a few examples of behavior which an component developer might wish to rely on but which +are NOT guaranteed by an eventually consistent system (e.g. a distributed system composed of +multiple replicas, each of which may receive writes in a different order, making no attempt to +converge on a global consensus):
+Read-your-own-writes: eventual consistency does NOT guarantee that a write to a given key +followed by a read from the same key will retrieve the same or newer value.
+Convergence: eventual consistency does NOT guarantee that any two replicas will agree on the +value for a given key -- even after all writes have had time to propagate to all replicas.
+Last-write-wins: eventual consistency does NOT guarantee that the most recent write will +take precendence over an earlier one; old writes may overwrite newer ones temporarily or +permanently.
+This interface does not currently make any hard guarantees about the durability of values
stored. A valid implementation might rely on an in-memory hash table, the contents of which are
@@ -79,7 +65,7 @@ all writes to disk -- or even to a quorum of disk-backed nodes at multiple locat
returning a result for a set call. Finally, a third implementation might persist values
asynchronously on a best-effort basis without blocking set calls, in which case an I/O error
could occur after the component instance which originally made the call has exited.
Future versions of the wasi-keyvalue package may provide ways to query and control the
+
Future versions of the wasi:keyvalue package may provide ways to query and control the
durability and consistency provided by the backing implementation.
resource bucketA 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 +
It is worth noting that the exact terminology for bucket in key-value stores can vary depending on the specific implementation. For example:
bucket resources using the same identifier MAY result in connections
to two separate replicas in a distributed database, and that writes to one of those
resources are not guaranteed to be readable from the other resource promptly (or ever, in
-the case of a replica failure). See the Consistency section of the store interface
-documentation for details.Consistency section of the
+store interface documentation for details.
open: funcGet the bucket with the specified identifier.
diff --git a/watch-service.md b/watch-service.md index fe83324..496b694 100644 --- a/watch-service.md +++ b/watch-service.md @@ -22,53 +22,39 @@ ensuring compatibility between different key-value stores. Note: the clients wil serialization/deserialization overhead to be handled by the key-value store. The value could be a serialized object from JSON, HTML or vendor-specific data types like AWS S3 objects.An implementation of this interface MUST be eventually consistent, meaning that, after some time
-with no further updates, all replicas in the (potentially distributed) system will eventually
-converge on a consistent state for all values. This allows replicas to temporarily diverge to
-ensure low latency and high availability. Implementations based on a centralized or local
-backing store may provide a stronger consistency model, but guest components which are intended
-to be portable to any wasi-keyvalue implementation should not rely on anything stronger than
-eventual consistency.
Given that each bucket resource may represent a connection to a different replica in a
-distributed system, values read for a given key from two different buckets may differ, even if
-those bucket resources were opened using the same string identifier. In addition, consecutive
-operations on a single bucket resource may produce temporarily inconsistent results if
-e.g. the implementation is forced to reconnect to a different replica due to a connection
-failure. For example, a write followed by a read may not return the value just written, even if
-no other recent or subsequent writes have occurred.
Consider the following pseudocode example (and assume we start with an empty store and no other -concurrent activity):
-bucketA = open("foo")
-bucketB = open("foo")
-bucketA.set("bar", "a")
-
-// These are guaranteed to succeed:
-assert bucketA.get("bar").equals("a") or bucketA.get("bar") is None
-assert bucketB.get("bar").equals("a") or bucketB.get("bar") is None
-
-// This is likely to succeed, but not guaranteed; e.g. `bucketA` might need to reconnect to a
-// different replica which hasn't received the above write yet. It will _eventually_
-// succeed, provided there are no irrecoverable errors which prevent the propagation of the
-// write.
-assert bucketA.get("bar").equals("a")
-
-// Likewise, this will _eventually_ succeed in the absence of irrecoverable errors:
-assert bucketB.get("bar").equals("a")
-
-bucketB.set("bar", "b")
-bucketC = open("foo")
-value = bucketC.get("bar")
-
-// This is guaranteed to succeed:
-assert value.equals("a") or value.equals("b") or value is None
-
-In other words, the bucketC resource MAY reflect either the most recent write to the bucketA
-resource, or the one to the bucketB resource, or neither, depending on how quickly either of
-those writes reached the replica from which the bucketC resource is reading. However,
-assuming there are no irrecoverable errors -- such that the state of a replica is irretrievably
-lost before it can be propagated -- one of the values ("a" or "b") MUST eventually be considered
-the "latest" and replicated across the system, at which point all three resources will return
-that same value.
An implementation of this interface MUST be eventually consistent, but is not required to +provide any consistency guaranteeds beyond that. Practically speaking, eventual consistency is +among the weakest of consistency models, guaranteeing only that values will not be produced +"from nowhere", i.e. any value read is guaranteed to have been written to that key at some +earlier time. Beyond that, there are no guarantees, and thus a portable component must neither +expect nor rely on anything else.
+In the future, additional interfaces may be added to wasi:key-value with stronger guarantees,
+which will allow components to express their requirements by importing whichever interface(s)
+provides matching (or stronger) guarantees. For example, a component requiring strict
+serializability might import a (currently hypothetical) strict-serializable-store interface
+with a similar signature to store but with much stronger semantic guarantees. On the other
+end, a host might either support implementations of both the store and
+strict-serializable-store or just the former, in which case the host would immediately reject
+a component which imports the unsupported interface.
Here are a few examples of behavior which an component developer might wish to rely on but which +are NOT guaranteed by an eventually consistent system (e.g. a distributed system composed of +multiple replicas, each of which may receive writes in a different order, making no attempt to +converge on a global consensus):
+Read-your-own-writes: eventual consistency does NOT guarantee that a write to a given key +followed by a read from the same key will retrieve the same or newer value.
+Convergence: eventual consistency does NOT guarantee that any two replicas will agree on the +value for a given key -- even after all writes have had time to propagate to all replicas.
+Last-write-wins: eventual consistency does NOT guarantee that the most recent write will +take precendence over an earlier one; old writes may overwrite newer ones temporarily or +permanently.
+This interface does not currently make any hard guarantees about the durability of values
stored. A valid implementation might rely on an in-memory hash table, the contents of which are
@@ -77,7 +63,7 @@ all writes to disk -- or even to a quorum of disk-backed nodes at multiple locat
returning a result for a set call. Finally, a third implementation might persist values
asynchronously on a best-effort basis without blocking set calls, in which case an I/O error
could occur after the component instance which originally made the call has exited.
Future versions of the wasi-keyvalue package may provide ways to query and control the
+
Future versions of the wasi:keyvalue package may provide ways to query and control the
durability and consistency provided by the backing implementation.
resource bucketA 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 +
It is worth noting that the exact terminology for bucket in key-value stores can vary depending on the specific implementation. For example:
bucket resources using the same identifier MAY result in connections
to two separate replicas in a distributed database, and that writes to one of those
resources are not guaranteed to be readable from the other resource promptly (or ever, in
-the case of a replica failure). See the Consistency section of the store interface
-documentation for details.Consistency section of the
+store interface documentation for details.
open: funcGet the bucket with the specified identifier.
diff --git a/wit/store.wit b/wit/store.wit index 93e751a..251d72d 100644 --- a/wit/store.wit +++ b/wit/store.wit @@ -10,58 +10,36 @@ /// /// ## Consistency /// -/// An implementation of this interface MUST be eventually consistent, meaning that, after some time -/// with no further updates, all replicas in the (potentially distributed) system will eventually -/// converge on a consistent state for all values. This allows replicas to temporarily diverge to -/// ensure low latency and high availability. Implementations based on a centralized or local -/// backing store may provide a stronger consistency model, but guest components which are intended -/// to be portable to any `wasi-keyvalue` implementation should not rely on anything stronger than -/// eventual consistency. +/// An implementation of this interface MUST be eventually consistent, but is not required to +/// provide any consistency guaranteeds beyond that. Practically speaking, eventual consistency is +/// among the weakest of consistency models, guaranteeing only that values will not be produced +/// "from nowhere", i.e. any value read is guaranteed to have been written to that key at some +/// earlier time. Beyond that, there are no guarantees, and thus a portable component must neither +/// expect nor rely on anything else. /// -/// Given that each `bucket` resource may represent a connection to a different replica in a -/// distributed system, values read for a given key from two different `bucket`s may differ, even if -/// those `bucket` resources were opened using the same string identifier. In addition, consecutive -/// operations on a single `bucket` resource may produce temporarily inconsistent results if -/// e.g. the implementation is forced to reconnect to a different replica due to a connection -/// failure. For example, a write followed by a read may not return the value just written, even if -/// no other recent or subsequent writes have occurred. +/// In the future, additional interfaces may be added to `wasi:key-value` with stronger guarantees, +/// which will allow components to express their requirements by importing whichever interface(s) +/// provides matching (or stronger) guarantees. For example, a component requiring strict +/// serializability might import a (currently hypothetical) `strict-serializable-store` interface +/// with a similar signature to `store` but with much stronger semantic guarantees. On the other +/// end, a host might either support implementations of both the `store` and +/// `strict-serializable-store` or just the former, in which case the host would immediately reject +/// a component which imports the unsupported interface. /// -/// Consider the following pseudocode example (and assume we start with an empty store and no other -/// concurrent activity): +/// Here are a few examples of behavior which an component developer might wish to rely on but which +/// are _NOT_ guaranteed by an eventually consistent system (e.g. a distributed system composed of +/// multiple replicas, each of which may receive writes in a different order, making no attempt to +/// converge on a global consensus): /// -/// ``` -/// bucketA = open("foo") -/// bucketB = open("foo") -/// bucketA.set("bar", "a") +/// - Read-your-own-writes: eventual consistency does _NOT_ guarantee that a write to a given key +/// followed by a read from the same key will retrieve the same or newer value. /// -/// // These are guaranteed to succeed: -/// assert bucketA.get("bar").equals("a") or bucketA.get("bar") is None -/// assert bucketB.get("bar").equals("a") or bucketB.get("bar") is None +/// - Convergence: eventual consistency does _NOT_ guarantee that any two replicas will agree on the +/// value for a given key -- even after all writes have had time to propagate to all replicas. /// -/// // This is likely to succeed, but not guaranteed; e.g. `bucketA` might need to reconnect to a -/// // different replica which hasn't received the above write yet. It will _eventually_ -/// // succeed, provided there are no irrecoverable errors which prevent the propagation of the -/// // write. -/// assert bucketA.get("bar").equals("a") -/// -/// // Likewise, this will _eventually_ succeed in the absence of irrecoverable errors: -/// assert bucketB.get("bar").equals("a") -/// -/// bucketB.set("bar", "b") -/// bucketC = open("foo") -/// value = bucketC.get("bar") -/// -/// // This is guaranteed to succeed: -/// assert value.equals("a") or value.equals("b") or value is None -/// ``` -/// -/// In other words, the `bucketC` resource MAY reflect either the most recent write to the `bucketA` -/// resource, or the one to the `bucketB` resource, or neither, depending on how quickly either of -/// those writes reached the replica from which the `bucketC` resource is reading. However, -/// assuming there are no irrecoverable errors -- such that the state of a replica is irretrievably -/// lost before it can be propagated -- one of the values ("a" or "b") MUST eventually be considered -/// the "latest" and replicated across the system, at which point all three resources will return -/// that same value. +/// - Last-write-wins: eventual consistency does _NOT_ guarantee that the most recent write will +/// take precendence over an earlier one; old writes may overwrite newer ones temporarily or +/// permanently. /// /// ## Durability /// @@ -73,7 +51,7 @@ /// asynchronously on a best-effort basis without blocking `set` calls, in which case an I/O error /// could occur after the component instance which originally made the call has exited. /// -/// Future versions of the `wasi-keyvalue` package may provide ways to query and control the +/// Future versions of the `wasi:keyvalue` package may provide ways to query and control the /// durability and consistency provided by the backing implementation. interface store { /// The set of errors which may be raised by functions in this package @@ -108,7 +86,7 @@ interface store { /// 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 + /// It is worth noting that the exact terminology for bucket in key-value stores can vary /// depending on the specific implementation. For example: /// /// 1. Amazon DynamoDB calls a collection of key-value pairs a table @@ -125,8 +103,8 @@ interface store { /// Note that opening two `bucket` resources using the same identifier MAY result in connections /// to two separate replicas in a distributed database, and that writes to one of those /// resources are not guaranteed to be readable from the other resource promptly (or ever, in - /// the case of a replica failure). See the `Consistency` section of the `store` interface - /// documentation for details. + /// the case of a replica failure or message reordering). See the `Consistency` section of the + /// `store` interface documentation for details. resource bucket { /// Get the value associated with the specified `key` /// From a9f34a46e854736d85245da206cc2d31dd7dc72f Mon Sep 17 00:00:00 2001 From: Joel DiceIn the future, additional interfaces may be added to wasi:key-value with stronger guarantees,
+
In the future, additional interfaces may be added to wasi:keyvalue with stronger guarantees,
which will allow components to express their requirements by importing whichever interface(s)
provides matching (or stronger) guarantees. For example, a component requiring strict
serializability might import a (currently hypothetical) strict-serializable-store interface
@@ -38,7 +38,7 @@ with a similar signature to store but with much stronger semantic g
end, a host might either support implementations of both the store and
strict-serializable-store or just the former, in which case the host would immediately reject
a component which imports the unsupported interface.
Here are a few examples of behavior which an component developer might wish to rely on but which +
Here are a few examples of behavior which a component developer might wish to rely on but which are NOT guaranteed by an eventually consistent system (e.g. a distributed system composed of multiple replicas, each of which may receive writes in a different order, making no attempt to converge on a global consensus):
@@ -65,8 +65,8 @@ all writes to disk -- or even to a quorum of disk-backed nodes at multiple locat returning a result for aset call. Finally, a third implementation might persist values
asynchronously on a best-effort basis without blocking set calls, in which case an I/O error
could occur after the component instance which originally made the call has exited.
-Future versions of the wasi:keyvalue package may provide ways to query and control the
-durability and consistency provided by the backing implementation.
Future versions of wasi:keyvalue may provide ways to query and control the durability and
+consistency provided by the backing implementation.
variant errorIn the future, additional interfaces may be added to wasi:key-value with stronger guarantees,
+
In the future, additional interfaces may be added to wasi:keyvalue with stronger guarantees,
which will allow components to express their requirements by importing whichever interface(s)
provides matching (or stronger) guarantees. For example, a component requiring strict
serializability might import a (currently hypothetical) strict-serializable-store interface
@@ -36,7 +36,7 @@ with a similar signature to store but with much stronger semantic g
end, a host might either support implementations of both the store and
strict-serializable-store or just the former, in which case the host would immediately reject
a component which imports the unsupported interface.
Here are a few examples of behavior which an component developer might wish to rely on but which +
Here are a few examples of behavior which a component developer might wish to rely on but which are NOT guaranteed by an eventually consistent system (e.g. a distributed system composed of multiple replicas, each of which may receive writes in a different order, making no attempt to converge on a global consensus):
@@ -63,8 +63,8 @@ all writes to disk -- or even to a quorum of disk-backed nodes at multiple locat returning a result for aset call. Finally, a third implementation might persist values
asynchronously on a best-effort basis without blocking set calls, in which case an I/O error
could occur after the component instance which originally made the call has exited.
-Future versions of the wasi:keyvalue package may provide ways to query and control the
-durability and consistency provided by the backing implementation.
Future versions of wasi:keyvalue may provide ways to query and control the durability and
+consistency provided by the backing implementation.
variant error