Ultra fast, persistent database supporting Valkey API
See the codeSableDB?SableDB is a key-value NoSQL database that utilizes RocksDB as its storage engine and is compatible with the Valkey protocol.
It aims to reduce memory costs and increase capacity compared to Valkey. SableDB features include Valkey-compatible access via
any Valkey client, up to 64K databases support, asynchronous replication using transaction log tailing and TLS connectivity support.
SableDB is supported on all major OS: Linux, macOS and Windows
Make sure you have clang installed and then:
git clone https://github.com/sabledb-io/sabledb.git
cd sabledb
git submodule update --init
cargo build --release
cargo test --release
# RocksDB requires clang
sudo apt install -y clang
git clone https://github.com/sabledb-io/sabledb.git
cd sabledb
git submodule update --init
cargo build --release
cargo test --release
On Windows, we require MSYS2 terminal for building SableDB.
First, ensure that you have the required toolchain (beside Rust):
pacman -Sy git \
mingw-w64-clang-x86_64-toolchain \
mingw-w64-clang-x86_64-python3 \
mingw-w64-clang-x86_64-cmake \
mingw-w64-clang-x86_64-libffi \
unzip \
mingw-w64-clang-x86_64-rust-bindgen \
mingw-w64-clang-x86_64-nasm \
mingw-w64-clang-x86_64-gcc-compat
git clone https://github.com/sabledb-io/sabledb.git
cd sabledb
git submodule update --init
cargo build --release
cargo test --release
SableDB./target/release/sabledb
Usage:
$target/release/sabledb [sabledb.ini]
docker build -t sabledb:latest .
docker run -p 6379:6379 sabledb:latest
If you prefer to use docker-compose, you can use the following command:
docker compose up --build
To tail the logs, use the below command:
docker exec -it sabledb-sabledb-1 /bin/bash -c "tail -f /var/lib/sabledb/log/sabledb.log.*"
Note: the container name (in the above example: sabledb-sabledb-1) can be found using the command docker ps
SableDB as a persistent storage using Valkey's APIsb command line utility (target/release/sb) for performance testingMULTI / EXEC )AUDIT.APPEND / AUDIT.RANGE )sbSableDB uses its own benchmarking tool named sb. sb supports the following commands:
setgetlpushlpoprpushrpopincrpinghsetsetget (a mix between SET:GET commands, the ratio is controlled by the --setget-ratio option. The default is set to 1:4)Run sb --help to get the full help message.
Below is a simple ( ping ) test conducted locally using WSL2 on Windows 10 (same machine is running both SableDB and sb...):

set test, on the same set-up (local machine, WSL2 on Windows 10):

