Ruvio.

Packages

There is one server, one Linux image, and first-party clients for Rust and C#. The clients speak RESP2 and do not send CLIENT SETINFO. The ruvio crate is the server, not a client library.

Where they live

Download counts come from crates.io, NuGet, and Docker Hub when those APIs allow a browser. A private Docker Hub repository does not publish a pull count.

Counts refresh when this page loads.

PackageKindVersionInstall
ruvio Server binary 0.2.6 cargo install ruvio --locked --version 0.2.6
ruvio-client Rust SDK 0.2.5 cargo add ruvio-client
Ruvio.Client C# SDK 0.2.12 dotnet add package Ruvio.Client --version 0.2.12
Ruvio.Client.AspNetCore ASP.NET Core 0.2.12 dotnet add package Ruvio.Client.AspNetCore --version 0.2.12
salihcantekin/ruvio Linux image linux docker pull salihcantekin/ruvio:linux
salihcantekin/ruvio-ui Operator UI linux docker pull salihcantekin/ruvio-ui:linux

Install the server

cargo install ruvio puts the ruvio and ruvio-ctl binaries on the host. There is no container. The process listens on the machine's own port.

cargo install ruvio --locked --version 0.2.6
ruvio
redis-cli -h 127.0.0.1 -p 6379 PING

Default bind is loopback. Settings resolve in this order: built-in defaults, a TOML file passed with --config, RUVIO_* environment variables, then command-line options. See Start and Configuration.

Docker image

Two published Linux images. Docker selects amd64 or arm64. The server tags linux, latest, and 0.2.6 are the same image. The operator UI tags linux, latest, and 0.1.0 are the same image.

docker pull salihcantekin/ruvio:linux
docker run --rm -p 6379:6379 -v ruvio-data:/data salihcantekin/ruvio:linux

The container sets RUVIO_ADDR=0.0.0.0:6379, RUVIO_DATA_DIR=/data, and RUVIO_PROTECTED_MODE=false, because a published port is not loopback inside the namespace. Put a password on any bind that is not loopback when you leave that default. The process runs as UID 10001.

A local build of the server image is docker build -f Dockerfile.linux -t ruvio:local .

Operator UI

The UI container serves the browser and connects to the Ruvio server. RUVIO_UI_TARGET_HOST and RUVIO_UI_TARGET_PORT are that server. A default process is 127.0.0.1:6379. Port 8080 is only the UI.

docker pull salihcantekin/ruvio-ui:linux
docker run --rm -p 8080:8080 \
  -e RUVIO_UI_TARGET_HOST=127.0.0.1 \
  -e RUVIO_UI_TARGET_PORT=6379 \
  salihcantekin/ruvio-ui:linux

Open http://127.0.0.1:8080. Sign-in is the server ACL user. Details are on the UI page.

Rust SDK

ruvio-client is a blocking RESP2 client. Connecting does not send HELLO or CLIENT SETINFO. Give it one address; it sends CLUSTER SLOTS and, on a sharded server, opens one socket per shard and routes by hash slot. Methods are named after the command (hset, zadd, xreadgroup) and cover every command Ruvio supports. execute and execute_many send any argument list, as redis-cli would.

cargo add ruvio-client
use std::time::Duration;
use ruvio_client::Client;

let mut db = Client::connect("127.0.0.1", 6379)?;
db.set("session:ada", "hello")?;
let value = db.get_string("session:ada")?;
db.incr("visits")?;
db.expire("session:ada", Duration::from_secs(60))?;
db.hset("user:1", &[("name", "Ada"), ("plan", "pro")])?;
db.zadd("leaderboard", &[("player1", 100.0), ("player2", 80.0)])?;
let top = db.zrevrange_with_scores("leaderboard", 0, 9)?;

One client is bound to one logical database. Database 0 sends nothing; any other index sends SELECT before the connect call returns.

use std::net::{IpAddr, Ipv4Addr, SocketAddr};
use ruvio_client::{Client, ClientOptions};

let mut cache = Client::connect_database("127.0.0.1", 6379, 2)?;
let mut sessions = Client::connect_ip(IpAddr::V4(Ipv4Addr::LOCALHOST), 6379, 3)?;
let mut main = Client::connect_addr(SocketAddr::from(([10, 0, 0, 5], 6379)), 0)?;
let mut secured = Client::connect_with(ClientOptions {
    host: "10.0.0.5".into(),
    username: Some("app".into()),
    password: Some("secret".into()),
    database: 4,
    ..ClientOptions::default()
})?;

