§ S3 · Data and formats
S3-compatible stores
The three conditional requests that the S3 backend needs, how pravo checks that a store honours them, and how to test a store before it holds a state.
On this page
The S3 backend keeps one object for each record and one object for each lock. Its locks rest on three conditional requests. A store that honours all three keeps two applies apart. A store that ignores one lets two commands hold the same lock, and nothing says so.
| Request | Used for | A store that honours it answers a failed condition with |
|---|---|---|
PutObject with If-None-Match: * |
Creating a lock: the object must not exist. | 412 PreconditionFailed, or 409 ConditionalRequestConflict when another conditional request on the key is running. |
PutObject with If-Match: ETAG |
Renewing a lock and replacing a record that the command read. | The same. |
DeleteObject with If-Match: ETAG |
Releasing a lock, and reclaiming one that went stale. | The same. |
If-Match on an object that does not exist is answered with 404 NoSuchKey by some stores. pravo treats that as a missing object, as it does for a plain read.
Before a command that writes the state first uses a bucket, pravo probes the store. It writes scratch objects under PREFIX/probe/ and tries each of the three requests with a tag that must be refused and with one that must be accepted. It reads the contents back after a refused request, and it starts eight creates of one key at once and requires one winner. It deletes the scratch objects when it ends. A store that ignores a condition, or answers 501, is refused with a message that names the condition:
Error: s3://infra-state/prod cannot hold a state that more than one command writes: a delete with a stale entity tag succeeded, so If-Match is ignored on a delete
A store that passes is remembered by endpoint and bucket for thirty days, in $PRAVO_HOME/s3-probe. The probe needs the same permissions as the backend: get, put, delete and list. A store that cannot be reached or written gives that error, not a refusal. PRAVO_S3_PROBE=off skips the probe, for a store that you have tested yourself. Test a store runs it on demand.
Test a store
pravo state probe runs the checks above against the store that the root names. It ignores the remembered verdict and the PRAVO_S3_PROBE variable, prints one line for each check, and exits 1 at the first check that fails:
create only (If-None-Match) ok
replace (If-Match on a put) ok
delete (If-Match on a delete) FAILED
Error: s3://infra-state/prod cannot hold a state that more than one command writes: a delete with a stale entity tag succeeded, so If-Match is ignored on a delete
--json prints the same as one document. A store that passes is remembered for thirty days.
To try a store before a project uses it, make a directory with a main.cue that holds only the $settings block, run pravo init and then pravo state probe:
package probe
import "pravo.dev/pravo"
$settings: pravo.#Settings & {
state: pravo.#S3State & {backend: "s3", bucket: "my-bucket", endpoint: "https://s3.example.com"}
}
The probe shows that the store checks the conditions one request at a time and that concurrent creates have one winner. It does not show that the store keeps that guarantee under every load. Amazon S3 honours the conditions on a versioned bucket. On other stores a versioned bucket is where conditional requests tend to be weakest, so probe the bucket as it will be used, with versioning set the way you will set it.
Remote state sets up the bucket, and State format describes the objects.