redis-rb

RubyキャッシュライブラリRedisクライアントNoSQL

GitHub概要

redis/redis-rb

A Ruby client library for Redis

スター3,990
ウォッチ85
フォーク1,035
作成日:2009年2月26日
言語:Ruby
ライセンス:MIT License

トピックス

なし

スター履歴

redis/redis-rb Star History
データ取得日時: 2025/10/22 08:07

キャッシュライブラリ

redis-rb

概要

redis-rbは、RedisとRubyアプリケーション間のインターフェースを提供する公式Rubyクライアントライブラリで、RedisのAPIと1対1の対応関係を保ちながら、慣用的なRubyインターフェースを提供し、パフォーマンスに重点を置いた設計が特徴です。

詳細

redis-rb(レディス・アールビー)は、RedisとRubyアプリケーション間のインターフェースを提供する公式Rubyクライアントライブラリです。RedisのAPIと1対1の対応関係を保ちながら、慣用的なRubyインターフェースを提供することを目指しています。スレッドセーフ性、クライアント側のシャーディング、パイプライニング、そしてパフォーマンスへの執着を特徴としています。すべてのRedisコマンドの包括的なサポートを提供し、Pub/Sub機能、トランザクション(MULTI/EXEC)、Sentinelサポートによる自動フェイルオーバー、分散操作、SSL/TLS接続をサポートしています。hiredisドライバーとの統合により、大きな応答や大規模なパイプラインでの速度最適化が可能です。デフォルトでは、各Redisインスタンスはサーバーに対して1つの接続のみを持ち、この接続の使用はミューテックスによって保護されています。接続プールの機能は提供されておらず、connection_poolgemの使用が強く推奨されています。Redis 2.8以上でSentinel機能を使用する場合は自動フェイルオーバーが可能で、クラスターサポートはcompanion gem「redis-clustering」で提供されています。SSL証明書認証、設定可能なタイムアウト、再接続試行の設定、豊富なエラーハンドリングなど、プロダクション環境での使用に必要な機能を包括的にサポートしています。

メリット・デメリット

メリット

  • 公式サポート: Redisの公式Rubyクライアントライブラリ
  • 1対1 API対応: RedisのAPIと1対1の対応関係で学習が容易
  • 高性能: hiredisドライバーサポートによる速度最適化
  • スレッドセーフ: ミューテックスによる安全な並行アクセス
  • パイプライニング: 複数コマンドの効率的な一括実行
  • Sentinel対応: 高可用性のための自動フェイルオーバー
  • 豊富な機能: Pub/Sub、トランザション、分散操作の完全サポート

デメリット

  • 接続プール非提供: 別途connection_poolgemが必要
  • クラスター分離: クラスター機能は別gemでの提供
  • 単一接続: インスタンス毎に1接続の制限
  • Ruby依存: Ruby固有の機能に強く依存した設計
  • 学習コスト: 高度な機能(Sentinel、分散等)の習得が必要

主要リンク

書き方の例

インストールと基本接続

# Gemfile
gem 'redis'

# または hiredis サポート付き(高速化)
gem 'redis'
gem 'hiredis-client'

# インストール
# bundle install

# 基本接続
require 'redis'

redis = Redis.new

# 詳細設定での接続
redis = Redis.new(host: "10.0.1.1", port: 6380, db: 15)

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

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

# 接続テスト
puts redis.ping  # => PONG

基本的なキー・バリュー操作

require 'redis'

redis = Redis.new

# 文字列の設定と取得
redis.set('best_car_ever', 'Tesla Model S')
car = redis.get('best_car_ever')
puts car  # => "Tesla Model S"

# 期限付きキー設定
redis.setex('session:123', 3600, 'session_data')  # 1時間後に期限切れ

# 数値の操作
redis.set('counter', 0)
count = redis.incr('counter')
puts count  # => 1

# 複数キーの一括操作
redis.mset('key1', 'value1', 'key2', 'value2', 'key3', 'value3')
values = redis.mget('key1', 'key2', 'key3')
puts values  # => ["value1", "value2", "value3"]

# キーの存在確認
if redis.exists('best_car_ever')
  puts 'Key exists'
end

# キーの削除
redis.del('best_car_ever')

リスト・セット・ハッシュ操作

# リスト操作
redis.lpush('tasks', 'task1', 'task2', 'task3')
redis.rpush('tasks', 'task4')

# リストの内容を取得
tasks = redis.lrange('tasks', 0, -1)
puts tasks  # => ["task3", "task2", "task1", "task4"]

