# rueidis: A Go Redis Client With Auto Pipelining and Client-Side Caching

> rueidis is an Apache-2.0 Go client for Redis that pipelines concurrent commands automatically and caches reads on the client with server invalidation. It suits Go services that want throughput without hand-written pipelining, and it is the wrong pick if you need a synchronous, single-connection client.

**redis/rueidis** — A fast Golang Redis client that supports Client Side Caching, Auto Pipelining, RDMA, etc.

- Repository: https://github.com/redis/rueidis
- Stars: 2,984 · Forks: 259
- Language: Go
- License: Apache-2.0
- Published: 2026-09-24 · Updated: 2026-09-24 · Language: en
- Canonical page: https://hysenlabs.com/projects/redis-rueidis

## What rueidis solves for Go services talking to Redis

A plain Redis client sends one command, waits for one reply, then sends the next. Under concurrency that pattern wastes round trips: ten goroutines each doing a GET produce ten separate request-response cycles even when they arrive at the same moment. rueidis targets that gap. Its README describes it as "a fast Golang Redis client that does auto pipelining and supports server-assisted client-side caching." The audience is Go services that already treat Redis as a hot path and want the client to batch work on their behalf rather than restructuring call sites.

The second problem it addresses is repeated reads. If many requests hit the same key, the server answers the same bytes over and over. rueidis offers a client-side cache where the server tells the client when a cached key is invalidated, so reads can be served locally between invalidations. That is a different trade from a plain cache-aside layer you write yourself: the invalidation signal comes from Redis rather than from a TTL you guessed.

The repository is organized around that split. Top-level files such as pipe.go, pool.go, cache.go and lru.go sit alongside subpackages including om/, rueidisaside/, rueidislock/ and rueidiscompat/. The compat package is described as a "Go-redis like API adapter," which matters if you are migrating an existing codebase rather than starting fresh.

## How auto pipelining and the command builder actually work

Commands are constructed through a builder reached at client.B(). The README's example builds a SET with a NX flag and a HGETALL, then sends each with client.Do(). The builder returns a command object rather than a string, so the client knows the shape of the reply before it arrives and can decode it into typed accessors such as AsStrMap().

The pipelining happens implicitly. According to the README, "All concurrent non-blocking redis commands (such as GET, SET) are automatically pipelined by default." Calling client.Do() from multiple goroutines is enough; the client batches those in-flight commands into fewer round trips and system calls. There is no explicit flush step in the caller's code.

That design has a cost the README states plainly: auto pipelining "relies on additional goroutines to process requests and responses and may add some latencies due to goroutine scheduling and head of line blocking." Setting DisableAutoPipelining to true switches to a connection pooling model where each request gets a dedicated connection on the same goroutine. You can then opt back in per command with ToPipe().

Manual batching is available through DoMulti(), which takes a slice of commands and returns a slice of responses in order. The recycling rule applies to both paths: commands are returned to a sync.Pool after execution, so reusing a built command in a later call is a bug unless you call Pin() after Build().

## Installing rueidis and running a first cached read

The module path is github.com/redis/rueidis and go.mod declares go 1.25.0, so a Go toolchain at that version or newer is required. Add it with:

```bash
go get github.com/redis/rueidis
```

The README's getting-started program constructs a client against a single address and defers Close(). This is the smallest working shape:

```go
client, err := rueidis.NewClient(rueidis.ClientOption{InitAddress: []string{"127.0.0.1:6379"}})
if err != nil {
  panic(err)
}
defer client.Close()
```

With the client open, a write and a read look like this. Note that Build() produces the command and Do() executes it:

```go
err = client.Do(ctx, client.B().Set().Key("key").Value("val").Nx().Build()).Error()
hm, err := client.Do(ctx, client.B().Hgetall().Key("hm").Build()).AsStrMap()
```

To exercise the caching path, use DoCache() with a client-side TTL. The README shows a HMGET marked with Cache() and a one-minute TTL, decoded with ToArray():

```go
client.DoCache(ctx, client.B().Hmget().Key("mk").Field("1", "2").Cache(), time.Minute).ToArray()
```

If you want a local Redis to test against, the repository's docker-compose.yml defines a redis service on port 6379 using the redis:7.4-alpine image, along with separate services for Sentinel, cluster, KeyDB, Dragonfly and Kvrocks. Those extra services exist because the test suite runs against multiple servers, not because rueidis requires them.

## The recycling rule is the sharpest edge in the API

The README uses bold warning formatting for one sentence: you "SHOULD NOT" reuse a command in another Do() or DoMulti() call because it has been recycled to the underlying sync.Pool by default. This is not a stylistic preference, it is a correctness constraint. A command that has been returned to the pool may be overwritten by another goroutine's Build() before your second call reads it.

The escape hatch is Pin(). Pinning a command after Build() prevents recycling. The README's DoMulti example pins every command so that after execution the caller can still read cmds[i].Commands()[1] to recover the key. If you do not need the command afterward, pinning is wasted allocation.

This is the kind of trade-off worth weighing before adoption. The pool is part of why the client keeps allocation low under load, and the cost is an API where holding a reference to a built command is a latent bug. Teams used to constructing a command once and sending it repeatedly will need to change that habit. The MGet and MGetCache helpers exist partly to avoid the problem: they map keys to responses so you do not have to correlate a response slice back to a command slice yourself.

A second boundary: auto pipelining is described as applying to non-blocking commands. Blocking commands do not fit that model, and the README does not claim they do.

## Client-side caching depends on the server, not just the client

The caching mode is opt-in at the call site but enabled by default in the client, per the README. You get it by calling DoCache() or DoMultiCache() with a client-side TTL. The mechanism is server-assisted: Redis tracks which keys a connection has cached and pushes invalidation messages when those keys change.

