Skip to main content

A Redis client for .NET. Your calls go out as RESP, the replies come back as string?, long, and your own types.

Get started
dotnet add package Respire --prerelease

On the wire 55 bytes

*5\r\n$3\r\nSET\r\n$8\r\ngreeting\r\n$5\r\nhello\r\n$2\r\nEX\r\n$3\r\n300\r\n

In Respire

await redis.SetAsync("greeting", "hello", expiry: TimeSpan.FromSeconds(300));

Replies come back as C# types

Pick a command to see the frame Respire writes, the frame Redis sends back, and the value your code receives. Errors become exceptions. Pushes become cache evictions.

Values and serialization

You write

string? name = await redis.GetStringAsync("user:42:name");

Respire sends

*2\r\n$3\r\nGET\r\n$12\r\nuser:42:name\r\n

Redis replies

$3\r\nAda\r\n

You get

name is "Ada"string?

  • +Simple string
  • -Error
  • :Integer
  • $Bulk string
  • * %Array, map
  • >Push

Concurrent callers share one socket write

Each await looks like its own round trip. Requests that arrive together are written to the socket together, and replies are matched back in order. There is no batching switch to remember.

How the wire layer works
  1. Aawait redis.GetStringAsync("user:1")waiting
  2. Bawait redis.IncrementAsync("visits")waiting
  3. Cawait redis.Hashes.GetAllAsync("cart:9")waiting
Write buffer
ABC
1 write
Redis

Three concurrent awaits

Hot reads never leave the process

Turn on client-side caching and eligible reads stay in bounded process memory. Redis sends a > push frame when a key you read changes, and Respire evicts the stale copy for you.

var options = new RespireOptions
{
Endpoints = { new("localhost") },
ClientSideCache = new(),
};
Read about client-side caching
Server round trip0.0 ns
App
Redis
Local cache hit0.0 ns
AppCache
Redis

Replaying 10,000 times slower than real time

Measured with BenchmarkDotNet on .NET 10: 186.5 μs for a StackExchange.Redis GET against a local server, 151.5 ns and 0 B allocated for a Respire cache hit. See the benchmark run.

Blocking reads wait on their own connections

BLPOP and blocking stream reads hold a connection until data arrives. Respire sends them over a dedicated pool, so a worker waiting 30 seconds for a job never delays the GETs behind it.

string? job = await redis.Lists.LeftPopAsync(
"jobs",
waitFor: TimeSpan.FromSeconds(30));
Build a work queue
Shared connection
Blocking pool
BLPOP jobs waiting, up to 30s

Regular commands keep moving while BLPOP waits.

Respire is pre-release. Try it, and tell us what breaks.

dotnet add package Respire --prerelease