# redis-rb: the Ruby Redis client that mirrors the command set

> redis-rb is a Ruby client that maps Redis commands one-to-one onto Ruby methods, with a single mutex-guarded connection per instance. Version 6.0 negotiates RESP3 by default and falls back to RESP2 on older servers.

**redis/redis-rb** — A Ruby client library for Redis

- Repository: https://github.com/redis/redis-rb
- Stars: 4,004 · Forks: 1,029
- Language: Ruby
- License: MIT
- Published: 2026-09-23 · Updated: 2026-09-23 · Language: en
- Canonical page: https://hysenlabs.com/projects/redis-redis-rb

## The problem redis-rb solves for Ruby applications

Ruby's Redis clients split into two camps. One camp wraps Redis in objects, sessions, models or caches, so the Ruby method you call is not the Redis command you run. The other camp stays thin. redis-rb is firmly in the second camp: the README says the client "tries to match Redis' API one-to-one, while still providing an idiomatic interface", and that the Redis class exports methods named identically to the commands they execute.

That matters when you are debugging. If a call to redis.set("mykey", "hello world") returns "OK" and redis.get("mykey") returns "hello world", you can reason about the program with the Redis command reference open in another window. There is no translation layer to hold in your head.

The audience is therefore Ruby application developers, background workers and libraries that need the command surface rather than a data-model abstraction. The repository layout supports that reading: examples/ contains small standalone scripts for basic commands, lists, sets, pub/sub, keyspace notifications, sentinel and search, rather than a framework integration.

## One connection, one mutex, and the pool you supply yourself

The most consequential design decision is stated plainly in the README: "The client does not provide connection pooling. Each Redis instance has one and only one connection to the server, and use of this connection is protected by a mutex."

So a Redis object is not a pool. It is a single socket with a lock around it. Under a threaded server such as Puma or Sidekiq, every thread that shares one instance serialises behind that mutex. The README's own recommendation is the connection_pool gem, and it gives a ConnectionPool::Wrapper example where MyApp.redis is a lazily built pool and calls like MyApp.redis.incr("some-counter") are dispatched to a checked-out connection.

This is a deliberate trade-off, not an oversight. Keeping pooling out means redis-rb has no opinion about pool size, checkout timeouts or reconnection policy, and it can be embedded in a framework that has its own. The cost lands on the application: you own the pool, and you own the decision about how many connections each process opens. For a single-threaded script, none of this matters. For a Rails app with a thread pool, it is the first thing to get right.

## RESP3 by default and what changes in reply shapes

Starting in 6.0, the client negotiates RESP3 with HELLO 3 by default. The README states that command return values are unchanged from 5.x with one exception: GEOPOS and GEOSEARCH/GEORADIUS with WITHCOORD now return coordinates as Float instead of String.

The mechanism is worth understanding because it explains why the default moved. Under RESP2, structured replies arrive as flat arrays of bulk strings and the Ruby client reshapes them: HGETALL arrives as [field, value, field, value, ...] and is turned into a Hash, and sorted-set scores are converted from String to Float pair by pair. Under RESP3 the server tags those replies as native maps and doubles, so the final Hash and Float come out of the parser and the reshaping pass disappears.

The README reports benchmark figures from bench/resp_comparison.rb on Ruby 3.4 with 100-element replies: hash reads use roughly 10 to 25 percent less client CPU per call on both drivers, and with the hiredis binding sorted-set reads with scores gain up to 16 percent throughput plus about 20 percent less CPU per call. With the pure-Ruby driver those sorted-set reads are unchanged. Simple string commands and stream commands are unaffected in both protocols. Treat those numbers as the project's own measurements, not as an independent result.

RESP3 also enables push messages: server-initiated notifications on an existing connection, which the README describes as the foundation for CLIENT TRACKING invalidation events and for pub/sub over the regular command connection. Those features are groundwork, not shipped behaviour, in what the README documents.

## Installing redis-rb and running a first command

The README's install step is a single gem command. Run it and you get the library plus its dependencies:

```bash
gem install redis
```

Then require the library and instantiate the client. With no arguments it assumes a Redis server on localhost at port 6379 with a default configuration:

```ruby
require "redis"

redis = Redis.new
redis.set("mykey", "hello world")
# => "OK"

redis.get("mykey")
# => "hello world"
```

If your server is elsewhere, pass connection options. The README shows host, port and database selection, and also a redis:// URL form, which is often what you want when the address comes from an environment variable:

```ruby
redis = Redis.new(host: "10.0.1.1", port: 6380, db: 15)
```

The README also gives the URL form, and notes that passwords with special characters must be URL-encoded, for example with CGI.escape:

```ruby
redis = Redis.new(url: "redis://:p4ssw0rd@10.0.1.1:6380/15")
```

Unix sockets use path:, and password-protected instances take password:, or username: plus password: for ACL-authenticated connections.

For a threaded application, wrap the client rather than sharing it. The README's example uses ConnectionPool::Wrapper, which means call sites keep calling Redis-style methods and the wrapper handles checkout:

```ruby
module MyApp
  def self.redis
    @redis ||= ConnectionPool::Wrapper.new do
      Redis.new(url: ENV["REDIS_URL"])
    end
  end
end

MyApp.redis.incr("some-counter")
```

If you want to keep the pre-6.0 wire behaviour, pass protocol: 2 to Redis.new. Servers without RESP3 support are detected on connect and the client falls back to RESP2 automatically, so older servers need no configuration.

## CLIENT SETINFO and the proxies that reject it

On connect, redis-rb identifies itself with CLIENT SETINFO, so the library name and version appear in CLIENT LIST and CLIENT INFO as lib-name=redis-rb lib-ver=<Redis::VERSION>. Gems built on top of redis-rb can extend that identity with driver_info:, which is appended in parentheses rather than replacing the library name.