That dependency is the limitation. Client-side caching requires a server that supports the invalidation protocol, and the README links to Redis's own documentation on the feature rather than restating the server requirements. If you run an older server or a proxy that does not forward the relevant push messages, the caching path is not something you can simply turn on. The docker-compose.yml includes a redis5 service on port 6355 and a keydb6 service, which suggests the project tests against older and alternative servers, but the README does not state which of them support client-side caching.

The RESP3 topic tag on the repository points the same direction: the invalidation push model is a RESP3 feature. A deployment pinned to RESP2 is not the target for DoCache().

There is also a consistency question the README does not answer. Between an invalidation arriving and your code acting on it, a cached value can be stale by however long that window is. The TTL you pass to DoCache() bounds it, but the README does not document a rollback or a forced-refresh call for the case where you need a guaranteed-fresh read.

## rueidis compared with go-redis

The README benchmarks rueidis against go-redis v9 directly and claims higher throughput across 1, 8 and 64 parallelism settings, with a figure of roughly 14x on a local MacBook Pro M1 Pro run at parallelism 64, key size 16, value size 64. Benchmark source is linked to a separate repository. Treat that number as a synthetic local result, not a production expectation, and note that the benchmark is maintained by the same author.

The design difference behind it is the pipelining model. rueidis batches concurrent commands by default and decodes replies through a typed builder. go-redis uses a more conventional request-per-call model, and its API is the one most Go developers already know. That familiarity is not a small thing: the existence of rueidiscompat, a "Go-redis like API adapter," is itself an admission that the native rueidis API is a migration cost.

If your workload is low-concurrency, or if you value an API where a built command is just a value you can hold and resend, go-redis is the more predictable choice. rueidis pays off when many goroutines are issuing independent commands and the batching is doing real work. The README's own advice to disable auto pipelining when goroutine scheduling and head-of-line blocking hurt latency is a reminder that the fast path is not universally faster.

Beyond the core client, the repository ships subpackages that go-redis does not: rueidisaside for a cache-aside pattern, rueidislock for distributed locks, rueidisprob for probabilistic data structures without Redis Stack, and rueidisotel for OpenTelemetry integration.

## Maintenance, licensing and what an upgrade costs

The repository is not archived, and the last push was on 2026-09-16. Releases are frequent: v1.0.78 on 2026-09-15, v1.0.77 on 2026-08-14 and v1.0.76 on 2026-06-20. The version numbers stay in the 1.0.x line, which is worth noting if you were expecting a 2.0 with breaking changes; the project has not shipped one.

Upgrade cost is mostly the Go toolchain floor. go.mod requires go 1.25.0, so a project on an older Go release cannot take a new rueidis version without upgrading Go first. That is a real constraint for teams on a fixed toolchain, and it will bite on a schedule set by the maintainers rather than by you.

The dependency surface is small. go.mod lists github.com/onsi/gomega and golang.org/x/sys as direct requirements, with go-cmp, yaml and golang.org/x/net and golang.org/x/text as indirect. A client library that pulls in a test framework as a direct dependency is slightly unusual, but the indirect list is short enough that vendoring is not painful.

Licensing is Apache-2.0, and the repository carries both a LICENSE and a NOTICE file. Apache-2.0 includes an explicit patent grant and requires that the NOTICE contents be preserved in distributions. If you vendor or redistribute the code, keep NOTICE intact. That is a description of the licence text, not legal advice; check with your own counsel for your distribution model.

## Conclusion

Adopt rueidis if your Go service issues many concurrent Redis commands and you want pipelining and client-side caching without writing that plumbing yourself; the README's own examples show both are opt-in or on by default. Do not adopt it if you need commands to never be recycled, or if you cannot run Redis with RESP3, since client-side caching depends on it. Before committing, verify that your Redis or compatible server supports the invalidation push messages the caching path needs, and check whether DisableAutoPipelining changes your latency profile under your own load.

## FAQ

### What is rueidis?

rueidis is an Apache-2.0 Go client for Redis. The README describes it as a fast Golang Redis client that does auto pipelining and supports server-assisted client-side caching, with additional subpackages for cache-aside, distributed locks, mocking and OpenTelemetry.

### How does rueidis compare with go-redis?

The README benchmarks rueidis against go-redis v9 and reports higher throughput at 1, 8 and 64 parallelism, with a roughly 14x figure in one local benchmark. The design difference is that rueidis pipelines concurrent non-blocking commands automatically, while go-redis uses a more conventional request-per-call model. rueidis also ships a Go-redis like API adapter for migration.

### How do I install the rueidis client?

Add the module with go get github.com/redis/rueidis. The go.mod file declares go 1.25.0, so you need a Go toolchain at that version or newer.

### Can I reuse a command built with rueidis in another call?

No. The README warns that built commands are recycled to a sync.Pool by default and must not be reused in another Do() or DoMulti() call. Call Pin() after Build() if you need to keep the command.

### Does rueidis support client-side caching?

Yes. Calling DoCache() or DoMultiCache() with a client-side TTL enables the server-assisted client-side caching mode, which the README says is enabled by default in the client. The invalidation signal comes from the server, so the server must support it.

## Sources

- [Issues](https://github.com/redis/rueidis/issues)
- [License: Apache-2.0](https://github.com/redis/rueidis/blob/main/LICENSE)
- [README](https://github.com/redis/rueidis/blob/main/README.md)
- [redis/rueidis on GitHub](https://github.com/redis/rueidis)
- [Releases](https://github.com/redis/rueidis/releases)

---

Hysen Labs editorial analysis, written from the project's own repository and release notes. Cite the canonical page: https://hysenlabs.com/projects/redis-rueidis
