Configuration
Settings merge in this order: built-in defaults, a TOML file, RUVIO_* environment variables, then CLI flags. Later layers win. Unknown TOML fields fail startup. With no --config, the process loads RUVIO_CONFIG, then ruvio.toml beside the binary, then /etc/ruvio/ruvio.toml, then ./ruvio.toml. A missing file is created from the built-in defaults. Environment variables and CLI flags are written back into that file at startup. The file is read once: an edit applies on the next start. There is no CONFIG SET. After start, CONFIG GET * (or a glob) shows the effective process and redacts requirepass. ruvio --help lists CLI names. This page follows docs/CONFIGURATION.md.
Sample file
The repository ships this shape as ruvio.toml. The process loads the default path above, and --config still selects a file explicitly. How that works for an installed binary, a checkout, and Docker is in How to apply.
address = "127.0.0.1:6379" shards = 1 advertised_host = "127.0.0.1" # advertised_port = 6379 hot_keys = [] notify_keyspace_events = "" shutdown_grace_ms = 30000 metrics_address = "127.0.0.1:9121" [protocol] max_arguments = 64 max_key_bytes = 1048576 max_value_bytes = 16777216 max_command_bytes = 67108864 max_pipeline_commands = 1024 max_connection_buffer_bytes = 67108864 databases = 16 [timeouts] idle_ms = 300000 read_ms = 30000 write_ms = 30000 [connections] max_global = 10000 max_per_ip = 256 max_per_user = 0 max_commands_per_second = 0 [requests] max_queued_requests = 1024 max_output_buffer_bytes = 16777216 [persistence] profile = "memory" data_dir = "./data" wal_segment_bytes = 268435456 sync_interval_ms = 1000 snapshot_max_bytes = 1073741824 snapshot_journal_bytes = 67108864 snapshot_interval_ms = 60000 snapshot_min_mutations = 0 [replication] role = "off" listen = "127.0.0.1:26379" primary = "127.0.0.1:26379" [security] requirepass = "" protected_mode = true tls_cert = "" tls_key = "" [[security.users]] name = "app" rules = "on >app-secret ~app:* +@read +ping +auth +info" [memory] max_shard_bytes = 0 max_process_bytes = 0 reserved_headroom_bytes = 16777216 maxmemory_policy = "noeviction" maxmemory_samples = 16 active_expire_keys = 16 hash_max_compact_entries = 64 hash_max_compact_value_bytes = 64 [scripts] max_bytes = 16384 max_cached = 1024 max_calls = 1024 max_instructions = 1000000
How to apply
Save the sample as TOML, then point the process at it with --config. Environment and flags still override that file. A typical split: put stable limits and ACL users in the file; put the bind address, password, shard count, and advertise host in the environment so the same file works on a laptop and in Docker.
Installed binary (cargo install ruvio)
The crate puts ruvio and ruvio-ctl on the host. Paths in the file are relative to the working directory, not the binary.
ruvio --config ./ruvio.toml
Override one field without editing the file. CLI wins:
ruvio --config ./ruvio.toml --durability always --data-dir ./data
Or skip the file and use environment names (RUVIO_ADDR, RUVIO_SHARDS, RUVIO_DURABILITY, RUVIO_DATA_DIR, RUVIO_REQUIREPASS, RUVIO_ADVERTISE_HOST, …):
RUVIO_SHARDS=2 RUVIO_ADDR=0.0.0.0:6379 ruvio
From a checkout
Pass flags after -- so Cargo does not eat them.
cargo run --release --bin ruvio -- --config ./ruvio.toml RUVIO_SHARDS=4 RUVIO_ADDR=0.0.0.0:6379 cargo run --release --bin ruvio
Linux container
The published image already sets environment that the file would otherwise default differently:
RUVIO_ADDR=0.0.0.0:6379RUVIO_DATA_DIR=/dataRUVIO_PROTECTED_MODE=false
Those values beat a file unless you pass a later CLI flag. The process is UID 10001. The entrypoint is ruvio, so extra arguments are flags, not a shell.
Environment only — no file:
docker run --rm -p 6379:6379 -v ruvio-data:/data \ -e RUVIO_DURABILITY=everysec \ -e RUVIO_REQUIREPASS=secret \ salihcantekin/ruvio:linux
Two shards that remote clients can follow. Advertise the host (and published ports) the app can reach:
docker run --rm -p 6379-6380:6379-6380 -v ruvio-data:/data \ -e RUVIO_SHARDS=2 \ -e RUVIO_ADVERTISE_HOST=YOUR_SERVER_IP \ -e RUVIO_PROTECTED_MODE=false \ salihcantekin/ruvio:linux
Same sample file, mounted where the process can read it. Keep data_dir = "/data" (or omit it and keep the image default). Host paths such as ./data are wrong inside the container.
docker run --rm -p 6379:6379 \ -v ruvio-data:/data \ -v "$PWD/ruvio.toml:/etc/ruvio.toml:ro" \ salihcantekin/ruvio:linux --config /etc/ruvio.toml
Then -e RUVIO_SHARDS=2 still overrides shards in that file. Put a password on any bind that is not loopback when you leave protected_mode false.
Coupled settings
These pairs are validated together. A mismatch fails before a listener opens.
| If you set | It also requires / changes |
|---|---|
shards > 1 | Only logical database 0 is allowed. Shard i binds address port + i. CLUSTER SLOTS / MOVED use advertised_host and advertised_port (or the bind port). cpu_set must not be an empty string. Change shard count on an empty data directory after a restart. |
advertised_host / advertised_port | Must be the host and shard-0 port that clients can reach. Set both when Docker or a load balancer remaps ports (example: publish 7000–7003 and set advertise port 7000). Wrong values send MOVED to an address the app cannot dial. |
metrics_address | Multi-shard uses consecutive metrics ports. That range must not overlap the RESP range or the replication range. |
protocol.max_connection_buffer_bytes | Must be ≥ protocol.max_command_bytes. |
protocol.databases with shards > 1 | SELECT other than 0 is refused even if databases is 16. |
connections.max_per_ip / max_per_user | Neither may exceed max_global. max_per_user of 0 disables the cap. |
persistence.profile | memory / snapshot cannot run replication. everysec uses sync_interval_ms. always syncs each record. WAL-backed profiles lock data_dir/LOCK. Scheduled checkpoints use snapshot_interval_ms and snapshot_min_mutations together. |
replication.role = primary | Needs everysec or always, and replication.listen as a numeric socket. Shard i uses listen port + i. That range must not overlap RESP, metrics, or the cluster-bus ports (RESP port + 10000). At most 16 replica streams. ACL SETUSER is refused while replication is on. |
replication.role = replica | Needs a WAL profile and replication.primary (host:port allowed). The process dials; it does not listen. |
address not loopback and empty requirepass | protected_mode stays on and rejects non-loopback clients. Container images set RUVIO_PROTECTED_MODE=false because a published port is not loopback inside the namespace. |
tls_cert / tls_key | Both PEM paths or neither. Plaintext clients are closed at handshake. |
memory.max_shard_bytes | 0 disables the budget. If set, reserved_headroom_bytes must be smaller than it. Must not exceed max_process_bytes when that is also set. Eviction policies only run when this budget is in use. A single write larger than the shard budget is OOM and does not evict. |
hot_keys | Matching keys bypass slot routing inside this process. Enable on empty data; existing shard keys are not migrated. Checkpoints wait for a virtual hot partition. Only GET, SET, DEL are allowed on a match. |
notify_keyspace_events | Combine K or E with event classes: g (delete), $ (string SET), x (expired), e (evicted), or A (all four). For example, Exe enables keyevent channels for expiration and eviction. Delivery uses sharded SSUBSCRIBE, not durable, not PSUBSCRIBE. |
Process
| Setting | Allowed | Default | Meaning | Coupled with |
|---|---|---|---|---|
address / RUVIO_ADDR | Numeric ip:port | 127.0.0.1:6379 | RESP bind. Shard i uses this port plus i. | Protected mode, metrics and replication port ranges, advertised_port |
shards / RUVIO_SHARDS | Integer ≥ 1 | 1 | Shard owners in this process and how 16,384 slots are split. | Databases, advertise, CPU set, metrics ports |
advertised_host / RUVIO_ADVERTISE_HOST | Hostname or IP string | 127.0.0.1 | Host in CLUSTER SLOTS / SHARDS / NODES and MOVED. | shards, advertised_port |
advertised_port / RUVIO_ADVERTISE_PORT | Omitted, or port 1–65535 | Bind port | Announced port for shard 0. Shard i is this plus i. | address, published Docker ports |
cpu_set / RUVIO_CPUSET | CPU list, or omit | Unset | Pin shard executors. An empty string is rejected when shards > 1. | shards |
hot_keys / RUVIO_HOT_KEYS | List of Redis * / ? globs, or empty | Empty (off) | Matching string keys use a process-wide store, same value on every shard in this process. | Checkpoints, empty data directory |
notify_keyspace_events / RUVIO_NOTIFY_KEYSPACE_EVENTS | Empty, or K/E plus event classes g, $, x, e, A | Empty (off) | K enables keyspace channels; E enables keyevent channels. Classes: g delete, $ string SET, x expired, e evicted, A all four. | SSUBSCRIBE, not WAL |
shutdown_grace_ms | Positive milliseconds | 30000 | After signal: stop accept, drain clients, flush durable state. | Persistence profile |
metrics_address | Omitted/empty, or numeric ip:port | Omitted (off) | HTTP GET /metrics per shard on consecutive ports. INFO prometheus still works without it. | RESP and replication port ranges |
Listening for expired and evicted keys
notify_keyspace_events is empty by default. Its value combines channel type and event classes:
Kenables keyspace channels named__keyspace@<db>__:<key>; payload is the event name.Eenables keyevent channels named__keyevent@<db>__:<event>; payload is the key name.gmeans delete,$means stringSET,xmeans expired, andemeans evicted.Aenables all four event classes.
To publish both expiration and eviction keyevent notices, combine E with x and e:
notify_keyspace_events = "Exe"
Ruvio reads this setting at startup; restart after changing the TOML file. Subscribe to the exact channel for the key's database, such as SSUBSCRIBE __keyevent@0__:expired or SSUBSCRIBE __keyevent@0__:evicted. PSUBSCRIBE patterns do not receive these notifications. Expired and evicted keyevent messages contain only the key name as their payload; they do not include or return the key's value.
await db.SubscribeExpiredAsync(message =>
Console.WriteLine($"Expired key: {message.Payload}"));
await db.SubscribeEvictedAsync(message =>
Console.WriteLine($"Evicted key: {message.Payload}"));
Notices are live and best-effort: they are not replayed to later subscribers and are not written to the WAL. An eviction notice is sent only when memory pressure triggers eviction; the default noeviction policy does not evict. In a multi-shard deployment, notifications are local to the shard that owns the key; one subscription does not collect events from every shard. The Operator Pub/Sub screen also provides expired/evicted listeners with database and shard selection.
Protocol
| Setting | Allowed | Default | Meaning | Coupled with |
|---|---|---|---|---|
max_arguments | > 0 | 64 | Arguments in one command array. | |
max_key_bytes | > 0 | 1 MiB | Maximum key length. | |
max_value_bytes | > 0 | 16 MiB | Maximum value length. | Shard memory budget |
max_command_bytes | > 0 | 64 MiB | Maximum encoded command. | max_connection_buffer_bytes |
max_pipeline_commands | > 0 | 1024 | Responses held before a Linux pipeline flush. Portable writes one reply at a time. | Compiled adapter |
max_connection_buffer_bytes | ≥ max_command_bytes | 64 MiB | Input buffer cap on one connection. | max_command_bytes |
databases / RUVIO_DATABASES | 1–256 | 16 | SELECT 0 .. databases-1 on a standalone process. | shards > 1 forces DB 0 |
Timeouts
Zero is rejected for all three.
| Setting | Allowed | Default | Meaning | Coupled with |
|---|---|---|---|---|
idle_ms | > 0 | 300000 | Deadline while waiting for the next complete frame. | read_ms after a partial frame |
read_ms | > 0 | 30000 | Deadline until more bytes arrive on a partial frame. | idle_ms |
write_ms | > 0 | 30000 | Blocked write timeout. | Output buffer limit |
Connections
| Setting | Allowed | Default | Meaning | Coupled with |
|---|---|---|---|---|
max_global | > 0 | 10000 | Process-wide admitted connections. Excess: ERR max number of clients reached, then close. | Shared across shards |
max_per_ip | > 0, ≤ max_global | 256 | Cap per source IP. | max_global |
max_per_user | 0 (off) or ≤ max_global | 0 | Cap per ACL identity after AUTH. | ACL users, max_global |
max_commands_per_second | 0 (off) or > 0 | 0 | Per-connection token bucket. Burst equals one second. Excess is not executed. |
Requests
| Setting | Allowed | Default | Meaning | Coupled with |
|---|---|---|---|---|
max_queued_requests | > 0 | 1024 | Portable shard queue. Full queue returns TRYAGAIN. | Portable adapter |
max_output_buffer_bytes | > 0 | 16 MiB | Per-connection reply accumulation. Slow clients are cut. | Write timeout, memory charges |
Persistence
| Setting | Allowed | Default | Meaning | Coupled with |
|---|---|---|---|---|
profile | memory, snapshot, everysec, always | memory | memory: no recovery. snapshot: last completed checkpoint. everysec: ack after append, sync on the interval. always: ack only after the record is durable. A failed append does not change the key; later writes are MISCONF. | Replication needs WAL (everysec or always) |
data_dir | Directory path | ./data | WAL, checkpoints, LOCK, CLOCK. | WAL profiles take LOCK |
wal_segment_bytes | Positive size | 256 MiB | Rotate WAL segments at this size. | Checkpoints delete covered segments |
sync_interval_ms | Positive when used | 1000 | How often everysec synchronizes dirty logs. | profile = everysec |
snapshot_max_bytes | Positive size | 1 GiB | Upper bound on a checkpoint file. | |
snapshot_journal_bytes | Positive size | 64 MiB | Copy-on-write / journal bound while a snapshot runs. | Memory charges |
snapshot_interval_ms | 0 (off) or > 0 | 60000 | Scheduled checkpoint period. SAVE still works when 0. | snapshot_min_mutations |
snapshot_min_mutations | ≥ 0 | 0 | Mutations required before a scheduled checkpoint. 0 means any mutation after the interval. | snapshot_interval_ms |
Replication
| Setting | Allowed | Default | Meaning | Coupled with |
|---|---|---|---|---|
role | off, primary, replica | off | No failover, no REPLICAOF vote. Off opens no replication port. | WAL profile, adapter, listen/primary |
listen | Numeric ip:port | Sample 127.0.0.1:26379 | Primary bind. Shard i uses port plus i. | Required for primary. Must not overlap RESP/metrics/bus |
primary | Numeric address or host:port | Sample 127.0.0.1:26379 | Address a replica dials. Hostnames resolve again on each dial. | Required for replica |
Security
The operator UI does not have its own accounts. Sign-in is AUTH against this list. Empty user and password send no AUTH and the session is default. default is always present: on nopass ~* +@all until requirepass is set, and then that password belongs to default. Named users in [[security.users]] are loaded at startup and survive a restart. ACL SETUSER from the UI changes only the running process.
| Setting | Allowed | Default | Meaning | Coupled with |
|---|---|---|---|---|
requirepass | String, max 65535 bytes, or empty | Empty | Password for the implicit default user. Compared in constant time. Never logged. Redacted in CONFIG GET. | protected_mode, named users |
protected_mode | true / false | true | With a public bind and no password, non-loopback clients are rejected. | address, requirepass |
tls_cert / tls_key | Both PEM paths, or both empty | Empty (off) | TLS before RESP. Files reload when mtime changes. No client certificates. | tls_client_ca |
[[security.users]] | name + Redis-style rules | None | on/off, >password, nopass, ~pattern, +@cat, +cmd. Implicit default is on nopass ~* +@all until a password is set. ACL SETUSER is memory-only and refused while replication is on. | max_per_user, replication |
Memory
| Setting | Allowed | Default | Meaning | Coupled with |
|---|---|---|---|---|
max_shard_bytes | 0 (off) or budget | 0 | Admission budget per shard, including connection and persistence charges. | Headroom, process cap, policy |
max_process_bytes | 0 (off) or budget | 0 | Process-wide cap. Shard limit cannot exceed it when both are set. | max_shard_bytes |
reserved_headroom_bytes | Positive, < max_shard_bytes when that is set | 16 MiB | Unusable slice of the shard budget so eviction/OOM has room. | max_shard_bytes |
maxmemory_policy | noeviction, allkeys-lru, allkeys-lfu, volatile-lru, volatile-ttl | noeviction | What happens when the shard budget is exceeded. Eviction is shard-local, at most 16 deletes per command. | Only matters if max_shard_bytes > 0 |
maxmemory_samples | 1–64 | 16 | Candidates examined per eviction decision. | Policy |
active_expire_keys | 1–1024 | 16 | Expired keys reaped per command or idle tick. | |
hash_max_compact_entries | 1–4096 | 64 | Small hashes stay compact until this field count. | hash_max_compact_value_bytes |
hash_max_compact_value_bytes | 1–65536 | 64 | A longer field or value promotes the hash to a table. | hash_max_compact_entries |
Scripts
Zero is rejected. Environment names: RUVIO_SCRIPT_MAX_BYTES, RUVIO_SCRIPT_MAX_CACHED, RUVIO_SCRIPT_MAX_CALLS, RUVIO_SCRIPT_MAX_INSTRUCTIONS.
| Setting | Allowed | Default | Meaning | Coupled with |
|---|---|---|---|---|
max_bytes | > 0 | 16384 | Maximum script or function body. | |
max_cached | > 0 | 1024 | Cached EVALSHA / loaded scripts. | |
max_calls | > 0 | 1024 | Nested call quota in the Lua sandbox. | |
max_instructions | > 0 | 1000000 | Instruction quota. Recovery replays mutations, not Lua. | WAL transaction batch |