# リストから要素を取得
task = redis.lpop('tasks')
puts task  # => "task3"

# セット操作
redis.sadd('tags', 'ruby', 'redis', 'cache')
members = redis.smembers('tags')
puts members  # => ["ruby", "redis", "cache"]

# セットの演算
redis.sadd('set1', 'a', 'b', 'c')
redis.sadd('set2', 'b', 'c', 'd')
intersection = redis.sinter('set1', 'set2')
puts intersection  # => ["b", "c"]

# ハッシュ操作
redis.hset('user:1', 'name', 'John')
redis.hset('user:1', 'email', '[email protected]')
redis.hset('user:1', 'age', 30)

# ハッシュの一括設定
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"

パイプライニング

# パイプラインで複数コマンドを効率的に実行
redis.pipelined do |pipe|
  pipe.set('key1', 'value1')
  pipe.set('key2', 'value2')
  pipe.get('key1')
  pipe.get('key2')
  pipe.incr('counter')
end

# Futureオブジェクトの利用
redis.pipelined do |pipe|
  future_value1 = pipe.get('key1')
  future_value2 = pipe.get('key2')
  
  # パイプライン実行後に値を取得
  puts future_value1.value  # => "value1"
  puts future_value2.value  # => "value2"
end

# トランザクション付きパイプライン
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機能

require 'redis'
require 'thread'

# Publisher側
def publisher(redis)
  sleep(1)  # Subscriberの準備を待つ
  5.times do |i|
    redis.publish('notifications', "Message #{i}")
    sleep(1)
  end
end

# Subscriber側
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

# 別スレッドでPublisher実行
redis_pub = Redis.new
redis_sub = Redis.new

pub_thread = Thread.new { publisher(redis_pub) }

# Subscriber実行
subscriber(redis_sub)

pub_thread.join

接続プールの使用

require 'redis'
require 'connection_pool'

# 接続プールの設定
pool = ConnectionPool.new(size: 5, timeout: 5) do
  Redis.new(host: 'localhost', port: 6379)
end

# 接続プールの使用
pool.with do |redis|
  redis.set('pooled_key', 'pooled_value')
  value = redis.get('pooled_key')
  puts value  # => "pooled_value"
end

# マルチスレッド環境での使用
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)

エラーハンドリング

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

# 再接続設定
redis = Redis.new(
  reconnect_attempts: 3,
  reconnect_delay: 0.5,
  reconnect_delay_max: 5.0
)

# 特定のブロックで再接続を無効化
redis.without_reconnect do
  # このブロック内では再接続しない
  redis.ping
end

Sentinel設定

require 'redis'

# Sentinel設定
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
)

# スレーブ接続
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
)

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

分散Redis操作

require 'redis'
require 'redis/distributed'

# 分散Redis設定
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.set('user:1', 'Alice')   # ノード1に配置
distributed.set('user:2', 'Bob')     # ノード2に配置
distributed.set('user:3', 'Charlie') # ノード3に配置

# データ取得
user1 = distributed.get('user:1')
user2 = distributed.get('user:2')
puts "#{user1}, #{user2}"  # => "Alice, Bob"

# すべてのノードの情報取得
distributed.nodes.each_with_index do |node, index|
  puts "Node #{index}: #{node.info['redis_version']}"
end

SSL/TLS接続

require 'redis'

# SSL設定
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
  }
)

# または
redis = Redis.new(url: "rediss://localhost:6380")

# 接続テスト
puts redis.ping  # => PONG

高度なキャッシングパターン

require 'redis'
require 'json'

class CacheManager
  def initialize(redis_client)
    @redis = redis_client
  end
  
  def cached_method(key, expiration = 3600)
    # キャッシュから取得を試行
    cached_value = @redis.get(key)
    return JSON.parse(cached_value) if cached_value
    
    # キャッシュにない場合はブロックを実行
    result = yield
    
    # 結果をキャッシュに保存
    @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

# 使用例
redis = Redis.new
cache = CacheManager.new(redis)

# 重い処理をキャッシュ
result = cache.cached_method('expensive_calc', 1800) do
  # 重い計算処理
  sleep(2)
  { result: (1..100000).sum, timestamp: Time.now }
end

puts result  # 初回は2秒かかる
result = cache.cached_method('expensive_calc') { nil }  # 2回目は即座に返る

# パターンマッチでキャッシュ無効化
cache.invalidate_pattern('user:*')