IMPORTANT
SableDB is under constant development, if you are missing a command, feel free to open an issue
and visit this page again in couple of days
| Command | Supported | Fully supported? | Comment |
|---|---|---|---|
| append | ✓ | ✓ | |
| decr | ✓ | ✓ | |
| decrby | ✓ | ✓ | |
| get | ✓ | ✓ | |
| getdel | ✓ | ✓ | |
| getex | ✓ | ✓ | |
| getrange | ✓ | ✓ | |
| getset | ✓ | ✓ | |
| incr | ✓ | ✓ | |
| incrby | ✓ | ✓ | |
| incrbyfloat | ✓ | ✓ | |
| lcs | ✓ | x | Does not support: IDX, MINMATCHLEN and WITHMATCHLEN |
| mget | ✓ | ✓ | |
| mset | ✓ | ✓ | |
| msetnx | ✓ | ✓ | |
| psetex | ✓ | ✓ | |
| set | ✓ | ✓ | |
| setex | ✓ | ✓ | |
| setnx | ✓ | ✓ | |
| setrange | ✓ | ✓ | |
| strlen | ✓ | ✓ | |
| substr | ✓ | ✓ |
| Command | Supported | Fully supported? | Comment |
|---|---|---|---|
| blmove | ✓ | ✓ | |
| blmpop | ✓ | ✓ | |
| blpop | ✓ | ✓ | |
| brpop | ✓ | ✓ | |
| brpoplpush | ✓ | ✓ | |
| lindex | ✓ | ✓ | |
| linsert | ✓ | ✓ | |
| llen | ✓ | ✓ | |
| lmove | ✓ | ✓ | |
| lmpop | ✓ | ✓ | |
| lpop | ✓ | ✓ | |
| lpos | ✓ | ✓ | |
| lpush | ✓ | ✓ | |
| lpushx | ✓ | ✓ | |
| lrange | ✓ | ✓ | |
| lrem | ✓ | ✓ | |
| lset | ✓ | ✓ | |
| ltrim | ✓ | ✓ | |
| rpop | ✓ | ✓ | |
| rpoplpush | ✓ | ✓ | |
| rpush | ✓ | ✓ | |
| rpushx | ✓ | ✓ |
| Command | Supported | Fully supported? | Comment |
|---|---|---|---|
| hset | ✓ | ✓ | |
| hget | ✓ | ✓ | |
| hmget | ✓ | ✓ | |
| hmset | ✓ | ✓ | |
| hgetall | ✓ | ✓ | |
| hdel | ✓ | ✓ | |
| hlen | ✓ | ✓ | |
| hexists | ✓ | ✓ | |
| hincrby | ✓ | ✓ | |
| hincrbyfloat | ✓ | ✓ | |
| hkeys | ✓ | ✓ | |
| hvals | ✓ | ✓ | |
| hrandfield | ✓ | ✓ | |
| hscan | ✓ | ✓ | |
| hsetnx | ✓ | ✓ | |
| hstrlen | ✓ | ✓ |
ZSET) commands| Command | Supported | Fully supported? | Comment |
|---|---|---|---|
| bzmpop | ✓ | ✓ | |
| bzpopmax | ✓ | ✓ | |
| bzpopmin | ✓ | ✓ | |
| zadd | ✓ | ✓ | |
| zcard | ✓ | ✓ | |
| zincrby | ✓ | ✓ | |
| zcount | ✓ | ✓ | |
| zdiff | ✓ | ✓ | |
| zdiffstore | ✓ | ✓ | |
| zinter | ✓ | ✓ | |
| zintercard | ✓ | ✓ | |
| zinterstore | ✓ | ✓ | |
| zlexcount | ✓ | ✓ | |
| zmpop | ✓ | ✓ | |
| zmscore | ✓ | ✓ | |
| zpopmax | ✓ | ✓ | |
| zpopmin | ✓ | ✓ | |
| zrandmember | ✓ | ✓ | |
| zrangebyscore | ✓ | ✓ | |
| zrevrangebyscore | ✓ | ✓ | |
| zrangebylex | ✓ | ✓ | |
| zrevrangebylex | ✓ | ✓ | |
| zrange | ✓ | ✓ | |
| zrangestore | ✓ | ✓ | |
| zrank | ✓ | ✓ | |
| zrem | ✓ | ✓ | |
| zremrangebylex | ✓ | ✓ | |
| zremrangebyrank | ✓ | ✓ | |
| zremrangebyscore | ✓ | ✓ | |
| zrevrange | ✓ | ✓ | |
| zrevrank | ✓ | ✓ | |
| zunion | ✓ | ✓ | |
| zunionstore | ✓ | ✓ | |
| zscore | ✓ | ✓ | |
| zscan | ✓ | ✓ |
| Command | Supported | Fully supported? | Comment |
|---|---|---|---|
| sadd | ✓ | ✓ | |
| scard | ✓ | ✓ | |
| sdiff | ✓ | ✓ | |
| sdiffstore | ✓ | ✓ | |
| sinter | ✓ | ✓ | |
| sintercard | ✓ | ✓ | |
| sinterstore | ✓ | ✓ | |
| sismember | ✓ | ✓ | |
| smismember | ✓ | ✓ | |
| smembers | ✓ | ✓ | |
| smove | ✓ | ✓ | |
| spop | ✓ | ✓ | |
| srandmember | ✓ | ✓ | |
| srem | ✓ | ✓ | |
| sscan | ✓ | ✓ | |
| sunion | ✓ | ✓ | |
| sunionstore | ✓ | ✓ |
| Command | Supported | Fully supported? | Comment |
|---|---|---|---|
| del | ✓ | ✓ | |
| ttl | ✓ | ✓ | |
| exists | ✓ | ✓ | |
| expire | ✓ | ✓ | |
| keys | ✓ | x | Pattern uses wildcard match ( ? and * ) |
| scan | ✓ | x | Pattern uses wildcard match ( ? and * ) |
| Command | Supported | Fully supported? | Comment |
|---|---|---|---|
| info | ✓ | ✓ | SableDB has its own INFO output format |
| ping | ✓ | ✓ | |
| replicaof | ✓ | ✓ | |
| slaveof | ✓ | ✓ | |
| command | ✓ | ✓ | |
| command docs | ✓ | x | |
| flushall | ✓ | ✓ | |
| flushdb | ✓ | ✓ | |
| dbsize | ✓ | ✓ | Data is accurate for the last scan performed on the storage |
slot count SLOT_NUM | ✓ | ✓ | An extension command. Count how many keys map to SLOT_NUM |
slot calc KEY | ✓ | ✓ | An extension command. Return the slot number for a given KEY |
slot sendto NODE_ID SLOT_NUM | ✓ | ✓ | An extension command. Send slot SLOT_NUM to node NODE_ID |
| Command | Supported | Fully supported? | Comment |
|---|---|---|---|
| multi | ✓ | ✓ | |
| exec | ✓ | ✓ | |
| discard | ✓ | ✓ | |
| watch | ✓ | ✓ | |
| unwatch | ✓ | ✓ |
| Command | Supported | Fully supported? | Comment |
|---|---|---|---|
| client id | ✓ | ✓ | |
| client kill | ✓ | x | supports: client kill ID <client-id> |
| hello | ✓ | x | RESP2 only, AUTH is not supported |
| select | ✓ | ✓ | |
| ping | ✓ | ✓ |
| Command | Supported | Fully supported? | Comment |
|---|---|---|---|
| cluster nodes | ✓ | ✓ | |
| cluster myid | ✓ | ✓ |
SableDB offers locking capabilities for application that requires it. Locks in SableDB are ephemeral data commands,
this means, that the data is not persistent and is not replicated from primary to replica. These commands are marked as
readonly so they can be used with replica servers.
Note about the locks:
DEADLOCK errorLOCK command can be a blocking command if timeout is providedSableDBThe syntax is:
LOCK <LOCK-NAME> [TIMEOUT-MS]
UNLOCK <LOCK-NAME>
| Command | Supported | Fully supported? | Comment |
|---|---|---|---|
| LOCK | ✓ | ✓ | If timeout is provided, this is a blocking command |
| UNLOCK | ✓ | ✓ |
SableDB provides an append-only, per-key audit trail: a building block for future higher-level
features (e.g. a task-coordination layer) that need a persisted, ordered history of events.
Entries are never individually deleted or reordered -- delete the whole trail with the generic
DEL command, and its entries are reclaimed by the same background eviction mechanism used for
overwritten Lists/Hashes/Sets/Zsets.
Alongside each trail, SableDB also maintains a lightweight feed of AuditLog create/delete events
across all task-ids, so a client can discover new activity without already knowing which task-ids
to look at (e.g. to drive a live "activity feed" table view).
The syntax is:
AUDIT.APPEND <task-id> <event> [<details>]
AUDIT.RANGE <task-id> [FROM <seq>] [LIMIT <n>]
AUDIT.FEED [FROM <seq>] [LIMIT <n>]
AUDIT.APPEND appends a new entry and returns its sequence number (0-based, per task-id).AUDIT.RANGE returns entries in append order, each as [sequence, timestamp_ms, event, details],
starting at sequence FROM (default 0) and returning at most LIMIT entries (default 100).AUDIT.FEED returns [sequence, timestamp_ms, kind, task-id] rows (kind is created or
deleted), oldest first, starting at feed sequence FROM (default 0) and returning at most
LIMIT rows (default: unbounded). This feed is shard-local: sequence comes from a
per-node counter, so it is only meaningful for paging one shard's feed, not for merging feeds
from different nodes -- a client polling a cluster should discover shards via CLUSTER NODES,
track a cursor per shard, and merge by timestamp_ms instead. A task's created row is replaced
by its deleted row when the trail is deleted (not left alongside it), so the feed only grows
without bound for trails that are created and never deleted -- there is still no eviction or
retention policy for that portion.DEL <task-id>. DEL detects when a key holds an AuditLog and routes it
through the same path AuditDb::delete() uses internally, so the AUDIT.FEED bookkeeping above
(replacing the created row with a deleted row) still happens -- deleting an AuditLog isn't
special-cased at the protocol level, just under the hood.Time complexity
| Command | Complexity | Notes |
|---|---|---|
AUDIT.APPEND | O(1) | One metadata read, one entry write; on a task's first append only, one extra Bookkeeping write and one extra feed-index write. |
AUDIT.RANGE | O(LIMIT) | Seeks directly to FROM's own key rather than scanning from the start of the trail, so cost does not grow with how far into the trail FROM points -- only with how many entries are actually returned. |
AUDIT.FEED | O(LIMIT) | Same reasoning as AUDIT.RANGE: FROM seeks directly to the target row instead of scanning the feed from the beginning. |
DEL (AuditLog key) | O(1) | Deleting the container and swapping the feed row (deleting the old one, writing the new one) are both direct key operations by a stored key, not a scan. The trail's individual entries are not deleted synchronously -- they're left as orphans and reclaimed later by the background eviction sweep (O(total orphaned entries) at that point, off the request path). |
| Command | Supported | Fully supported? | Comment |
|---|---|---|---|
| AUDIT.APPEND | ✓ | ✓ | |
| AUDIT.RANGE | ✓ | ✓ | |
| AUDIT.FEED | ✓ | ✓ | Shard-local; no retention/eviction policy yet |
Processor: AMD Ryzen 9 7950X 16-Core Processor 4.50 GHz
Installed RAM: 64.0 GB (63.2 GB usable)
System type: 64-bit operating system, x64-based processor
Disk: Crucial T700, 2TB PCIe Gen5 NVMe M.2 SSD
get tests used randomg keys in the range of 0000001 - 5000000 (key size = 7Bytes)get & setget test:
setCommand used:
sb --threads 6 -c 512 -t set -n 5000000 -r 5000000 -d <64|128|256>
| Payload size (bytes) | rps | p50 (ms) | p90 (ms) | p99 (ms) |
|---|---|---|---|---|
| 64 | 781K | 0.559ms | 0.751ms | 1.919ms |
| 128 | 715K | 0.595ms | 0.919ms | 2.015ms |
| 256 | 656K | 0.583ms | 1.359ms | 2.007ms |
getCommand used:
sb --threads 6 -c 512 -t set -n 5000000 -r 5000000 -d <64|128|256> -z
| Payload size (bytes) | rps | p50 (ms) | p90 (ms) | p99 (ms) |
|---|---|---|---|---|
| 64 | 1.04M | 0.457ms | 0.607ms | 0.783ms |
| 128 | 1.03M | 0.469ms | 0.607ms | 0.735ms |
| 256 | 931K | 0.483ms | 0.635ms | 0.795ms |
setget with 1:4 ratio (1 SET for every 4 GET calls)Command used:
sb --threads 6 -c 512 -t setget -n 5000000 -r 5000000 -d <64|128|256> -z
| Payload size (bytes) | rps | p50 (ms) | p90 (ms) | p99 (ms) |
|---|---|---|---|---|
| 64 | 923K | 0.483ms | 0.635ms | 1.239ms |
| 128 | 911K | 0.481ms | 0.639ms | 1.495ms |
| 256 | 787K | 0.563ms | 0.755ms | 1.727ms |
ping command)| Command | rps | pipeline | p50 (ms) | p90 (ms) | p99 (ms) |
|---|---|---|---|---|---|
| ping | 1.6M | 1 | 0.407 | 0.775 | 1.143 |
7,684 followers · starred Apr 2024
390 followers · starred Apr 2024
145 followers · starred Apr 2024
1,683 followers · starred Apr 2024
Ultra fast, persistent database supporting Valkey API
See the codeSableDB?SableDB is a key-value NoSQL database that utilizes RocksDB as its storage engine and is compatible with the Valkey protocol.
It aims to reduce memory costs and increase capacity compared to Valkey. SableDB features include Valkey-compatible access via
any Valkey client, up to 64K databases support, asynchronous replication using transaction log tailing and TLS connectivity support.
SableDB is supported on all major OS: Linux, macOS and Windows
Make sure you have clang installed and then:
git clone https://github.com/sabledb-io/sabledb.git
cd sabledb
git submodule update --init
cargo build --release
cargo test --release
# RocksDB requires clang
sudo apt install -y clang
git clone https://github.com/sabledb-io/sabledb.git
cd sabledb
git submodule update --init
cargo build --release
cargo test --release
On Windows, we require MSYS2 terminal for building SableDB.
First, ensure that you have the required toolchain (beside Rust):
pacman -Sy git \
mingw-w64-clang-x86_64-toolchain \
mingw-w64-clang-x86_64-python3 \
mingw-w64-clang-x86_64-cmake \
mingw-w64-clang-x86_64-libffi \
unzip \
mingw-w64-clang-x86_64-rust-bindgen \
mingw-w64-clang-x86_64-nasm \
mingw-w64-clang-x86_64-gcc-compat
git clone https://github.com/sabledb-io/sabledb.git
cd sabledb
git submodule update --init
cargo build --release
cargo test --release
SableDB./target/release/sabledb
Usage:
$target/release/sabledb [sabledb.ini]
docker build -t sabledb:latest .
docker run -p 6379:6379 sabledb:latest
If you prefer to use docker-compose, you can use the following command:
docker compose up --build
To tail the logs, use the below command:
docker exec -it sabledb-sabledb-1 /bin/bash -c "tail -f /var/lib/sabledb/log/sabledb.log.*"
Note: the container name (in the above example: sabledb-sabledb-1) can be found using the command docker ps
SableDB as a persistent storage using Valkey's APIsb command line utility (target/release/sb) for performance testingMULTI / EXEC )AUDIT.APPEND / AUDIT.RANGE )sbSableDB uses its own benchmarking tool named sb. sb supports the following commands:
setgetlpushlpoprpushrpopincrpinghsetsetget (a mix between SET:GET commands, the ratio is controlled by the --setget-ratio option. The default is set to 1:4)Run sb --help to get the full help message.
Below is a simple ( ping ) test conducted locally using WSL2 on Windows 10 (same machine is running both SableDB and sb...):

