Docs
  • 0.38.1latest
  • 0.37.1
  • 0.37.0
  • 0.36.2
  • 0.36.1
  • 0.36.0
  • 0.35.3
  • 0.35.2
  • 0.35.1
  • 0.35.0
  • 0.34.4
  • 0.34.3-beta
  • 0.34.2-beta
  • 0.34.1-beta
  • 0.34.0-beta
  • 0.33.4-beta
  • 0.33.3-beta
  • 0.33.2-beta
  • 0.33.1-beta
  • 0.33.0-beta
  • 0.32.0-beta

RESP clients

Point a RESP2 or RESP3 client at the node's RESP port. Its keys live in a space, its lists, hashes, sets and sorted sets are the store's own, and its streams are topics.

--resp <host:port> makes a node speak RESP, the protocol that key-value servers and their client libraries use, on port 6379 by convention. A client that speaks RESP2 or RESP3 connects to that address and works on a TessariDB space. The space is in the same store and the same transactions as everything else.

tessaridb ./data --serve 0.0.0.0:9080 \
  --resp 0.0.0.0:6379 --resp-space shop.orders.cache

The space a connection works on

A RESP connection has one space. --resp-space <namespace>.<database>.<space> (or TESSARIDB_RESP_SPACE) names the one every connection starts in. A client can change it with an extension command that any client's raw-command call can send:

> TESSARIDB.USE shop orders sessions
OK

The name is looked up when the connection next uses it, so a space that does not exist is reported then: no table named "sessions". A node started without --resp-space answers every key command with ERR no space is chosen … send TESSARIDB.USE <namespace> <database> <space>.

SELECT 0 is accepted and any other index is refused, DB index is out of range. The database number of the protocol has nothing to map to, and a space is chosen by name.

A cache server's space

For the data a cache server would hold — sessions, rate counters, hot lookups — declare the space in memory:

DEFINE NAMESPACE IF NOT EXISTS shop;
USE NAMESPACE shop;
DEFINE DATABASE IF NOT EXISTS orders;
USE DATABASE orders;
DEFINE SPACE cache IN MEMORY MAX 100000;

An IN MEMORY space is never written to the log or to disk. It is not in a backup, it is empty after a restart, and it needs MAX, because a cache with no bound runs out of memory. A durable space, declared without IN MEMORY, is served the same way and keeps its keys. The choice belongs to the space, not to the client.

What a value is stored as

A key is the space's key, so SET session:ada … writes cache:'session:ada'. A value is stored by what it holds:

A client sendsIt is stored asSo
a canonical decimal integer, 41an integerINCR is the store's own atomic increment, and TessariQL reads a number
any other UTF-8 text, helloa stringTessariQL reads a string
anything else, \xff\x00\x01bytesread back byte for byte

GET answers exactly the bytes that were sent, whichever row applies. The same key read from TessariQL:

GET cache:'visits';

42 — after SET visits 41 and INCR visits over RESP, the store holds the number, not the text.

The commands

These are the families served. The replies are the protocol's, in RESP2 or RESP3 as the connection asked with HELLO:

FamilyCommands
strings and countersGET SET (with EX PX EXAT PXAT NX XX KEEPTTL GET) SETNX SETEX PSETEX GETSET GETDEL GETEX MGET MSET MSETNX APPEND STRLEN GETRANGE SETRANGE SUBSTR LCS INCR INCRBY INCRBYFLOAT DECR DECRBY
keys and expiryDEL UNLINK EXISTS TYPE KEYS SCAN RANDOMKEY RENAME RENAMENX COPY TOUCH OBJECT EXPIRE PEXPIRE EXPIREAT PEXPIREAT EXPIRETIME PEXPIRETIME TTL PTTL PERSIST DBSIZE
hashesHSET HSETNX HMSET HGET HMGET HGETALL HDEL HEXISTS HLEN HKEYS HVALS HSTRLEN HINCRBY HINCRBYFLOAT HRANDFIELD HSCAN, and a field that expires on its own: HEXPIRE HPEXPIRE HEXPIREAT HPEXPIREAT HTTL HPTTL HEXPIRETIME HPEXPIRETIME HPERSIST
listsLPUSH RPUSH LPUSHX RPUSHX LPOP RPOP LRANGE LINDEX LSET LINSERT LLEN LREM LTRIM LPOS LMOVE RPOPLPUSH LMPOP, and blocking BLPOP BRPOP BLMOVE BRPOPLPUSH BLMPOP
setsSADD SREM SMEMBERS SISMEMBER SMISMEMBER SCARD SPOP SRANDMEMBER SMOVE SSCAN SINTER SUNION SDIFF SINTERCARD SINTERSTORE SUNIONSTORE SDIFFSTORE
sorted setsZADD ZINCRBY ZREM ZSCORE ZMSCORE ZCARD ZCOUNT ZLEXCOUNT ZRANK ZREVRANK ZRANGE ZRANGEBYSCORE ZRANGEBYLEX ZREVRANGE ZREVRANGEBYSCORE ZREVRANGEBYLEX ZRANGESTORE ZREMRANGEBYRANK ZREMRANGEBYSCORE ZREMRANGEBYLEX ZPOPMIN ZPOPMAX ZMPOP ZRANDMEMBER ZSCAN ZINTER ZUNION ZDIFF ZINTERCARD ZINTERSTORE ZUNIONSTORE ZDIFFSTORE, and blocking BZPOPMIN BZPOPMAX BZMPOP
transactionsMULTI EXEC DISCARD WATCH UNWATCH
streamsXADD XRANGE XREVRANGE XLEN XREAD XTRIM XINFO XGROUP XREADGROUP XACK XPENDING XCLAIM XAUTOCLAIM
pub/subSUBSCRIBE UNSUBSCRIBE PSUBSCRIBE PUNSUBSCRIBE SSUBSCRIBE SUNSUBSCRIBE PUBLISH SPUBLISH PUBSUB
connectionHELLO AUTH PING ECHO SELECT CLIENT RESET QUIT COMMAND INFO TIME, and TESSARIDB.USE

