{"id":23646239,"url":"https://github.com/redis-rb/redis-cluster-client","last_synced_at":"2026-05-03T03:06:23.610Z","repository":{"id":36989326,"uuid":"472734564","full_name":"redis-rb/redis-cluster-client","owner":"redis-rb","description":"Redis cluster-aware client for Ruby","archived":false,"fork":false,"pushed_at":"2025-03-13T00:32:49.000Z","size":719,"stargazers_count":22,"open_issues_count":1,"forks_count":10,"subscribers_count":3,"default_branch":"master","last_synced_at":"2025-03-28T19:09:32.624Z","etag":null,"topics":["redis","ruby"],"latest_commit_sha":null,"homepage":"https://rubygems.org/gems/redis-cluster-client","language":"Ruby","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/redis-rb.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2022-03-22T11:21:08.000Z","updated_at":"2025-03-13T00:32:53.000Z","dependencies_parsed_at":"2023-02-18T06:45:20.309Z","dependency_job_id":"4c37ccfb-872f-41c3-8bc8-b1a770c251a2","html_url":"https://github.com/redis-rb/redis-cluster-client","commit_stats":{"total_commits":321,"total_committers":12,"mean_commits":26.75,"dds":0.1339563862928349,"last_synced_commit":"628276b0266aaa66c49c8d19467e4135a5070567"},"previous_names":[],"tags_count":75,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/redis-rb%2Fredis-cluster-client","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/redis-rb%2Fredis-cluster-client/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/redis-rb%2Fredis-cluster-client/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/redis-rb%2Fredis-cluster-client/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/redis-rb","download_url":"https://codeload.github.com/redis-rb/redis-cluster-client/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":247242680,"owners_count":20907134,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["redis","ruby"],"created_at":"2024-12-28T13:19:30.395Z","updated_at":"2026-05-03T03:06:23.595Z","avatar_url":"https://github.com/redis-rb.png","language":"Ruby","funding_links":[],"categories":[],"sub_categories":[],"readme":"[![Gem Version](https://badge.fury.io/rb/redis-cluster-client.svg)](https://badge.fury.io/rb/redis-cluster-client)\n[![Ask DeepWiki](https://deepwiki.com/badge.svg)](https://deepwiki.com/redis-rb/redis-cluster-client)\n![Test status](https://github.com/redis-rb/redis-cluster-client/actions/workflows/test.yaml/badge.svg?branch=master)\n![Release status](https://github.com/redis-rb/redis-cluster-client/actions/workflows/release.yaml/badge.svg)\n\nRedis Cluster Client\n===============================================================================\nThis library is a client for [Redis cluster](https://redis.io/docs/reference/cluster-spec/).\nIt depends on [redis-client](https://github.com/redis-rb/redis-client).\nSo it would be better to read `redis-client` documents first.\n\n## Background\nThis gem underlies the official gem [redis-clustering](https://rubygems.org/gems/redis-clustering).\nThe redis-clustering gem was decoupled from [the redis gem](https://rubygems.org/gems/redis) as of `v5`.\nBoth are maintained by [the repository](https://github.com/redis/redis-rb) in the official organization.\nThe redis gem supported cluster mode since [the pull request](https://github.com/redis/redis-rb/pull/716) was merged until `v4`.\nYou can see more details and reasons in [the issue](https://github.com/redis/redis-rb/issues/1070) if you are interested.\n\n## Installation\n```ruby\ngem 'redis-cluster-client'\n```\n\n## Initialization\n| key | type | default | description |\n| --- | --- | --- | --- |\n| `:nodes` | String or Hash or Array\u003cString, Hash\u003e | `['redis://127.0.0.1:6379']` | node addresses for startup connection |\n| `:replica` | Boolean | `false` | `true` if client should use scale read feature |\n| `:replica_affinity` | Symbol or String | `:random` | scale reading strategy, `:random`, `random_with_primary` or `:latency` are valid |\n| `:fixed_hostname` | String | `nil` | required if client should connect to single endpoint with SSL |\n| `:slow_command_timeout` | Integer | `-1` | timeout used for slow commands that fetch metadata, e.g. CLUSTER SHARDS, COMMAND |\n| `:concurrency` | Hash | `{ model: :none }` | concurrency settings, `:on_demand`, `:pooled` and `:none` are valid models, size is a max number of workers, `:none` model is no concurrency, Please choose the one suited to your environment if needed. |\n| `:connect_with_original_config` | Boolean | `false` | `true` if client should retry the connection using the original endpoint that was passed in |\n| `:max_startup_sample` | Integer | `3` | maximum number of nodes to fetch `CLUSTER SHARDS` information for startup |\n\nAlso, [the other generic options](https://github.com/redis-rb/redis-client#configuration) can be passed.\nBut `:url`, `:host`, `:port` and `:path` are ignored because they conflict with the `:nodes` option.\n\n```ruby\nrequire 'redis_cluster_client'\n\n# The following examples are Docker containers on localhost.\n# The client first attempts to connect to redis://127.0.0.1:6379 internally.\n\n# To connect to primary nodes only\nRedisClient.cluster.new_client\n#=\u003e #\u003cRedisClient::Cluster 172.20.0.2:6379, 172.20.0.6:6379, 172.20.0.7:6379\u003e\n\n# To connect to all nodes to use scale reading feature\nRedisClient.cluster(replica: true).new_client\n#=\u003e #\u003cRedisClient::Cluster 172.20.0.2:6379, 172.20.0.3:6379, 172.20.0.4:6379, 172.20.0.5:6379, 172.20.0.6:6379, 172.20.0.7:6379\u003e\n\n# To connect to all nodes to use scale reading feature + make reads equally likely from replicas and primary\nRedisClient.cluster(replica: true, replica_affinity: :random_with_primary).new_client\n#=\u003e #\u003cRedisClient::Cluster 172.20.0.2:6379, 172.20.0.3:6379, 172.20.0.4:6379, 172.20.0.5:6379, 172.20.0.6:6379, 172.20.0.7:6379\u003e\n\n# To connect to all nodes to use scale reading feature prioritizing low-latency replicas\nRedisClient.cluster(replica: true, replica_affinity: :latency).new_client\n#=\u003e #\u003cRedisClient::Cluster 172.20.0.2:6379, 172.20.0.3:6379, 172.20.0.4:6379, 172.20.0.5:6379, 172.20.0.6:6379, 172.20.0.7:6379\u003e\n\n# With generic options for redis-client\nRedisClient.cluster(timeout: 3.0).new_client\n```\n\n```ruby\n# To connect with a subset of nodes for startup\nRedisClient.cluster(nodes: %w[redis://node1:6379 redis://node2:6379]).new_client\n```\n\n```ruby\n# To connect with a subset of auth-needed nodes for startup\n\n## with URL:\n### User name and password should be URI encoded and the same in every node.\nusername = 'myuser'\npassword = URI.encode_www_form_component('!\u0026\u003c123-abc\u003e')\nRedisClient.cluster(nodes: %W[redis://#{username}:#{password}@node1:6379 redis://#{username}:#{password}@node2:6379]).new_client\n\n## with options:\nRedisClient.cluster(nodes: %w[redis://node1:6379 redis://node2:6379], username: 'myuser', password: '!\u0026\u003c123-abc\u003e').new_client\n```\n\n```ruby\n# To connect to single endpoint\nRedisClient.cluster(nodes: 'redis://endpoint.example.com:6379').new_client\n```\n\n```ruby\n# To connect to single endpoint with SSL/TLS (such as Amazon ElastiCache for Redis)\nRedisClient.cluster(nodes: 'rediss://endpoint.example.com:6379').new_client\n```\n\n```ruby\n# To connect to NAT-ted endpoint with SSL/TLS (such as Microsoft Azure Cache for Redis)\nRedisClient.cluster(nodes: 'rediss://endpoint.example.com:6379', fixed_hostname: 'endpoint.example.com').new_client\n```\n\n```ruby\n# To specify a timeout for \"slow\" commands (CLUSTER SHARDS, COMMAND)\nRedisClient.cluster(slow_command_timeout: 4).new_client\n```\n\n```ruby\n# To specify concurrency settings\nRedisClient.cluster(concurrency: { model: :on_demand, size: 6 }).new_client\nRedisClient.cluster(concurrency: { model: :pooled, size: 3 }).new_client\nRedisClient.cluster(concurrency: { model: :none }).new_client\n\n# The above settings are used by sending commands to multiple nodes like pipelining.\n# Please choose the one suited your workloads.\n```\n\n```ruby\n# To reconnect using the original configuration options on error. This can be useful when using a DNS endpoint and the underlying host IPs are all updated\nRedisClient.cluster(connect_with_original_config: true).new_client\n```\n\n## Interfaces\nThe following methods are able to be used like `redis-client`.\n* `#call`\n* `#call_v`\n* `#call_once`\n* `#call_once_v`\n* `#blocking_call`\n* `#blocking_call_v`\n* `#scan`\n* `#sscan`\n* `#hscan`\n* `#zscan`\n* `#pipelined`\n* `#multi`\n* `#pubsub`\n* `#close`\n\nThe `#scan` method iterates all keys around every node seamlessly.\nThe `#pipelined` method splits and sends commands to each node and aggregates replies.\nThe `#multi` method supports the transaction feature but you should use a hashtag for your keys.\nThe `#pubsub` method supports sharded subscriptions.\nEvery interface handles redirections and resharding states internally.\n\n## Multiple keys and CROSSSLOT error\nA subset of commands can be passed multiple keys.\nIn cluster mode, these commands have a constraint that passed keys should belong to the same slot\nand not just the same node.\nTherefore, the following error occurs:\n\n```\n$ redis-cli -c mget key1 key2 key3\n(error) CROSSSLOT Keys in request don't hash to the same slot\n\n$ redis-cli -c cluster keyslot key1\n(integer) 9189\n\n$ redis-cli -c cluster keyslot key2\n(integer) 4998\n\n$ redis-cli -c cluster keyslot key3\n(integer) 935\n```\n\nFor the constraint, Redis cluster provides a feature to be able to bias keys to the same slot with a hash tag.\n\n```\n$ redis-cli -c mget {key}1 {key}2 {key}3\n1) (nil)\n2) (nil)\n3) (nil)\n\n$ redis-cli -c cluster keyslot {key}1\n(integer) 12539\n\n$ redis-cli -c cluster keyslot {key}2\n(integer) 12539\n\n$ redis-cli -c cluster keyslot {key}3\n(integer) 12539\n```\n\nIn addition, this gem handles multiple keys without a hash tag in MGET, MSET and DEL commands\nusing pipelining internally automatically.\nIf the first key includes a hash tag, this gem sends the command to the node as is.\nIf the first key doesn't have a hash tag, this gem converts the command into single-key commands\nand sends them to nodes with pipelining, then gathering replies and returning them.\n\n```ruby\nr = RedisClient.cluster.new_client\n#=\u003e #\u003cRedisClient::Cluster 127.0.0.1:6379\u003e\n\nr.call('mget', 'key1', 'key2', 'key3')\n#=\u003e [nil, nil, nil]\n\nr.call('mget', '{key}1', '{key}2', '{key}3')\n#=\u003e [nil, nil, nil]\n```\n\nThis behavior is for higher-level libraries to maintain compatibility with a standalone client.\nYou can exploit this behavior for migrating from a standalone server to a cluster.\nAlthough repeated single-key queries are slower than pipelining,\npipelined queries are still slower than a single-slot query with multiple keys.\nHence, we recommend using a hash tag in this use case for better performance.\n\n## Transactions\nThis gem supports [Redis transactions](https://redis.io/topics/transactions), including atomicity with `MULTI`/`EXEC`,\nand conditional execution with `WATCH`. Redis does not support cross-node transactions, so all keys used within a\ntransaction must live in the same key slot. To use transactions, you can use `#multi` method same as the [redis-client](https://github.com/redis-rb/redis-client#usage):\n\n```ruby\ncli.multi do |tx|\n  tx.call('INCR', 'my_key')\n  tx.call('INCR', 'my_key')\nend\n```\n\nMore commonly, however, you will want to perform transactions across multiple keys. To do this,\nyou need to ensure that all keys used in the transaction hash to the same slot;\nRedis provides a mechanism called [hashtags](https://redis.io/docs/reference/cluster-spec/#hash-tags) to achieve this.\nIf a key contains a hashtag (e.g. in the key `{foo}bar`, the hashtag is `foo`),\nthen it is guaranteed to hash to the same slot (and thus always live on the same node) as other keys which contain the same hashtag.\n\nSo, whilst it's not possible in Redis cluster to perform a transaction on the keys `foo` and `bar`,\nit _is_ possible to perform a transaction on the keys `{tag}foo` and `{tag}bar`.\nTo perform such transactions on this gem, use the hashtag:\n\n```ruby\ncli.multi do |tx|\n  tx.call('INCR', '{user123}coins_spent')\n  tx.call('DECR', '{user123}coins_available')\nend\n```\n\n```ruby\n# Conditional execution with WATCH can be used to e.g. atomically swap two keys\ncli.call('MSET', '{myslot}1', 'v1', '{myslot}2', 'v2')\ncli.multi(watch: %w[{myslot}1 {myslot}2]) do |tx|\n  old_key1 = cli.call('GET', '{myslot}1')\n  old_key2 = cli.call('GET', '{myslot}2')\n  tx.call('SET', '{myslot}1', old_key2)\n  tx.call('SET', '{myslot}2', old_key1)\nend\n# This transaction will swap the values of {myslot}1 and {myslot}2 only if no concurrent connection modified\n# either of the values\n```\n\nYou can early return out of your block with a `next` statement if you want to cancel your transaction.\nIn this context, don't use `break` and `return` statements.\n\n```ruby\n# The transaction isn't executed.\ncli.multi do |tx|\n  next if some_conditions?\n\n  tx.call('SET', '{key}1', '1')\n  tx.call('SET', '{key}2', '2')\nend\n```\n\n```ruby\n# The watching state is automatically cleared with an execution of an empty transaction.\ncli.multi(watch: %w[{key}1 {key}2]) do |tx|\n  next if some_conditions?\n\n  tx.call('SET', '{key}1', '1')\n  tx.call('SET', '{key}2', '2')\nend\n```\n\n`RedisClient::Cluster#multi` is aware of redirections and node failures like ordinary calls to `RedisClient::Cluster`,\nbut because you may have written non-idempotent code inside your block, the block is called once if e.g. the slot\nit is operating on moves to a different node.\n\n## ACL\nThe cluster client internally calls [COMMAND](https://redis.io/commands/command) and [CLUSTER SHARDS](https://redis.io/commands/cluster-shards) commands to operate correctly.\nPlease grant the following permissions.\n\n```ruby\n# The default user is administrator.\ncli1 = RedisClient.cluster.new_client\n\n# To create a user with permissions\n# Typically, user settings are configured in the config file for the server beforehand.\ncli1.call('ACL', 'SETUSER', 'foo', 'ON', '+COMMAND', '+CLUSTER|SHARDS', '+PING', '\u003emysecret')\n\n# To initialize client with the user\ncli2 = RedisClient.cluster(username: 'foo', password: 'mysecret').new_client\n\n# The user can only call the PING command.\ncli2.call('PING')\n#=\u003e \"PONG\"\n\ncli2.call('GET', 'key1')\n#=\u003e NOPERM this user has no permissions to run the 'get' command (RedisClient::PermissionError)\n```\n\nOtherwise:\n\n```ruby\nRedisClient.cluster(username: 'foo', password: 'mysecret').new_client\n#=\u003e Redis client could not fetch cluster information: NOPERM this user has no permissions to run the 'cluster|nodes' command (RedisClient::Cluster::InitialSetupError)\n```\n\n## Connection pooling\nYou can use the internal connection pooling feature implemented by [redis-client](https://github.com/redis-rb/redis-client#usage) if needed.\n\n```ruby\n# example of docker on localhost\nRedisClient.cluster.new_pool(timeout: 1.0, size: 2)\n#=\u003e #\u003cRedisClient::Cluster 172.21.0.3:6379, 172.21.0.6:6379, 172.21.0.7:6379\u003e\n```\n\n## Connection drivers\nPlease see [redis-client](https://github.com/redis-rb/redis-client#drivers).\n\n## Development\nPlease make sure the following tools are installed on your machine.\n\n| Tool | Version | URL |\n| --- | --- | --- |\n| Docker | latest stable | https://docs.docker.com/engine/install/ |\n| Ruby | latest stable | https://www.ruby-lang.org/en/ |\n\nPlease fork this repository and check out the code.\n\n```\n$ git clone git@github.com:your-account-name/redis-cluster-client.git\n$ cd redis-cluster-client/\n$ git remote add upstream https://github.com/redis-rb/redis-cluster-client.git\n$ git fetch -p upstream\n```\n\nPlease do the following steps.\n\n* Build a Redis cluster with Docker\n* Install gems\n* Run basic test cases\n\n```\n## If you use Docker server and your OS is Linux:\n$ bundle config set path '.bundle'\n$ bundle install --jobs=$(grep process /proc/cpuinfo | wc -l)\n$ docker compose up\n$ bundle exec rake test\n\n## else:\n$ docker compose --profile ruby up\n$ docker compose --profile ruby exec ruby bundle install\n$ docker compose --profile ruby exec ruby bundle exec rake test\n```\n\nYou can see more information in the YAML file for GitHub Actions.\n\n## Migration\nThis library might help you if you want to migrate your Redis from a standalone server to a cluster.\nHere is an example code.\n\n```ruby\n# frozen_string_literal: true\n\nrequire 'bundler/inline'\n\ngemfile do\n  source 'https://rubygems.org'\n  gem 'redis-cluster-client'\nend\n\nsrc = RedisClient.config(url: ENV.fetch('REDIS_URL')).new_client\ndest = RedisClient.cluster(nodes: ENV.fetch('REDIS_CLUSTER_URL')).new_client\nnode = dest.instance_variable_get(:@router).instance_variable_get(:@node)\n\nsrc.scan do |key|\n  slot = ::RedisClient::Cluster::KeySlotConverter.convert(key)\n  node_key = node.find_node_key_of_primary(slot)\n  host, port = ::RedisClient::Cluster::NodeKey.split(node_key)\n  src.blocking_call(10, 'MIGRATE', host, port, key, 0, 7, 'COPY', 'REPLACE')\nend\n```\n\nFurther optimization is needed to perform well in production environments with large numbers of keys.\nAlso, it should handle errors.\n\n## See also\n* https://redis.io/docs/reference/cluster-spec/\n* https://github.com/redis/redis-rb/issues/1070\n* https://github.com/redis/redis/issues/8948\n* https://github.com/valkey-io/valkey/issues/384\n* https://github.com/antirez/redis-rb-cluster\n* https://twitter.com/antirez\n* https://bsky.app/profile/antirez.bsky.social\n* http://antirez.com/latest/0\n* https://www.youtube.com/@antirez\n* https://www.twitch.tv/thetrueantirez/\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fredis-rb%2Fredis-cluster-client","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fredis-rb%2Fredis-cluster-client","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fredis-rb%2Fredis-cluster-client/lists"}