UltraFabric KV
A key-value store for sessions, feature flags, counters and caches that lives in the same database as your SQL tables, under the same tokens and the same tenant isolation. There is no second system to secure, back up or migrate.
UltraFabric KV runs in the repository's end-to-end tests today. There is no public build or hosted endpoint yet; early access teams get the first hosted databases.
What you can rely on
- Strongly consistent. Once a write is acknowledged, every later read sees it. There is no propagation delay to wait out.
- Compare-and-set. Every entry has a version. A write or delete can require the version you read, or that the key is absent, so counters, leases and idempotency records are safe under concurrency. In our end-to-end test, 64 clients made 3,200 concurrent increments to one counter and none was lost.
- All-or-nothing batches. A bulk request of puts and deletes is applied completely or not at all, including its conditions.
- Expiry. An entry with a time to live is absent for every read, list and condition from the moment it expires, also across restarts.
- Your tenant only. The tenant comes from your verified token, never from a header. Another tenant can neither read, list, count, overwrite nor detect your namespaces or keys, even with identical names. A namespace can require a clearance, and a token below it cannot use it.
- Nothing cut short. A key, value, batch or metadata over a limit is refused whole.
Requests
Every request carries Authorization: Bearer <token>. Errors are JSON: {"error": {"code": "...", "message": "..."}}.
| Request | What it does |
|---|---|
GET /v1/namespaces | Your namespaces, with their class and creation time. |
PUT /v1/namespaces/{ns} | Create a namespace (optional body {"class":"internal"}). 201 when created, 200 when it already exists with that class, 409 with another. |
DELETE /v1/namespaces/{ns} | Delete a namespace and every entry in it. |
GET /v1/namespaces/{ns}/values/{key} | The value, with ETag (its version), UF-Expires-At and UF-Metadata. HEAD for the headers only. |
PUT /v1/namespaces/{ns}/values/{key} | Write the body as the value. ?ttl=<seconds> or ?expires_at=<RFC 3339>; UF-Metadata: <json>; If-Match: "<version>" or If-None-Match: *. |
DELETE /v1/namespaces/{ns}/values/{key} | Delete a key (also fine when absent); If-Match makes it conditional. |
GET /v1/namespaces/{ns}/keys?prefix=&cursor=&limit= | Keys in order, with version, expiry and metadata. Follow cursor until complete is true; a page can be short while more keys follow. |
POST /v1/namespaces/{ns}/bulk | Puts and deletes, each optionally conditional, applied as one step. |
| Status | When |
|---|---|
| 401 / 403 | No or refused token / a namespace above your clearance, or a token without a tenant. |
| 404 | No live entry (not_found) or no such namespace (namespace_not_found). |
| 412 | A condition did not hold; nothing was written. |
| 413 / 400 | Over a limit, or a malformed request; nothing was written. |
502 outcome_unknown | The service lost its connection after sending your write, so it may or may not have been applied. It is never retried for you; read the key before trying again. |
Limits
| Key | 1–512 bytes of UTF-8 |
| Value | 4 MiB |
| Metadata | 1,024 bytes of JSON |
| Bulk request | 10,000 items, 15 MiB |
| List page | 1,000 keys |
| Namespaces per tenant | 1,000 |
| Time to live | 1 second to 10 years |
Go SDK
c := kv.New("https://kv.example", kv.StaticToken(token))
_ = c.CreateNamespace(ctx, "sessions", kv.Internal)
ns := c.Namespace("sessions")
v, err := ns.Put(ctx, "u:42", data, kv.TTL(30*time.Minute))
e, err := ns.Get(ctx, "u:42") // kv.ErrNotFound when absent
_, err = ns.Put(ctx, "u:42", next, kv.IfVersion(e.Version)) // kv.ErrConflict if it changed
for it := ns.List(ctx, kv.Prefix("u:")); it.Next(); {
fmt.Println(it.Item().Name)
}Speed
Measured through the HTTP service with 16 clients on a shared development server (indicative only): about 9,400 reads per second with a median of 0.74 ms, and 8,700 operations per second for a 95 % read mix. Write-heavy mixes are slower today, about 2,200 operations per second at half writes, because each write is made durable on its own; batching those writes together is the next step on the roadmap.
