Ruvio.

Clients

Use the first-party SDKs only: Ruvio.Client, Ruvio.Client.AspNetCore, and ruvio-client. They speak RESP2, do not send CLIENT SETINFO or HELLO on connect, and learn shards from one seed. Install commands are on Packages.

C#

Ruvio.Client is the async SDK. Reads start with Get. Writes use Set, Add, Remove, Delete, and Increment. One client is one logical database. Cluster mode allows only database 0.

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.AddSortedSetAsync("leaderboard", ("player1", 100));
await db.AddGeoAsync("cities", (13.361389, 38.115556, "Palermo"));
var nearby = await db.GeoSearchAsync("cities", 13.36, 38.11, 20, "km");
var rows = await db.GetSortedSetRangeByRankWithScoresAsync("leaderboard", 0, -1, descending: true);

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

Give one seed. The client sends CLUSTER SLOTS, opens one socket per shard, and routes by slot. MOVED refreshes the map. GetManyAsync splits MGET.

await using var clusterConnection = await RuvioConnection.ConnectAsync("127.0.0.1", 6379);
IRuvioClient db = await clusterConnection.GetDefaultDatabaseAsync();
await db.SetAsync("{user1000}.following", "1");
await db.SetAsync("somekey", "other-shard");
var both = await db.GetManyAsync("{user1000}.following", "somekey");
int slot = RuvioClient.GetHashSlot("somekey"); // 11058

Pub/Sub uses a second socket. PUBLISH stays on the command connection.

await db.SubscribeAsync("news", message => Console.WriteLine(message.Payload));
await db.PublishAsync("news", "hello");

ASP.NET Core

Ruvio.Client.AspNetCore registers an owning connection singleton and a separate database-scoped IRuvioClient. The connection owns and disposes every client and socket it creates. Services that send commands take IRuvioClient; inject IRuvioConnection to get a client for another logical database or manage reconnect and lifecycle events.

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;
}, connection =>
{
    connection.ConnectionLost += (_, notice) => { };
    connection.ConnectionRestored += (_, notice) => { };
});

The application disposes the connection owner once; do not dispose individual database clients.

Rust

ruvio-client is the blocking SDK. The ruvio crate is the server, not a client.

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.geo_add("cities", &[(13.361389, 38.115556, "Palermo")])?;
let nearby = db.geo_search("cities", 13.36, 38.11, 20.0, "km")?;
db.expire("session:ada", Duration::from_secs(60))?;
db.set("{user1000}.following", "1")?;
db.set("somekey", "other-shard")?;

Reconnect

Default on, for a single server and for each shard socket. The command that discovers the drop fails. The next command opens the socket, then runs. The C# client sends one GET, MGET, EXISTS, HGET, TTL, or PTTL again when the socket closes before any reply byte. A pipeline is not retried. The Rust client does not retry that command. Channels subscribed on the Pub/Sub socket are sent again. Eight attempts, from 100 ms up to 2 seconds. ReconnectDelay / set_reconnect_delay replaces that wait; null or None stops it. With a custom delay, the attempt cap is not applied.

await using var connection = await RuvioConnection.ConnectAsync(new RuvioClientOptions
{
    Host = "127.0.0.1",
    Port = 6379,
    Reconnect = true,
    ReconnectDelay = attempt => attempt >= 4 ? null : TimeSpan.FromMilliseconds(50 * attempt),
});
connection.ConnectionLost += (_, notice) =>
    Console.WriteLine($"lost {notice.Host}:{notice.Port} subscriber={notice.Subscriber}");
connection.ConnectionRestored += (_, notice) =>
    Console.WriteLine($"restored {notice.Host}:{notice.Port}");
// options.Reconnect = false; then await connection.ReconnectAsync();
db.on_connection_lost(|notice| {
    println!("lost {}:{} subscriber={}", notice.host, notice.port, notice.subscriber);
});
db.on_connection_restored(|notice| {
    println!("restored {}:{}", notice.host, notice.port);
});
db.set_reconnect_delay(|attempt| {
    if attempt >= 4 { None } else { Some(Duration::from_millis(50 * attempt as u64)) }
});
// options.reconnect = false; then db.reconnect()?;

RuvioConnectionNotice and ConnectionNotice carry the host, the port, and whether the socket is the Pub/Sub connection. C# also keeps ConnectionFailed and puts the exception on the lost notice. Configuration can set Reconnect, MaxReconnectAttempts, ReconnectBaseDelay, and ReconnectMaxDelay. The delay function is code, not JSON.

Slots

Hash tags keep related keys on one slot: {user1000}.following and {user1000}.profile. Slot = CRC16(tag) % 16384. With two shards, slots 0–8191 are the first port and 8192–16383 the next. A multi-key command across slots is CROSSSLOT unless the first-party client splits it (MGET, MSET, DEL).