Skip to content

Redis Compatibility

Flo speaks RESP (the Redis serialization protocol) on the same TCP port as its native protocol. The Acceptor detects RESP by the leading byte (*, $, +, -, :) and dispatches to a translation layer that maps each command onto an equivalent Flo opcode. No proxy, no separate port, no additional configuration.

This makes Flo a drop-in target for tools that already speak Redis — redis-cli, RedisInsight, ioredis, go-redis, redis-py, Lettuce, etc. — for the subset of commands listed below.

RedisFlo opcodeNotes
GET keykv_getReturns nil if missing.
SET key valuekv_put
SET key value EX secondskv_put + TTLTTL TLV.
SET key value NXkv_put + if_not_exists
SET key value XXkv_put + if_exists
DEL key [key ...]kv_deletePer-key.
EXISTS keykv_exists
EXPIRE key secondskv_touch
PEXPIRE key mskv_touchRounded down to seconds.
PERSIST keykv_persist
TTL key(synthesized)Computed from key metadata.
RedisFlo opcodeNotes
INCR keykv_incr (delta=1)
INCRBY key nkv_incr (delta=n)Signed i64.
DECR keykv_incr (delta=-1)
DECRBY key nkv_incr (delta=-n)

A counter and a string value cannot share a key. Mixing returns WRONGTYPE-style Conflict.

RedisFlo opcodeNotes
JSON.GET key [path]kv_json_getPath defaults to $.
JSON.SET key path valuekv_json_setAtomic per-Raft-entry sub-field set.
JSON.DEL key [path]kv_json_del

Path syntax is a small JSONPath subset: $, .field, .field.nested, [index].

RedisFlo opcodeNotes
SCAN cursor MATCH patternkv_scanPrefix-only; * suffix supported.
KEYS patternkv_scan (drained)Avoid in production — scans the whole keyspace.
RedisFlo opcodeNotes
LPUSH queue valuequeue_enqueue
RPOP queuequeue_dequeueSingle-message dequeue.
BRPOP queue timeoutqueue_dequeue + block_msLong-poll.

Flo queues are leased / acked, not auto-removed. After RPOP, the message is held under a lease until ack/nack/expiry. See Queues.

RedisFlo opcodeNotes
XADD stream * field valuestream_append
XREAD COUNT n STREAMS stream idstream_read
XLEN streamstream_info

Flo does not implement multi-key transactions. The WATCH/MULTI/EXEC pattern translates idiomatically to Flo’s CAS:

# Redis
WATCH counter
val = GET counter
MULTI
SET counter (val+1)
EXEC
# Flo equivalent (single Raft entry, no coordinator)
r = kv.get("counter") # returns value + version
kv.put("counter", new_val,
cas_version = r.version) # fails with Conflict if version moved

For atomic increments specifically, prefer INCR / kv.incr — it’s unconditional and never conflicts.

AreaBehavior
PersistenceEvery write is Raft-replicated and committed to UAL before responding. There is no SAVE/BGSAVE — Flo is always durable.
EvictionNo maxmemory LRU eviction. Out-of-memory rejects new writes; configure TTLs explicitly.
Pub/SubNot supported via RESP. Use Streams or Streaming Updates.
Lua scriptingEVAL/EVALSHA not supported. Use WASM Processing for server-side logic.
Cluster slotsCLUSTER commands return a single-slot response. Flo’s partitioning is internal and works without client awareness.
TransactionsMULTI/EXEC returns an error. Use CAS or model multi-field state as a single JSON document.
OBJECT ENCODING etc.Not supported.
Terminal window
# redis-cli
redis-cli -h flo.example.com -p 9000
127.0.0.1:9000> SET hello world
OK
127.0.0.1:9000> INCR counter
(integer) 1
127.0.0.1:9000> JSON.SET order:42 $ '{"items":3,"status":"new"}'
OK
127.0.0.1:9000> JSON.SET order:42 $.status '"shipped"'
OK
127.0.0.1:9000> JSON.GET order:42 $.status
"\"shipped\""

Any RESP client library should connect with the same host/port settings you’d give a Redis instance. There is no AUTH negotiation today — see Security for transport-level options.