From 678cc01fd5142a9fccec14b94fa715bd0d98225f Mon Sep 17 00:00:00 2001 From: Andy Stark Date: Thu, 8 Oct 2026 16:28:08 +0100 Subject: [PATCH] DOC-7168 Add a connect page for redis-rb (Ruby) New ruby/connect.md covering basic connection (ACL user, URL with URL-encoded credentials, Unix socket), cluster via redis-clustering, Sentinel, TLS and mutual TLS, connection pools, timeouts and reconnection, and the RESP3 default. Linked from ruby/_index.md. Unsupported features (client-side caching, smart client handoffs, Entra ID) are left out rather than marked unsupported. All 21 code blocks ran against local servers (standalone with an ACL user and Unix socket, TLS with optional and required client certs, a 3+3 cluster, and Sentinel), with test copies differing only in ports, hosts, passwords, cert paths and key names. Versions: redis 6.0.0, redis-client 0.30.1 (the exact version redis 6.0.0 pins), connection_pool 3.0.2, redis-clustering 6.0.0 + redis-cluster-client 0.16.7 loaded from GitHub tags because RubyGems was blocked; Ruby 4.0.6, Redis 7.2.7. Two caveats rest on measurement. The failover note: a forced SENTINEL FAILOVER lost 22 and 20 acknowledged writes, because redis-rb only notices the switch when the demoted primary returns READONLY. The retry note: with the default reconnect_attempts of 1, a reply dropped by a proxy made INCR run twice, and even with no retry the server had applied it before the client raised. Learned: redis-rb defaults to one retry (redis-client to zero), so writes can run twice out of the box Constraint: every code block here is run-verified; re-run any edit against a real server Rejected: copying the redis-rb url docstring | it says url overrides options, but explicit options win (config.rb:233-244, tested) Directive: keep role: :replica, not :slave, in examples; both work but the docs say replica Gaps: gem install lines not run (RubyGems blocked); hiredis driver cert-path rule is source-only; RESP2 fallback not run Recheck: Sentinel failover caveat if redis-rb starts subscribing to +switch-master Ticket: DOC-7168 Co-Authored-By: Claude Opus 5.5 --- content/develop/clients/ruby/_index.md | 3 + content/develop/clients/ruby/connect.md | 307 ++++++++++++++++++++++++ 2 files changed, 310 insertions(+) create mode 100644 content/develop/clients/ruby/connect.md diff --git a/content/develop/clients/ruby/_index.md b/content/develop/clients/ruby/_index.md index 8644734476..ef128452c1 100644 --- a/content/develop/clients/ruby/_index.md +++ b/content/develop/clients/ruby/_index.md @@ -51,6 +51,9 @@ Close the connection when you're done. {{< jupyter-example set="landing" step="close" depends="connect" lang_filter="Ruby" description="Foundational: Properly close a Redis client connection to release resources" difficulty="beginner" no_output="true" />}} +To connect to a cluster, through Redis Sentinel, or over TLS, see +[Connect to the server](/content/develop/clients/ruby/connect.md). + ## More information The diff --git a/content/develop/clients/ruby/connect.md b/content/develop/clients/ruby/connect.md new file mode 100644 index 0000000000..8637e59542 --- /dev/null +++ b/content/develop/clients/ruby/connect.md @@ -0,0 +1,307 @@ +--- +categories: +- docs +- develop +- stack +- oss +- rs +- rc +- oss +- kubernetes +- clients +description: Connect your Ruby application to a Redis database +linkTitle: Connect +title: Connect to the server +weight: 1 +--- + +## Basic connection + +Create a `Redis` object with the host, port, and database number of your server. The +defaults are `localhost`, port 6379, and database 0. If you don't pass a host, port, socket +path, or URL, the client uses the URL in the `REDIS_URL` environment variable, if it's set. + +```ruby +require "redis" + +r = Redis.new(host: "localhost", port: 6379, db: 0) +``` + +Store and retrieve a string to test the connection. + +```ruby +r.set("foo", "bar") +# => "OK" + +r.get("foo") +# => "bar" +``` + +To connect as an [access control list (ACL)](/content/operate/oss_and_stack/management/security/acl.md) +user, pass `username` and `password`: + +```ruby +r = Redis.new( + host: "localhost", + port: 6379, + username: "default", # use your Redis user + password: "secret", # use your Redis password +) +``` + +You can also pass the connection details as a `redis://` URL. If you pass other options +with `url`, such as `password`, those options override the values in the URL. + +```ruby +r = Redis.new(url: "redis://default:secret@localhost:6379/0") +``` + +The client URL-decodes the username and password, so you must URL-encode any special +characters in them, such as `@`, `:`, `/`, or `+`: + +```ruby +require "uri" + +password = URI.encode_www_form_component("p@ss/word+1") +r = Redis.new(url: "redis://default:#{password}@localhost:6379") +``` + +To connect through a Unix socket, pass the socket path in `path`, or use a `unix://` URL: + +```ruby +r = Redis.new(path: "/var/run/redis/redis.sock") + +r = Redis.new(url: "unix:///var/run/redis/redis.sock") +``` + +## Connect to a Redis cluster + +Cluster support is in the separate `redis-clustering` gem: + +```bash +gem install redis-clustering +``` + +Create a `Redis::Cluster` object with a list of cluster nodes. The list doesn't need to +include every node, because the client discovers the rest of the cluster from the nodes +you list. + +```ruby +require "redis-clustering" + +rc = Redis::Cluster.new(nodes: [ + "redis://localhost:16379", + "redis://localhost:16380", + "redis://localhost:16381", +]) + +rc.set("foo", "bar") +# => "OK" + +rc.get("foo") +# => "bar" +``` + +To send read-only commands to replicas, set `replica: true`. Use `replica_affinity` to +choose a replica: `:random` (the default) picks a random replica, `:random_with_primary` +picks from the replicas and the primary, and `:latency` picks the replica with the lowest +latency. Replication is asynchronous, so a replica can return stale data. + +```ruby +rc = Redis::Cluster.new( + nodes: ["redis://localhost:16379"], + replica: true, + replica_affinity: :latency, +) +``` + +## Connect to Redis Sentinel + +To connect through [Redis Sentinel](/content/operate/oss_and_stack/management/sentinel.md), +pass `sentinels` and `name` to `Redis.new`. Set `name` to the name of the primary that the +Sentinels monitor. The client asks the Sentinels for the current address of the primary. + +```ruby +require "redis" + +SENTINELS = [ + { host: "localhost", port: 26379 }, + { host: "localhost", port: 26380 }, + { host: "localhost", port: 26381 }, +] + +r = Redis.new(name: "mymaster", sentinels: SENTINELS) + +r.set("foo", "bar") +# => "OK" + +r.get("foo") +# => "bar" +``` + +The Sentinels and the data nodes have separate credentials, and neither inherits the +other's. Set `sentinel_password` (and `sentinel_username`, if your Sentinels use ACL users) +for the Sentinels, and `username` and `password` for the primary and replicas: + +```ruby +r = Redis.new( + name: "mymaster", + sentinels: SENTINELS, + sentinel_password: "sentinel-secret", # use your Sentinel password + username: "default", # use your Redis user + password: "secret", # use your Redis password +) +``` + +The Sentinel connections use your other options, such as the timeouts and `ssl`. + +To connect to a replica, set `role: :replica`. The client connects to a random replica. +Replication is asynchronous, so a replica can return stale data, and writes fail with +`Redis::ReadOnlyError`. + +```ruby +replica = Redis.new(name: "mymaster", sentinels: SENTINELS, role: :replica) + +replica.get("foo") +# => "bar" +``` + +redis-rb doesn't listen for failover announcements from the Sentinels. After a failover, +the client keeps using the old primary until Sentinel reconfigures it as a replica and it +starts rejecting writes. The client then reconnects to the new primary. Any writes that the +old primary accepts during that time are lost. + +## Connect to your production Redis with TLS + +When you deploy your application, use Transport Layer Security (TLS) and follow the +[Redis security](/content/operate/oss_and_stack/management/security/_index.md) guidelines. + +Set `ssl: true`, or use a `rediss://` URL, and pass the TLS settings in `ssl_params`. Set +`ca_file` to the certificate authority (CA) certificate that signed the server's +certificate: + +```ruby +r = Redis.new( + url: "rediss://default:secret@my-redis.example.com:6379", + ssl_params: { ca_file: "./redis_ca.pem" }, +) +``` + +If the server requires clients to authenticate with a certificate, also pass the client +certificate and private key in `cert` and `key`: + +```ruby +r = Redis.new( + host: "my-redis.example.com", + port: 6379, + username: "default", # use your Redis user + password: "secret", # use your Redis password + ssl: true, + ssl_params: { + ca_file: "./redis_ca.pem", + cert: OpenSSL::X509::Certificate.new(File.read("./redis_user.crt")), + key: OpenSSL::PKey.read(File.read("./redis_user_private.key")), + }, +) + +r.set("foo", "bar") +# => "OK" + +r.get("foo") +# => "bar" +``` + +`cert` and `key` also accept file paths, such as `cert: "./redis_user.crt"`. If you use the +`hiredis` driver, `cert` and `key` must be file paths. + +## Connect with a connection pool + +A `Redis` object holds a single connection, and a lock serializes access to it. You can +share one object between threads safely, but each thread waits for the others to finish +their commands. To run commands from several threads in parallel, use a pool of `Redis` +objects from the `connection_pool` gem: + +```bash +gem install connection_pool +``` + +Create a `ConnectionPool` and call `with` to check out a connection for a block of +commands: + +```ruby +require "connection_pool" +require "redis" + +pool = ConnectionPool.new(size: 5, timeout: 5) do + Redis.new(host: "localhost", port: 6379) +end + +pool.with do |r| + r.set("foo", "bar") + r.get("foo") +end +# => "bar" +``` + +`ConnectionPool::Wrapper` wraps a pool in an object that you call like a `Redis` object. +Each method call checks out a connection for that call only, so consecutive calls can run on +different connections. Use `with` when several commands must run on the same connection. + +```ruby +redis = ConnectionPool::Wrapper.new(size: 5, timeout: 5) do + Redis.new(host: "localhost", port: 6379) +end + +redis.set("foo", "bar") +redis.get("foo") +# => "bar" +``` + +See [Connection pools and multiplexing](/content/develop/clients/pools-and-muxing.md) for +more information. + +## Timeouts and reconnection + +The connect, read, and write timeouts default to 1 second. Set `timeout` to change all +three, or set `connect_timeout`, `read_timeout`, and `write_timeout` individually. All +values are in seconds. + +```ruby +r = Redis.new( + host: "localhost", + port: 6379, + connect_timeout: 0.5, + read_timeout: 2.0, + write_timeout: 2.0, +) +``` + +After a connection error, the client reconnects and retries the command once by default. +Set `reconnect_attempts` to an `Integer` to change the number of retries, or to an `Array` +of delays in seconds to wait before each retry: + +```ruby +r = Redis.new(host: "localhost", port: 6379, reconnect_attempts: [0.05, 0.25, 1.0]) +``` + +A retry can run a command twice: if the connection fails after the server runs a command +but before the client reads the reply, the client sends the command again. For commands +that aren't idempotent, such as `INCR` or `LPUSH`, set `reconnect_attempts: 0`, or run them +inside `without_reconnect`: + +```ruby +r.without_reconnect do + r.incr("counter") +end +``` + +## Protocol + +Starting with redis-rb 6.0, the client uses +[RESP3](/content/develop/reference/protocol-spec.md#resp-versions) (Redis serialization +protocol version 3) by default. If the server doesn't support RESP3, the client falls +back to RESP2 and prints a warning. To use RESP2, set `protocol: 2`: + +```ruby +r = Redis.new(host: "localhost", port: 6379, protocol: 2) +```