redis-rb

RubyCache LibraryRedisClientNoSQL

GitHub Overview

redis/redis-rb

A Ruby client library for Redis

Stars3,990
Watchers85
Forks1,035
Created:February 26, 2009
Language:Ruby
License:MIT License

Topics

None

Star History

redis/redis-rb Star History
Data as of: 10/22/2025, 08:07 AM

Cache Library

redis-rb

Overview

redis-rb is the official Ruby client library that provides an interface between Redis and Ruby applications, featuring a one-to-one mapping with Redis API while maintaining an idiomatic Ruby interface, with a design focused on performance optimization.

Details

redis-rb is the official Ruby client library that provides an interface between Redis and Ruby applications. It aims to match Redis' API one-to-one while still providing an idiomatic Ruby interface. Key features include thread-safety, client-side sharding, pipelining, and an obsession for performance. It provides comprehensive support for all Redis commands, Pub/Sub functionality, transactions (MULTI/EXEC), automatic failover through Sentinel support, distributed operations, and SSL/TLS connections. Integration with the hiredis driver enables speed optimization for large replies or big pipelines. By default, each Redis instance has one and only one connection to the server, and use of this connection is protected by a mutex. The client does not provide connection pooling; it is heavily recommended to use the connection_pool gem. Redis Sentinel support is available for automatic failover when using Redis 2.8+, and cluster support is provided through the companion gem "redis-clustering". The library supports SSL certificate authentication, configurable timeouts, reconnection attempt settings, and comprehensive error handling for production environments.

Pros and Cons

Pros

  • Official Support: Official Ruby client library for Redis
  • One-to-One API Mapping: Direct correspondence with Redis API for easy learning
  • High Performance: Speed optimization through hiredis driver support
  • Thread Safety: Safe concurrent access through mutex protection
  • Pipelining: Efficient batch execution of multiple commands
  • Sentinel Support: Automatic failover for high availability
  • Rich Features: Complete support for Pub/Sub, transactions, and distributed operations

Cons

  • No Connection Pooling: Requires separate connection_pool gem
  • Separate Cluster Support: Cluster functionality provided through separate gem
  • Single Connection: Limited to one connection per instance
  • Ruby Dependency: Strongly dependent on Ruby-specific features
  • Learning Curve: Advanced features (Sentinel, distributed operations) require expertise

Key Links

Code Examples

Installation and Basic Connection

# Gemfile
gem 'redis'

# Or with hiredis support (for performance)
gem 'redis'
gem 'hiredis-client'

# Installation
# bundle install

# Basic connection
require 'redis'

redis = Redis.new

# Detailed configuration
redis = Redis.new(host: "10.0.1.1", port: 6380, db: 15)

# URL connection
redis = Redis.new(url: "redis://:[email protected]:6380/15")

# SSL connection
redis = Redis.new(url: "rediss://10.0.1.1:6380/15")

# Connection test
puts redis.ping  # => PONG

Basic Key-Value Operations

require 'redis'

redis = Redis.new

# String set and get
redis.set('best_car_ever', 'Tesla Model S')
car = redis.get('best_car_ever')
puts car  # => "Tesla Model S"

# Key with expiration
redis.setex('session:123', 3600, 'session_data')  # Expires in 1 hour

# Numeric operations
redis.set('counter', 0)
count = redis.incr('counter')
puts count  # => 1

# Bulk operations for multiple keys
redis.mset('key1', 'value1', 'key2', 'value2', 'key3', 'value3')
values = redis.mget('key1', 'key2', 'key3')
puts values  # => ["value1", "value2", "value3"]

# Key existence check
if redis.exists('best_car_ever')
  puts 'Key exists'
end

# Key deletion
redis.del('best_car_ever')

List, Set, and Hash Operations

# List operations
redis.lpush('tasks', 'task1', 'task2', 'task3')
redis.rpush('tasks', 'task4')

# Get list contents
tasks = redis.lrange('tasks', 0, -1)
puts tasks  # => ["task3", "task2", "task1", "task4"]

# Pop elements from list
task = redis.lpop('tasks')
puts task  # => "task3"

# Set operations
redis.sadd('tags', 'ruby', 'redis', 'cache')
members = redis.smembers('tags')
puts members  # => ["ruby", "redis", "cache"]

# Set operations
redis.sadd('set1', 'a', 'b', 'c')
redis.sadd('set2', 'b', 'c', 'd')
intersection = redis.sinter('set1', 'set2')
puts intersection  # => ["b", "c"]

# Hash operations
redis.hset('user:1', 'name', 'John')
redis.hset('user:1', 'email', '[email protected]')
redis.hset('user:1', 'age', 30)

# Bulk hash set
redis.hmset('user:2', 'name', 'Alice', 'email', '[email protected]', 'age', 25)

user_data = redis.hgetall('user:1')
puts user_data  # => {"name"=>"John", "email"=>"john@example.com", "age"=>"30"}

name = redis.hget('user:1', 'name')
puts name  # => "John"

Pipelining

# Efficient execution of multiple commands with pipeline
redis.pipelined do |pipe|
  pipe.set('key1', 'value1')
  pipe.set('key2', 'value2')
  pipe.get('key1')
  pipe.get('key2')
  pipe.incr('counter')
end

# Using Future objects
redis.pipelined do |pipe|
  future_value1 = pipe.get('key1')
  future_value2 = pipe.get('key2')
  
  # Get values after pipeline execution
  puts future_value1.value  # => "value1"
  puts future_value2.value  # => "value2"
end

# Pipeline with transactions
redis.multi do |multi|
  multi.set('account:1:balance', 100)
  multi.set('account:2:balance', 200)
  multi.decrby('account:1:balance', 50)
  multi.incrby('account:2:balance', 50)