A hash, list, set or sorted set is one key in the space whose value is the container. Each command is one atomic statement on that key, not a read followed by a write. Asking a key for the wrong kind answers WRONGTYPE, as the protocol does.

A blocking pop waits on the store's commits rather than by polling. It is woken by any writer: another RESP client, or a TessariQL script.

What is not served

These answer ERR unknown command:

  • scripting — EVAL, FUNCTION and the rest. TessariQL is the store's language, and a script sent to the wire protocol or HTTP runs in one transaction.
  • administration — FLUSHALL, CONFIG, SAVE, REPLICAOF, DEBUG, ACL and the like. Users, backups and replication are TessariQL statements.
  • bit fields and probabilistic keys — GETBIT, BITFIELD, PFADD. For a distinct count the store has count(DISTINCT x), and approx_distinct in bounded memory.
  • geo commands — the store's shapes are their own engine, with an index and exact predicates.
  • cluster-mode commands — a TessariDB cluster is one store. Every node answers for the same space and carries a write to the node that leads it.

Transactions

MULTI … EXEC runs the queued commands as one store transaction: they commit together or not at all. WATCH makes EXEC refuse when a watched key changed first. A command the node does not serve, queued inside MULTI, discards the transaction at EXEC, as the protocol says it should.

Streams are topics

A stream key names a topic in the space's database, so it must be a plain identifier like signups, never a key such as orders:eu. XADD appends a message, which TessariQL reads with READ FROM and a Kafka consumer fetches.

> XADD signups * kind signup user 42
"1792670367016-1"

An ID is <milliseconds appended>-<position>. Both parts only ever grow, so IDs sort as the protocol requires, and an ID leads straight to its message. XRANGE and XTRIM … MINID bounded by a time alone are exact.

A group is a topic group. XREADGROUP hands out messages and holds them until XACK. XPENDING, XCLAIM and XAUTOCLAIM read and move what is held, and XINFO describes the stream, its groups and their consumers.

Where the store's rules differ from the protocol's, the store's win:

You might expectWhat happens
XADD key 1700000000000-1 … with your own IDrefused: a position is decided when the message commits
XDELrefused: a topic never has a hole. XTRIM removes from the front, which readers are told about
XSETIDrefused, for the same reason
fields in the order sentfields in name order: a message is an object
XREADGROUP … NOACK in one stepthe read and its acknowledgement, in one transaction
XCLAIM's IDLE, TIME and RETRYCOUNT optionsaccepted and not applied
XGROUP SETID keeping what is pendingit moves the group and forgets what it held
a stream on a topic split into partitionsrefused by name: a stream is one order

Pub/sub

PUBLISH reaches the subscribers connected to this node, in memory and never stored, as the protocol promises. A channel belongs to the space's database, so two tenants with a news channel never hear each other. Shard channels (SSUBSCRIBE, SPUBLISH) are a separate set of names, as in the protocol.

Each subscription is checked against the user's grants: subscribing needs read on the space and publishing needs write. The check is repeated while the connection is subscribed, so a revoked grant ends its subscriptions.

A subscriber that stops reading has a bounded queue. When the queue is full, that subscriber is disconnected rather than silently missing messages, and publishers are not slowed down.

Signing in

AUTH user password, or HELLO 3 AUTH user password, signs in as a store user. The connection then runs under that user's grants, on every command:

> AUTH reporting a-real-password
OK
> GET visits
"43"
> SET price 10
(error) NOPERM a viewer may not write (at 38..57)
> PUBLISH news hello
(error) NOPERM a viewer may not write (at 45..64)

A wrong password is answered WRONGPASS. A command sent before signing in to a store that has users is answered NOAUTH this store requires a signed-in user.

A single-argument AUTH password signs in as the user default, if the store has one. On a store with no users at all, every connection may work without signing in, as on every other surface.

TLS

A node given --tls-cert and --tls-key serves the RESP port over TLS 1.3 with the same certificate as its other surfaces. Point the client at it with TLS on and the issuing authority given. --require-client-tls refuses to start a node whose RESP port would otherwise be in the clear. See encryption in transit.

On a cluster

A RESP client may connect to any node. A write sent to a node that does not lead the space's range is carried to the node that does, and the reply comes back on the same connection.

An IN MEMORY space is held by the node leading its range. That is one copy for every client, and it is lost on a failover, as a cache server's memory is lost on a restart.

Next

  • Spaces — the key-value engine underneath, with expiry, counters, conditional writes and limits.
  • Kafka clients — the other protocol a node speaks.