This is a small feature with a sharp edge. Servers older than 7.2 do not support CLIENT SETINFO, and according to the README they reject it, the error is ignored, and the connection proceeds normally. That is a reasonable failure mode. The harder case is a proxy or a server configuration that cannot tolerate the command at all. For that, the README provides driver_info: false, which disables client identification entirely.

If you run redis-rb behind a managed proxy and see connection errors that mention SETINFO, this is the first switch to check. It is also worth knowing that the sanitisation rules are opinionated: runs of characters the server would reject, such as spaces and non-printable bytes, and runs of the parentheses that delimit the suffix, are each collapsed to a single underscore or dropped at the edges.

## What redis-rb does not do

The README does not document automatic reconnection, retry policy or circuit breaking. That is not the same as saying the client never reconnects, but it does mean the documented surface gives you no knob for it. If you are building something where a dropped connection must be handled with a specific backoff, you will be writing that logic yourself around the client.

Clustering is likewise outside the main client. The repository has a cluster/ directory and the README's sentinel section covers failover with Redis Sentinel, but the connection examples in the README are single-endpoint: host, port, db, url, path. There is no README section describing cluster slot routing or MOVED/ASK redirection handling. If your workload needs a sharded cluster and you want the client to route keys, verify the cluster support in the cluster/ directory and the specs before committing.

Version support is a real constraint too. The README states that redis-rb targets Ruby 3.2 and newer, and Redis server versions designated for support by Redis. On an older Ruby, 6.0 is not an option regardless of what your Redis server runs.

The final limitation is conceptual. Because commands map one-to-one, redis-rb gives you no protection from expensive operations. A KEYS call is a KEYS call. The library will not stop you from blocking the server, and it will not batch round trips for you beyond whatever Redis commands you choose to send.

## How redis-rb compares with an object-mapping Redis client

The clearest alternative in the Ruby ecosystem is an object-mapping client, where Redis data is exposed through Ruby classes and attributes rather than raw commands. The difference is where the abstraction sits.

With redis-rb, redis.hgetall("user:1") returns a Hash whose keys and values are strings (or, under RESP3, values already in their final Ruby shape). You decide what that Hash means. With an object mapper, you declare a model, and the library generates the Redis commands behind attribute readers and writers. You stop writing HGETALL and start writing user.name.

That is a genuine gain when your data is record-shaped and you want validations, callbacks or associations. It is a loss when you are using Redis for what it is good at: counters, rate limiters, sorted-set leaderboards, streams, locks. Those patterns are command-shaped, not record-shaped, and an object layer between you and the command usually means fighting the abstraction or dropping to a raw connection anyway.

redis-rb also differs from a framework cache store, which is a narrow adapter over a handful of operations. A cache store is easier to wire into a Rails app but exposes a fraction of the command surface. If your application only ever reads and writes cached blobs, a cache store is less code. If it also runs INCR, ZADD or XADD, you want the command-level client underneath.

## Version 6.0, the MIT licence and upgrade cost

The repository is licensed MIT, which permits commercial and closed-source use, modification and redistribution provided the copyright notice and permission notice are retained. That is the standard permissive position; the LICENSE file in the repository root is the authoritative text, and anything beyond that is a question for your own legal review rather than something this article can settle.

The 6.0 release is the one to think about before upgrading. Its headline change is the RESP3 default, and the README is explicit that return values are unchanged from 5.x except for GEOPOS and GEOSEARCH/GEORADIUS with WITHCOORD, which now yield Float coordinates instead of String. Code that compares those coordinates as strings, or that serialises them expecting strings, is the code that breaks. Passing protocol: 2 restores the previous behaviour and is the cheapest way to defer the migration.

Beyond that exception, the upgrade story is quiet, which is what you want. The client still sends the same commands and returns the same shapes for everything else the README documents. Maintenance signals are visible in the repository: the last push to the default branch was on 2026-09-23, and the most recent release listed is v6.0.0 from 2026-07-31.

## Conclusion

Adopt redis-rb if you run a supported Ruby (3.2 or newer) against a Redis server version Redis still supports, and you want command names that match the Redis documentation instead of an object-mapper layer. Do not adopt it expecting a built-in pool or background reconnection logic: the README states each Redis instance owns exactly one connection behind a mutex, and it points to the connection_pool gem for anything concurrent. Before rolling it out, verify that your server accepts HELLO 3 or falls back cleanly, and check whether CLIENT SETINFO is tolerated on your server or proxy, since redis-rb sends it on connect unless you pass driver_info: false.

## FAQ

### How do I install redis-rb?

Install it with the gem command gem install redis. The README then shows requiring "redis" and creating a client with Redis.new, which assumes a server on localhost at port 6379.

### Does redis-rb include connection pooling?

No. The README states that each Redis instance has exactly one connection protected by a mutex, and recommends the connection_pool gem, showing a ConnectionPool::Wrapper example.

### How do I keep the old RESP2 behaviour in redis-rb 6.0?

Pass protocol: 2 when creating the client, as in Redis.new(protocol: 2). Servers without RESP3 support are detected on connect and the client falls back to RESP2 automatically.

### What return values changed in redis-rb 6.0?

The README says command return values are unchanged from 5.x except for GEOPOS and GEOSEARCH/GEORADIUS with WITHCOORD, which now return coordinates as Float instead of String.

## Sources

- [Issues](https://github.com/redis/redis-rb/issues)
- [License: MIT](https://github.com/redis/redis-rb/blob/master/LICENSE)
- [README](https://github.com/redis/redis-rb/blob/master/README.md)
- [redis/redis-rb on GitHub](https://github.com/redis/redis-rb)
- [Releases](https://github.com/redis/redis-rb/releases)

---

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