end

Pub/Sub Functionality

require 'redis'
require 'thread'

# Publisher side
def publisher(redis)
  sleep(1)  # Wait for subscriber setup
  5.times do |i|
    redis.publish('notifications', "Message #{i}")
    sleep(1)
  end
end

# Subscriber side
def subscriber(redis)
  redis.subscribe('notifications') do |on|
    on.subscribe do |channel, subscriptions|
      puts "Subscribed to ##{channel} (#{subscriptions} subscriptions)"
    end

    on.message do |channel, message|
      puts "Received message: #{message}"
    end

    on.unsubscribe do |channel, subscriptions|
      puts "Unsubscribed from ##{channel} (#{subscriptions} subscriptions)"
    end
  end
end

# Run publisher in separate thread
redis_pub = Redis.new
redis_sub = Redis.new

pub_thread = Thread.new { publisher(redis_pub) }

# Run subscriber
subscriber(redis_sub)

pub_thread.join

Connection Pool Usage

require 'redis'
require 'connection_pool'

# Connection pool configuration
pool = ConnectionPool.new(size: 5, timeout: 5) do
  Redis.new(host: 'localhost', port: 6379)
end

# Using connection pool
pool.with do |redis|
  redis.set('pooled_key', 'pooled_value')
  value = redis.get('pooled_key')
  puts value  # => "pooled_value"
end

# Multi-threaded usage
threads = []

10.times do |i|
  threads << Thread.new do
    pool.with do |redis|
      redis.incr('thread_counter')
      count = redis.get('thread_counter')
      puts "Thread #{i}: counter = #{count}"
    end
  end
end

threads.each(&:join)

Error Handling

require 'redis'

redis = Redis.new

begin
  redis.set('test_key', 'test_value')
  value = redis.get('test_key')
  puts value
  
rescue Redis::CannotConnectError
  puts "Redis connection failed"
  
rescue Redis::TimeoutError
  puts "Redis operation timed out"
  
rescue Redis::CommandError => e
  puts "Redis command error: #{e.message}"
  
rescue Redis::BaseError => e
  puts "Redis error: #{e.message}"
end

# Reconnection configuration
redis = Redis.new(
  reconnect_attempts: 3,
  reconnect_delay: 0.5,
  reconnect_delay_max: 5.0
)

# Disable reconnection for specific block
redis.without_reconnect do
  # No reconnection within this block
  redis.ping
end

Sentinel Configuration

require 'redis'

# Sentinel configuration
redis = Redis.new(
  url: "redis://mymaster",
  sentinels: [
    { host: "127.0.0.1", port: 26379 },
    { host: "127.0.0.1", port: 26380 },
    { host: "127.0.0.1", port: 26381 }
  ],
  role: :master
)

# Slave connection
redis_slave = Redis.new(
  url: "redis://mymaster", 
  sentinels: [
    { host: "127.0.0.1", port: 26379 },
    { host: "127.0.0.1", port: 26380 },
    { host: "127.0.0.1", port: 26381 }
  ],
  role: :slave
)

# Operations
redis.set('sentinel_key', 'sentinel_value')
value = redis_slave.get('sentinel_key')
puts value  # => "sentinel_value"

Distributed Redis Operations

require 'redis'
require 'redis/distributed'

# Distributed Redis configuration
distributed = Redis::Distributed.new([
  "redis://127.0.0.1:6379/0",
  "redis://127.0.0.1:6380/0", 
  "redis://127.0.0.1:6381/0"
])

# Distributed operations (consistent hashing for node selection)
distributed.set('user:1', 'Alice')   # Placed on node 1
distributed.set('user:2', 'Bob')     # Placed on node 2
distributed.set('user:3', 'Charlie') # Placed on node 3

# Data retrieval
user1 = distributed.get('user:1')
user2 = distributed.get('user:2')
puts "#{user1}, #{user2}"  # => "Alice, Bob"

# Get information from all nodes
distributed.nodes.each_with_index do |node, index|
  puts "Node #{index}: #{node.info['redis_version']}"
end

SSL/TLS Connection

require 'redis'

# SSL configuration
redis = Redis.new(
  host: 'localhost',
  port: 6380,
  ssl: true,
  ssl_params: {
    cert_file: '/path/to/client.crt',
    key_file: '/path/to/client.key',
    ca_file: '/path/to/ca.crt',
    verify_mode: OpenSSL::SSL::VERIFY_PEER
  }
)

# Or using URL
redis = Redis.new(url: "rediss://localhost:6380")

# Connection test
puts redis.ping  # => PONG

Advanced Caching Patterns

require 'redis'
require 'json'

class CacheManager
  def initialize(redis_client)
    @redis = redis_client
  end
  
  def cached_method(key, expiration = 3600)
    # Try to get from cache
    cached_value = @redis.get(key)
    return JSON.parse(cached_value) if cached_value
    
    # Execute block if not in cache
    result = yield
    
    # Save result to cache
    @redis.setex(key, expiration, JSON.dump(result))
    result
  end
  
  def invalidate_pattern(pattern)
    keys = @redis.keys(pattern)
    @redis.del(keys) if keys.any?
  end
end

# Usage example
redis = Redis.new
cache = CacheManager.new(redis)

# Cache expensive computation
result = cache.cached_method('expensive_calc', 1800) do
  # Heavy computation
  sleep(2)
  { result: (1..100000).sum, timestamp: Time.now }
end

puts result  # First call takes 2 seconds
result = cache.cached_method('expensive_calc') { nil }  # Second call returns immediately

# Pattern-based cache invalidation
cache.invalidate_pattern('user:*')