Options structs cover ZADD NX/XX/GT/LT/CH/INCR, XREAD/XREADGROUP COUNT/BLOCK/NOACK, extended XPENDING, and XCLAIM. After subscriber().subscribe, listen or next_message reads deliveries on that second socket. Inside MULTI the server answers QUEUED, so queue commands with execute and read the results from exec. A password is sent as AUTH only when ClientOptions::password is set. A server error leaves the connection usable. A dropped socket fails that command and is opened again before the next one, including a shard. on_connection_lost and on_connection_restored report it. reconnect defaults to true; set_reconnect_delay returning None stops the retry. API docs: docs.rs/ruvio-client/0.2.5.

C# SDK

Ruvio.Client is the async RESP2 client. Give it one address; it sends CLUSTER SLOTS and, on a sharded server, opens one socket per shard and routes by hash slot. MOVED refreshes the map. Commands on one socket run one at a time. ExecuteManyAsync pipelines, grouped by shard when clustered.

dotnet add package Ruvio.Client --version 0.2.12
using Ruvio.Client;

await using var connection = await RuvioConnection.ConnectAsync("127.0.0.1", 6379);
IRuvioClient db = await connection.GetDefaultDatabaseAsync();
await db.SetAsync("session:ada", "hello");
string? value = await db.GetStringAsync("session:ada");
await db.IncrementAsync("visits");
long? visits = await db.GetInt64Async("visits");
await db.ExpireAsync("session:ada", TimeSpan.FromSeconds(60));

The connection owns its sockets and all database-scoped clients. Each client is fixed to one logical database; getting another database returns a separate cached client, so calls do not race through shared SELECT state. Cluster mode has only database 0. Dispose the connection once to dispose every client.

IRuvioClient cache = await connection.GetDatabaseAsync(2);
IRuvioClient sessions = await connection.GetDatabaseAsync(3);
IRuvioClient main = await connection.GetDatabaseAsync(0);

await using var securedConnection = await RuvioConnection.ConnectAsync(new RuvioClientOptions
{
    Host = "10.0.0.5",
    Port = 6379,
    Username = "app",
    Password = "secret",
    Database = 4,
});
IRuvioClient secured = await securedConnection.GetDefaultDatabaseAsync();

FlushDatabaseAsync, FlushAllDatabasesAsync, SwapDatabasesAsync, and MoveKeyAsync send FLUSHDB, FLUSHALL, SWAPDB, and MOVE. Typed options cover SET PXAT/KEEPTTL/GET, ZADD NX/XX/GT/LT/CH/INCR, XADD MAXLEN, XREAD/XREADGROUP COUNT/BLOCK/NOACK, XGROUP CREATE MKSTREAM, extended XPENDING, and XCLAIM options. A server error reply leaves the next command usable. A dropped command or shard socket fails that call; the next command opens it again. A single GET, MGET, EXISTS, HGET, TTL, or PTTL is sent once more when no reply byte arrived. A pipeline is not retried. ConnectionLost and ConnectionRestored carry host, port, and whether the socket is Pub/Sub; these events and reconnect control belong to the connection owner.

ASP.NET Core

Ruvio.Client.AspNetCore registers an owning RuvioConnection and a separate database-scoped IRuvioClient. The TCP connection opens when dependency injection creates the service. It depends on Ruvio.Client 0.2.11. Reconnect stays on unless options say otherwise. The callback receives the connection owner, where lost and restored handlers belong. Services that send commands take IRuvioClient; inject IRuvioConnection to obtain another logical database.

builder.Services.AddRuvioClient("127.0.0.1", 6379, database: 2);

Or with options:

dotnet add package Ruvio.Client.AspNetCore --version 0.2.12
using Ruvio.Client.AspNetCore;

builder.Services.AddRuvioClient(options =>
{
    options.Host = "127.0.0.1";
    options.Port = 6379;
    options.Database = 2;
    options.Reconnect = true;
    options.MaxReconnectAttempts = 8;
}, connection =>
{
    connection.ConnectionLost += (_, notice) => { };
    connection.ConnectionRestored += (_, notice) => { };
});

Or bind a configuration section:

builder.Services.AddRuvioClient(builder.Configuration.GetSection("Ruvio"));
{
  "Ruvio": {
    "Host": "127.0.0.1",
    "Port": 6379,
    "Database": 2,
    "Reconnect": true,
    "MaxReconnectAttempts": 8,
    "ReconnectBaseDelay": "00:00:00.100",
    "ReconnectMaxDelay": "00:00:02"
  }
}

Inject RuvioClient. Resolve it once at startup if the process should fail before it accepts traffic:

app.Services.GetRequiredService<RuvioClient>();