Expand description
§shardcache-client-rs
Blocking Rust client for shardcache’s native SCNP protocol.
SCNP is the native TCP protocol used by shardcache for Rust clients that want
lower overhead than RESP and, when configured, direct routing to shard-owned
server ports. This crate is intentionally small and synchronous: one client owns
one or more TCP connections and is meant to be used directly from worker
threads.
This is one of the four publishable crates in the 0.2.x release line alongside
shardmap, shardcache, and shardcache-redis.
§Install
Use the published crate from crates.io:
[dependencies]
shardcache-client-rs = "0.3.0"From a workspace checkout, use a path dependency:
[dependencies]
shardcache-client-rs = { path = "crates/shardcache-client-rs" }§API Shape
ShardCacheClient: one fanout connection to the ordinary server listener.ShardCacheDirectClient: one connection per direct shard listener, with client-side key routing.ShardCacheDirectRouter: route keys and connect individual shard-owned clients.client.redis(): optional first-party Redis command helpers behind theredisfeature.client.redis().command(...): generic Redis argv construction over SCNP.
§Server Shape
The client expects a running shardcache built with the server
feature:
cargo run -p shardcache --features server --bin shardcache -- \
--bind-addr 127.0.0.1:6380 \
--shard-count 4For direct shard routing, enable shard-owned listeners on the server config:
bind_addr = "127.0.0.1:6380"
shard_count = 4
server_endpoint_mode = "direct_shard"Then start the server with that config:
cargo run -p shardcache --features server --bin shardcache -- \
--config shardcache.tomlFor scripts and container runs that do not write a config file, the compatibility environment switch exposes the same direct port shape:
SHARDCACHE_DIRECT_SHARD_PORTS=1 \
SHARDCACHE_DIRECT_SHARD_BASE_PORT=6501 \
cargo run -p shardcache --features server --bin shardcache -- \
--bind-addr 127.0.0.1:6380 \
--shard-count 4With that layout, the fanout listener is 127.0.0.1:6380 and direct shard
listeners are 127.0.0.1:6501 through 127.0.0.1:6504.
§Fanout Client
Use ShardCacheClient when you want one connection to the ordinary server listener.
The server routes each request internally.
use shardcache_client_rs::ShardCacheClient;
fn main() -> shardcache_client_rs::Result<()> {
let mut client = ShardCacheClient::connect("127.0.0.1:6380")?;
let mut value = Vec::new();
client.set(b"user:42", b"ready")?;
let hit = client.get_into(b"user:42", &mut value)?;
assert!(hit);
assert_eq!(value, b"ready");
Ok(())
}§Direct Shard Client
Use ShardCacheDirectClient when the server exposes one SCNP port per shard and the
client should route keys directly. The address is the first direct shard port,
not the fanout port.
use shardcache_client_rs::ShardCacheDirectClient;
fn main() -> shardcache_client_rs::Result<()> {
let mut client = ShardCacheDirectClient::connect("127.0.0.1:6501", 4)?;
let mut value = Vec::new();
client.set(b"user:42", b"ready")?;
let hit = client.get_into(b"user:42", &mut value)?;
assert!(hit);
assert_eq!(value, b"ready");
Ok(())
}Direct mode requires the client shard count to match the server shard count. The shard count must be a non-zero power of two.
§Thread-Per-Shard Client
Use ShardCacheDirectRouter and ShardCacheDirectShardClient when your application already
partitions work by shard. A shard client refuses keys that route to a different
shard, which helps catch routing mistakes during development.
use shardcache_client_rs::ShardCacheDirectRouter;
fn main() -> shardcache_client_rs::Result<()> {
let router = ShardCacheDirectRouter::new("127.0.0.1:6501", 4)?;
let mut shard0 = router.connect_shard(0)?;
if router.route_key(b"user:42").shard_id == shard0.shard_id() {
shard0.set(b"user:42", b"ready")?;
}
Ok(())
}§Pipelining
ShardCacheClient and ShardCacheDirectShardClient expose ordered pipelining helpers.
Write requests with begin_pipeline_*, flush once, then read responses in the
same order.
use shardcache_client_rs::ShardCacheClient;
fn main() -> shardcache_client_rs::Result<()> {
let mut client = ShardCacheClient::connect("127.0.0.1:6380")?;
client.begin_pipeline_set(b"k1", b"v1")?;
client.begin_pipeline_set(b"k2", b"v2")?;
client.flush_pipeline()?;
client.finish_pipeline_set()?;
client.finish_pipeline_set()?;
let mut first = Vec::new();
let mut second = Vec::new();
client.begin_pipeline_get(b"k1")?;
client.begin_pipeline_get(b"k2")?;
client.flush_pipeline()?;
assert!(client.finish_pipeline_get_into(&mut first)?);
assert!(client.finish_pipeline_get_into(&mut second)?);
Ok(())
}§TTL And Delete
Use set_ex for cache entries that should expire on the server, and del
when the application invalidates a key explicitly.
use shardcache_client_rs::ShardCacheClient;
fn main() -> shardcache_client_rs::Result<()> {
let mut client = ShardCacheClient::connect("127.0.0.1:6380")?;
let mut value = Vec::new();
client.set_ex(b"session:1", b"active", 30_000)?;
assert!(client.get_into(b"session:1", &mut value)?);
assert!(client.del(b"session:1")?);
assert!(!client.get_into(b"session:1", &mut value)?);
Ok(())
}§Native Redis Commands
Enable the optional redis feature to use native SCNP Redis commands. Commands
with compact opcodes are sent as an SCNP opcode plus compact binary arguments.
Other Redis command names use the SCNP command-name wrapper, so the fanout
client can still cover the full Redis-compatible command surface without
building RESP request frames in user code.
[dependencies]
shardcache-client-rs = { version = "0.3.0", features = ["redis"] }The primary API is the first-party Redis namespace on the client. Common commands use Redis-client-style typed helpers, while less common commands can still pass Redis argv through the generic command path.
use shardcache_client_rs::{RedisResponse, ShardCacheClient};
fn main() -> shardcache_client_rs::Result<()> {
let mut client = ShardCacheClient::connect("127.0.0.1:6380")?;
client.redis().hset("user:42", "name", "Ada")?;
let response = client.redis().hgetall("user:42")?;
if let RedisResponse::Array(fields) = response {
assert!(!fields.is_empty());
}
Ok(())
}Commands without compact opcodes still use native SCNP through the same
namespace on ShardCacheClient:
use shardcache_client_rs::{RedisResponse, ShardCacheClient};
fn main() -> shardcache_client_rs::Result<()> {
let mut client = ShardCacheClient::connect("127.0.0.1:6380")?;
let response = client
.redis()
.geoadd("places", -122.4194, 37.7749, "san-francisco")?;
assert!(matches!(response, RedisResponse::Integer(1)));
Ok(())
}Use client.redis().command(...) for redis-rs-style generic command
construction:
use shardcache_client_rs::{RedisResponse, ShardCacheClient};
fn main() -> shardcache_client_rs::Result<()> {
let mut client = ShardCacheClient::connect("127.0.0.1:6380")?;
let response = client.redis().command("COMMAND").arg("COUNT").query()?;
assert!(matches!(response, RedisResponse::Integer(_)));
Ok(())
}Use redis_command, redis_command_by_name, client.redis().query_command(...),
or client.redis().execute(...) as lower-level escape hatches when you
explicitly want the compact-opcode path or already have borrowed command and
argv bytes.
RedisResponse decodes RESP2/RESP3 payloads returned by the command-name
wrapper into nested Rust values. Compact opcode commands return native SCNP
statuses directly; if a less-common compact command returns an embedded RESP
payload as RedisResponse::Value, call decode_resp_value() to decode it.
Direct shard clients route keyed compact-opcode commands to their owning shard
when all keys belong to one shard. Commands without compact opcodes, or commands
that require all shards, should use ShardCacheClient on the fanout listener.
§Routing Helpers
The crate exposes the same routing helpers used by the direct clients:
hash_keyhash_key_tagshard_indexShardCacheDirectRouterShardCacheRouteMode
ShardCacheRouteMode::FullKey is the default. ShardCacheRouteMode::SessionPrefix routes
keys shaped like s:<session>:c:<chunk> by the session prefix while preserving
the full-key lookup hash.
§TCP Buffer Tuning
On Unix platforms the client can request larger socket buffers by setting:
SCNP_CLIENT_TCP_BUFFER_BYTES=4194304SHARDCACHE_TCP_BUFFER_BYTES is also accepted for deployments that use one
shared environment knob for the server and client.
§Current Command Surface
This release supports the native hot-path commands currently implemented by
shardcache:
GETSETSETEXwith millisecond TTLsGETEXwith millisecond TTLsDELEXISTSTTL, returning Redis-compatible secondsEXPIREwith millisecond TTLs
With the redis feature enabled, it also supports the Redis-compatible command
surface through client.redis() on the fanout client. The low-level
redis_command and redis_command_by_name helpers remain available as escape
hatches.
The crate is organized internally with one command module per SCNP command so future commands can be added without growing the transport and routing code.
§License
Licensed under Apache-2.0.
Structs§
- Shard
Cache Client - Blocking SCNP client for the ordinary server listener.
- Shard
Cache Direct Client - Blocking SCNP client that automatically routes each key to its shard port.
- Shard
Cache Direct Router - Router for shard-owned SCNP direct ports.
- Shard
Cache Direct Shard Client - Blocking SCNP client pinned to one shard-owned port.
- Shard
Cache Route - Precomputed routed SCNP metadata for a key.
Enums§
- Shard
Cache Client Error - SCNP client error.
- Shard
Cache Route Mode - Shard-routing mode for direct SCNP clients.
Functions§
- hash_
key - Computes shardcache’s primary XXH3 key hash.
- hash_
key_ tag - Computes shardcache’s secondary key fingerprint.
- shard_
index - Computes the shard index for
hashandshard_count.
Type Aliases§
- Result
- SCNP client result.