Ruvio.

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:

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 setIt also requires / changes
shards > 1Only 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_portMust 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_addressMulti-shard uses consecutive metrics ports. That range must not overlap the RESP range or the replication range.
protocol.max_connection_buffer_bytesMust be ≥ protocol.max_command_bytes.
protocol.databases with shards > 1SELECT other than 0 is refused even if databases is 16.
connections.max_per_ip / max_per_userNeither may exceed max_global. max_per_user of 0 disables the cap.
persistence.profilememory / 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 = primaryNeeds 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 = replicaNeeds a WAL profile and replication.primary (host:port allowed). The process dials; it does not listen.
address not loopback and empty requirepassprotected_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_keyBoth PEM paths or neither. Plaintext clients are closed at handshake.
memory.max_shard_bytes0 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_keysMatching 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_eventsCombine 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

SettingAllowedDefaultMeaningCoupled with
address / RUVIO_ADDRNumeric ip:port127.0.0.1:6379RESP bind. Shard i uses this port plus i.Protected mode, metrics and replication port ranges, advertised_port
shards / RUVIO_SHARDSInteger ≥ 11Shard owners in this process and how 16,384 slots are split.Databases, advertise, CPU set, metrics ports
advertised_host / RUVIO_ADVERTISE_HOSTHostname or IP string127.0.0.1Host in CLUSTER SLOTS / SHARDS / NODES and MOVED.shards, advertised_port
advertised_port / RUVIO_ADVERTISE_PORTOmitted, or port 1–65535Bind portAnnounced port for shard 0. Shard i is this plus i.address, published Docker ports
cpu_set / RUVIO_CPUSETCPU list, or omitUnsetPin shard executors. An empty string is rejected when shards > 1.shards
hot_keys / RUVIO_HOT_KEYSList of Redis * / ? globs, or emptyEmpty (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_EVENTSEmpty, or K/E plus event classes g, $, x, e, AEmpty (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_msPositive milliseconds30000After signal: stop accept, drain clients, flush durable state.Persistence profile
metrics_addressOmitted/empty, or numeric ip:portOmitted (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:

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

SettingAllowedDefaultMeaningCoupled with
max_arguments> 064Arguments in one command array.
max_key_bytes> 01 MiBMaximum key length.
max_value_bytes> 016 MiBMaximum value length.Shard memory budget
max_command_bytes> 064 MiBMaximum encoded command.max_connection_buffer_bytes
max_pipeline_commands> 01024Responses held before a Linux pipeline flush. Portable writes one reply at a time.Compiled adapter
max_connection_buffer_bytes≥ max_command_bytes64 MiBInput buffer cap on one connection.max_command_bytes
databases / RUVIO_DATABASES1–25616SELECT 0 .. databases-1 on a standalone process.shards > 1 forces DB 0

Timeouts

Zero is rejected for all three.

SettingAllowedDefaultMeaningCoupled with
idle_ms> 0300000Deadline while waiting for the next complete frame.read_ms after a partial frame
read_ms> 030000Deadline until more bytes arrive on a partial frame.idle_ms
write_ms> 030000Blocked write timeout.Output buffer limit

Connections

SettingAllowedDefaultMeaningCoupled with
max_global> 010000Process-wide admitted connections. Excess: ERR max number of clients reached, then close.Shared across shards
max_per_ip> 0, ≤ max_global256Cap per source IP.max_global
max_per_user0 (off) or ≤ max_global0Cap per ACL identity after AUTH.ACL users, max_global
max_commands_per_second0 (off) or > 00Per-connection token bucket. Burst equals one second. Excess is not executed.

Requests

SettingAllowedDefaultMeaningCoupled with
max_queued_requests> 01024Portable shard queue. Full queue returns TRYAGAIN.Portable adapter
max_output_buffer_bytes> 016 MiBPer-connection reply accumulation. Slow clients are cut.Write timeout, memory charges

Persistence

SettingAllowedDefaultMeaningCoupled with
profilememory, snapshot, everysec, alwaysmemorymemory: 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_dirDirectory path./dataWAL, checkpoints, LOCK, CLOCK.WAL profiles take LOCK
wal_segment_bytesPositive size256 MiBRotate WAL segments at this size.Checkpoints delete covered segments
sync_interval_msPositive when used1000How often everysec synchronizes dirty logs.profile = everysec
snapshot_max_bytesPositive size1 GiBUpper bound on a checkpoint file.
snapshot_journal_bytesPositive size64 MiBCopy-on-write / journal bound while a snapshot runs.Memory charges
snapshot_interval_ms0 (off) or > 060000Scheduled checkpoint period. SAVE still works when 0.snapshot_min_mutations
snapshot_min_mutations≥ 00Mutations required before a scheduled checkpoint. 0 means any mutation after the interval.snapshot_interval_ms

Replication

SettingAllowedDefaultMeaningCoupled with
roleoff, primary, replicaoffNo failover, no REPLICAOF vote. Off opens no replication port.WAL profile, adapter, listen/primary
listenNumeric ip:portSample 127.0.0.1:26379Primary bind. Shard i uses port plus i.Required for primary. Must not overlap RESP/metrics/bus
primaryNumeric address or host:portSample 127.0.0.1:26379Address 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.

SettingAllowedDefaultMeaningCoupled with
requirepassString, max 65535 bytes, or emptyEmptyPassword for the implicit default user. Compared in constant time. Never logged. Redacted in CONFIG GET.protected_mode, named users
protected_modetrue / falsetrueWith a public bind and no password, non-loopback clients are rejected.address, requirepass
tls_cert / tls_keyBoth PEM paths, or both emptyEmpty (off)TLS before RESP. Files reload when mtime changes. No client certificates.tls_client_ca
[[security.users]]name + Redis-style rulesNoneon/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

SettingAllowedDefaultMeaningCoupled with
max_shard_bytes0 (off) or budget0Admission budget per shard, including connection and persistence charges.Headroom, process cap, policy
max_process_bytes0 (off) or budget0Process-wide cap. Shard limit cannot exceed it when both are set.max_shard_bytes
reserved_headroom_bytesPositive, < max_shard_bytes when that is set16 MiBUnusable slice of the shard budget so eviction/OOM has room.max_shard_bytes
maxmemory_policynoeviction, allkeys-lru, allkeys-lfu, volatile-lru, volatile-ttlnoevictionWhat 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_samples1–6416Candidates examined per eviction decision.Policy
active_expire_keys1–102416Expired keys reaped per command or idle tick.
hash_max_compact_entries1–409664Small hashes stay compact until this field count.hash_max_compact_value_bytes
hash_max_compact_value_bytes1–6553664A 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.

SettingAllowedDefaultMeaningCoupled with
max_bytes> 016384Maximum script or function body.
max_cached> 01024Cached EVALSHA / loaded scripts.
max_calls> 01024Nested call quota in the Lua sandbox.
max_instructions> 01000000Instruction quota. Recovery replays mutations, not Lua.WAL transaction batch