Skip to main content

KV Tables

The third table family. A timeseries table observes, a rows table registers; a kv table stores — one value per user-supplied string key, the Redis shape. Session tokens, leases, static-IP maps, presence, feature flags: the data an IoT stack usually parks in a separate Redis lives here, next to the registry and the telemetry.

CREATE KV TABLE cache (val TEXT, hits INT);
CREATE KV TABLE sessions (token TEXT, device TEXT) WITH (ttl='1h');

The value is ordinary columns with the same type labels as everywhere (TEXT, DOUBLE, BOOL, INT); WITH (ttl='…') is the only table option. FILL and STALENESS are read-time mechanics of the time axis — a kv table refuses them.

The key is yours

Every kv table carries an implicit string column key — the mirror image of a rows table, where the id belongs to the engine. Here you choose the address, and writing to the same key overwrites: INSERT is the upsert. The Redis verbs map onto SQL you already speak:

Redisxcon-db
SET k …INSERT INTO t (key, …) VALUES ('k', …)
GET kSELECT … FROM t WHERE key = 'k'
DEL kDELETE FROM t WHERE key = 'k'
SCANSELECT * FROM t — every live key, ordered
DBSIZESELECT count(*) FROM t

key is reserved: it cannot be declared as a column, and it must be a string. Result tags say what happened — SET 2, DEL 1.

SET — the upsert

INSERT INTO cache (key, val, hits) VALUES ('a', 'apple', 1); -- SET 1
INSERT INTO cache (key, val, hits) VALUES ('a', 'apricot', 5); -- same key: overwrite
INSERT INTO cache (key, hits) VALUES ('a', 6); -- partial: val stays 'apricot'

A SET that lists a subset of columns updates just those; the others keep their values (every column is an independent stream underneath). Multi-row VALUES sets many keys in one statement.

GET, scan, count

SELECT val, hits FROM cache WHERE key = 'a'; -- GET: the point read
SELECT * FROM cache; -- scan: every live key
SELECT count(*) FROM cache; -- how many live keys

The key is the only filter. A scan comes back ordered by key (ascending), and LIMIT/OFFSET page it deterministically. count(*) is the one aggregate a kv table answers — it is a store, not an analytics table.

DEL

DELETE FROM cache WHERE key = 'b'; -- DEL 1

The key leaves every read immediately (a tombstone, physically dropped at the next merge). SET the same key later and it simply exists again.

TTL — the lease

CREATE KV TABLE sessions (token TEXT) WITH (ttl='30m'); -- s, m, h, d

With a TTL every key is a lease: a key whose last SET is older than the TTL expires lazily on read — GET returns nothing, scans and count(*) skip it. Any SET refreshes the clock, a partial SET too, so a ttl='1h' table is exactly a Redis session store: touch the key and it lives another hour.

No feed, no time axis

A kv table is a cache/store, not a replicated log: there is no sync feed and no x_rev/x_deleted — that is the rows family's contract. FILL, STALENESS, SAMPLE BY, LATEST, entity/ts predicates and ILP ingest are timeseries concepts; kv refuses them all. Internally it rides the same mutable machinery as everything else — WAL, checkpoint and the backup contract are identical, and the merge worker keeps one value per key, so the store never grows without bound.

The Redis protocol (RESP) is not spoken: a kv table is reached over pg-wire SQL like every other table — which means every language's PostgreSQL driver is already the client.