set test, on the same set-up (local machine, WSL2 on Windows 10):

IMPORTANT
SableDB is under constant development, if you are missing a command, feel free to open an issue
and visit this page again in couple of days
| Command | Supported | Fully supported? | Comment |
|---|---|---|---|
| append | ✓ | ✓ | |
| decr | ✓ | ✓ | |
| decrby | ✓ | ✓ | |
| get | ✓ | ✓ | |
| getdel | ✓ | ✓ | |
| getex | ✓ | ✓ | |
| getrange | ✓ | ✓ | |
| getset | ✓ | ✓ | |
| incr | ✓ | ✓ | |
| incrby | ✓ | ✓ | |
| incrbyfloat | ✓ | ✓ | |
| lcs | ✓ | x | Does not support: IDX, MINMATCHLEN and WITHMATCHLEN |
| mget | ✓ | ✓ | |
| mset | ✓ | ✓ | |
| msetnx | ✓ | ✓ | |
| psetex | ✓ | ✓ | |
| set | ✓ | ✓ | |
| setex | ✓ | ✓ | |
| setnx | ✓ | ✓ | |
| setrange | ✓ | ✓ | |
| strlen | ✓ | ✓ | |
| substr | ✓ | ✓ |
| Command | Supported | Fully supported? | Comment |
|---|---|---|---|
| blmove | ✓ | ✓ | |
| blmpop | ✓ | ✓ | |
| blpop | ✓ | ✓ | |
| brpop | ✓ | ✓ | |
| brpoplpush | ✓ | ✓ | |
| lindex | ✓ | ✓ | |
| linsert | ✓ | ✓ | |
| llen | ✓ | ✓ | |
| lmove | ✓ | ✓ | |
| lmpop | ✓ | ✓ | |
| lpop | ✓ | ✓ | |
| lpos | ✓ | ✓ | |
| lpush | ✓ | ✓ | |
| lpushx | ✓ | ✓ | |
| lrange | ✓ | ✓ | |
| lrem | ✓ | ✓ | |
| lset | ✓ | ✓ | |
| ltrim | ✓ | ✓ | |
| rpop | ✓ | ✓ | |
| rpoplpush | ✓ | ✓ | |
| rpush | ✓ | ✓ | |
| rpushx | ✓ | ✓ |
| Command | Supported | Fully supported? | Comment |
|---|---|---|---|
| hset | ✓ | ✓ | |
| hget | ✓ | ✓ | |
| hmget | ✓ | ✓ | |
| hmset | ✓ | ✓ | |
| hgetall | ✓ | ✓ | |
| hdel | ✓ | ✓ | |
| hlen | ✓ | ✓ | |
| hexists | ✓ | ✓ | |
| hincrby | ✓ | ✓ | |
| hincrbyfloat | ✓ | ✓ | |
| hkeys | ✓ | ✓ | |
| hvals | ✓ | ✓ | |
| hrandfield | ✓ | ✓ | |
| hscan | ✓ | ✓ | |
| hsetnx | ✓ | ✓ | |
| hstrlen | ✓ | ✓ |
ZSET) commands| Command | Supported | Fully supported? | Comment |
|---|---|---|---|
| bzmpop | ✓ | ✓ | |
| bzpopmax | ✓ | ✓ | |
| bzpopmin | ✓ | ✓ | |
| zadd | ✓ | ✓ | |
| zcard | ✓ | ✓ | |
| zincrby | ✓ | ✓ | |
| zcount | ✓ | ✓ | |
| zdiff | ✓ | ✓ | |
| zdiffstore | ✓ | ✓ | |
| zinter | ✓ | ✓ | |
| zintercard | ✓ | ✓ | |
| zinterstore | ✓ | ✓ | |
| zlexcount | ✓ | ✓ | |
| zmpop | ✓ | ✓ | |
| zmscore | ✓ | ✓ | |
| zpopmax | ✓ | ✓ | |
| zpopmin | ✓ | ✓ | |
| zrandmember | ✓ | ✓ | |
| zrangebyscore | ✓ | ✓ | |
| zrevrangebyscore | ✓ | ✓ | |
| zrangebylex | ✓ | ✓ | |
| zrevrangebylex | ✓ | ✓ | |
| zrange | ✓ | ✓ | |
| zrangestore | ✓ | ✓ | |
| zrank | ✓ | ✓ | |
| zrem | ✓ | ✓ | |
| zremrangebylex | ✓ | ✓ | |
| zremrangebyrank | ✓ | ✓ | |
| zremrangebyscore | ✓ | ✓ | |
| zrevrange | ✓ | ✓ | |
| zrevrank | ✓ | ✓ | |
| zunion | ✓ | ✓ | |
| zunionstore | ✓ | ✓ | |
| zscore | ✓ | ✓ | |
| zscan | ✓ | ✓ |
| Command | Supported | Fully supported? | Comment |
|---|---|---|---|
| sadd | ✓ | ✓ | |
| scard | ✓ | ✓ | |
| sdiff | ✓ | ✓ | |
| sdiffstore | ✓ | ✓ | |
| sinter | ✓ | ✓ | |
| sintercard | ✓ | ✓ | |
| sinterstore | ✓ | ✓ | |
| sismember | ✓ | ✓ | |
| smismember | ✓ | ✓ | |
| smembers | ✓ | ✓ | |
| smove | ✓ | ✓ | |
| spop | ✓ | ✓ | |
| srandmember | ✓ | ✓ | |
| srem | ✓ | ✓ | |
| sscan | ✓ | ✓ | |
| sunion | ✓ | ✓ | |
| sunionstore | ✓ | ✓ |
| Command | Supported | Fully supported? | Comment |
|---|---|---|---|
| del | ✓ | ✓ | |
| ttl | ✓ | ✓ | |
| exists | ✓ | ✓ | |
| expire | ✓ | ✓ | |
| keys | ✓ | x | Pattern uses wildcard match ( ? and * ) |
| scan | ✓ | x | Pattern uses wildcard match ( ? and * ) |
| Command | Supported | Fully supported? | Comment |
|---|---|---|---|
| info | ✓ | ✓ | SableDB has its own INFO output format |
| ping | ✓ | ✓ | |
| replicaof | ✓ | ✓ | |
| slaveof | ✓ | ✓ | |
| command | ✓ | ✓ | |
| command docs | ✓ | x | |
| flushall | ✓ | ✓ | |
| flushdb | ✓ | ✓ | |
| dbsize | ✓ | ✓ | Data is accurate for the last scan performed on the storage |
slot count SLOT_NUM | ✓ | ✓ | An extension command. Count how many keys map to SLOT_NUM |
slot calc KEY | ✓ | ✓ | An extension command. Return the slot number for a given KEY |
slot sendto NODE_ID SLOT_NUM | ✓ | ✓ | An extension command. Send slot SLOT_NUM to node NODE_ID |
| Command | Supported | Fully supported? | Comment |
|---|---|---|---|
| multi | ✓ | ✓ | |
| exec | ✓ | ✓ | |
| discard | ✓ | ✓ | |
| watch | ✓ | ✓ | |
| unwatch | ✓ | ✓ |
| Command | Supported | Fully supported? | Comment |
|---|---|---|---|
| client id | ✓ | ✓ | |
| client kill | ✓ | x | supports: client kill ID <client-id> |
| hello | ✓ | x | RESP2 only, AUTH is not supported |
| select | ✓ | ✓ | |
| ping | ✓ | ✓ |
| Command | Supported | Fully supported? | Comment |
|---|---|---|---|
| cluster nodes | ✓ | ✓ | |
| cluster myid | ✓ | ✓ |
SableDB offers locking capabilities for application that requires it. Locks in SableDB are ephemeral data commands,
this means, that the data is not persistent and is not replicated from primary to replica. These commands are marked as
readonly so they can be used with replica servers.
Note about the locks:
DEADLOCK errorLOCK command can be a blocking command if timeout is providedSableDBThe syntax is:
LOCK <LOCK-NAME> [TIMEOUT-MS]
UNLOCK <LOCK-NAME>
| Command | Supported | Fully supported? | Comment |
|---|---|---|---|
| LOCK | ✓ | ✓ | If timeout is provided, this is a blocking command |
| UNLOCK | ✓ | ✓ |
SableDB provides an append-only, per-key audit trail: a building block for future higher-level
features (e.g. a task-coordination layer) that need a persisted, ordered history of events.
Entries are never individually deleted or reordered -- delete the whole trail with the generic
DEL command, and its entries are reclaimed by the same background eviction mechanism used for
overwritten Lists/Hashes/Sets/Zsets.
Alongside each trail, SableDB also maintains a lightweight feed of AuditLog create/delete events
across all task-ids, so a client can discover new activity without already knowing which task-ids
to look at (e.g. to drive a live "activity feed" table view).
The syntax is:
AUDIT.APPEND <task-id> <event> [<details>]
AUDIT.RANGE <task-id> [FROM <seq>] [LIMIT <n>]
AUDIT.FEED [FROM <seq>] [LIMIT <n>]
AUDIT.APPEND appends a new entry and returns its sequence number (0-based, per task-id).AUDIT.RANGE returns entries in append order, each as [sequence, timestamp_ms, event, details],
starting at sequence FROM (default 0) and returning at most LIMIT entries (default 100).AUDIT.FEED returns [sequence, timestamp_ms, kind, task-id] rows (kind is created or
deleted), oldest first, starting at feed sequence FROM (default 0) and returning at most
LIMIT rows (default: unbounded). This feed is shard-local: sequence comes from a
per-node counter, so it is only meaningful for paging one shard's feed, not for merging feeds
from different nodes -- a client polling a cluster should discover shards via CLUSTER NODES,
track a cursor per shard, and merge by timestamp_ms instead. A task's created row is replaced
by its deleted row when the trail is deleted (not left alongside it), so the feed only grows
without bound for trails that are created and never deleted -- there is still no eviction or
retention policy for that portion.DEL <task-id>. DEL detects when a key holds an AuditLog and routes it
through the same path AuditDb::delete() uses internally, so the AUDIT.FEED bookkeeping above
(replacing the created row with a deleted row) still happens -- deleting an AuditLog isn't
special-cased at the protocol level, just under the hood.Time complexity
| Command | Complexity | Notes |
|---|---|---|
AUDIT.APPEND | O(1) | One metadata read, one entry write; on a task's first append only, one extra Bookkeeping write and one extra feed-index write. |
AUDIT.RANGE | O(LIMIT) | Seeks directly to FROM's own key rather than scanning from the start of the trail, so cost does not grow with how far into the trail FROM points -- only with how many entries are actually returned. |
AUDIT.FEED | O(LIMIT) | Same reasoning as AUDIT.RANGE: FROM seeks directly to the target row instead of scanning the feed from the beginning. |
DEL (AuditLog key) | O(1) | Deleting the container and swapping the feed row (deleting the old one, writing the new one) are both direct key operations by a stored key, not a scan. The trail's individual entries are not deleted synchronously -- they're left as orphans and reclaimed later by the background eviction sweep (O(total orphaned entries) at that point, off the request path). |
| Command | Supported | Fully supported? | Comment |
|---|---|---|---|
| AUDIT.APPEND | ✓ | ✓ | |
| AUDIT.RANGE | ✓ | ✓ | |
| AUDIT.FEED | ✓ | ✓ | Shard-local; no retention/eviction policy yet |
Processor: AMD Ryzen 9 7950X 16-Core Processor 4.50 GHz
Installed RAM: 64.0 GB (63.2 GB usable)
System type: 64-bit operating system, x64-based processor
Disk: Crucial T700, 2TB PCIe Gen5 NVMe M.2 SSD
get tests used randomg keys in the range of 0000001 - 5000000 (key size = 7Bytes)get & setget test:
setCommand used:
sb --threads 6 -c 512 -t set -n 5000000 -r 5000000 -d <64|128|256>
| Payload size (bytes) | rps | p50 (ms) | p90 (ms) | p99 (ms) |
|---|---|---|---|---|
| 64 | 781K | 0.559ms | 0.751ms | 1.919ms |
| 128 | 715K | 0.595ms | 0.919ms | 2.015ms |
| 256 | 656K | 0.583ms | 1.359ms | 2.007ms |
getCommand used:
sb --threads 6 -c 512 -t set -n 5000000 -r 5000000 -d <64|128|256> -z
| Payload size (bytes) | rps | p50 (ms) | p90 (ms) | p99 (ms) |
|---|---|---|---|---|
| 64 | 1.04M | 0.457ms | 0.607ms | 0.783ms |
| 128 | 1.03M | 0.469ms | 0.607ms | 0.735ms |
| 256 | 931K | 0.483ms | 0.635ms | 0.795ms |
setget with 1:4 ratio (1 SET for every 4 GET calls)Command used:
sb --threads 6 -c 512 -t setget -n 5000000 -r 5000000 -d <64|128|256> -z
| Payload size (bytes) | rps | p50 (ms) | p90 (ms) | p99 (ms) |
|---|---|---|---|---|
| 64 | 923K | 0.483ms | 0.635ms | 1.239ms |
| 128 | 911K | 0.481ms | 0.639ms | 1.495ms |
| 256 | 787K | 0.563ms | 0.755ms | 1.727ms |
ping command)| Command | rps | pipeline | p50 (ms) | p90 (ms) | p99 (ms) |
|---|---|---|---|---|---|
| ping | 1.6M | 1 | 0.407 | 0.775 | 1.143 |
7,684 followers · starred Apr 2024
390 followers · starred Apr 2024
145 followers · starred Apr 2024
1,683 followers · starred Apr 2024