redis-store

RubyRailsキャッシュライブラリRedisセッションI18n

GitHub概要

redis-store/redis-store

Namespaced Rack::Session, Rack::Cache, I18n and cache Redis stores for Ruby web frameworks

スター1,499
ウォッチ24
フォーク341
作成日:2009年4月11日
言語:Ruby
ライセンス:MIT License

トピックス

libraryredisruby

スター履歴

redis-store/redis-store Star History
データ取得日時: 2025/10/22 10:04

キャッシュライブラリ

redis-store

概要

redis-storeは、Ruby on Rails、Sinatra、Rack等のモダンなRubyフレームワーク向けに、キャッシュ、セッション、I18n、HTTPキャッシュなど包括的なRedisストアを提供するライブラリですが、Rails 5.2以降では組み込みのRedis cache storeが推奨されています。

詳細

redis-store(レディス・ストア)は、Ruby on Rails、Sinatra、Rack、Rack::Cache、I18nなどのモダンなRubyフレームワーク向けに、フルセットのRedisストア(Cache、I18n、Session、HTTPキャッシュ)を提供するライブラリファミリーです。オブジェクトマーシャリング、タイムアウト、単一または複数ノードサポート、名前空間機能をサポートしています。redis-storeファミリーには、Rails統合のためのredis-rails、セッション管理のためのredis-actionpack、HTTPキャッシュストレージのためのredis-rack-cacheが含まれています。しかし、Rails 5.2.0では組み込みのRedisキャッシュストアが導入されており、単純なフラグメントキャッシュをRedisに保存するだけであれば、もはやこのgemは必要ありません。Rails組み込みのRedisCacheStoreは、vanilla Redis、hiredis、Redis::Distributedなど複数のRedisクライアントをサポートし、フォルトトレラント設計、Redisによるシャーディング、ローカルキャッシュ、バッチ操作(read_multi/write_multi)、圧縮などの高度な機能を提供します。Rails 5.2以降の新しいアプリケーションでは、キャッシュ目的ではredis-storegemの代わりに組み込みのRails Redis cache storeの使用が推奨されています。ただし、redis-activesupportgemのメンテナンスはセキュリティと互換性の問題について継続されていますが、新機能の受け入れは行われていません。

メリット・デメリット

メリット

  • 包括的なストア: キャッシュ、セッション、I18n、HTTPキャッシュの統合サポート
  • フレームワーク統合: Rails、Sinatra、Rackとの深い統合
  • 名前空間サポート: キーの衝突を防ぐ名前空間機能
  • オブジェクトマーシャリング: Rubyオブジェクトの自動シリアライゼーション
  • 複数ノード対応: 単一および複数Redisノードのサポート
  • タイムアウト設定: 柔軟なタイムアウト設定

デメリット

  • Rails 5.2以降非推奨: 組み込みRedis cache storeが標準となっている
  • メンテナンス縮小: 新機能の開発が停止されている
  • 複雑性: 単純なキャッシュには過剰な機能
  • 依存関係: 複数のgemに依存する構造
  • パフォーマンス: 組み込みストアに比べてオーバーヘッドが存在

主要リンク

書き方の例

Rails設定(Legacy - Redis-store使用)

# Gemfile
gem 'redis-rails'

# config/environments/production.rb
Rails.application.configure do
  # Redis-store を使用したキャッシュ設定
  config.cache_store = :redis_store, {
    host: 'localhost',
    port: 6379,
    db: 0,
    namespace: 'my_app_cache',
    expires_in: 90.minutes
  }
  
  # セッションストア設定
  config.session_store :redis_store, {
    servers: ["redis://localhost:6379/1"],
    expire_after: 90.minutes,
    key: '_my_app_session',
    threadsafe: false
  }
end

# config/initializers/redis_store.rb
Rails.application.config.session_store :redis_store, {
  servers: %w[redis://localhost:6379/1],
  expire_after: 90.minutes,
  key: '_my_app_session'
}

Rails 5.2+ 推奨設定(組み込みRedis Cache Store)

# Gemfile
gem 'redis', '~> 5.0'

# config/environments/production.rb
Rails.application.configure do
  # 基本設定
  config.cache_store = :redis_cache_store, { url: "redis://localhost:6379/0" }
  
  # 高度な設定
  config.cache_store = :redis_cache_store, {
    url: "redis://localhost:6379/0",
    namespace: "my_app_cache",
    compress: true,
    compress_threshold: 1.kilobyte,
    pool_size: 20,
    pool_timeout: 5,
    reconnect_attempts: 2,
    connect_timeout: 30,
    read_timeout: 0.2,
    write_timeout: 0.2,
    error_handler: -> (method:, returning:, exception:) {
      Rails.logger.warn("Redis cache error: #{exception.message}")
    }
  }
end

# パスワード付きRedis設定
config.cache_store = :redis_cache_store, {
  url: 'redis://localhost:6379/0',
  password: 'your_redis_password'
}

Redis-store セッション管理

# config/initializers/session_store.rb
Rails.application.config.session_store :redis_store, {
  servers: [
    {
      host: "localhost",
      port: 6379,
      db: 1,
      password: "mysecret"
    },
    {
      host: "localhost",
      port: 6380,
      db: 1,
      password: "mysecret"
    }
  ],
  expire_after: 90.minutes,
  namespace: "session",
  key: "_my_app_session"
}

# セッション操作例
class ApplicationController < ActionController::Base
  def store_user_preference
    session[:theme] = params[:theme]
    session[:language] = params[:language]
    redirect_to root_path, notice: '設定を保存しました'
  end
  
  def current_theme
    session[:theme] || 'default'
  end
end

Rack アプリケーションでの使用

# config.ru
require 'rack'
require 'redis-store'
require 'redis-rack-cache'

# キャッシュミドルウェア設定
use Rack::Cache,
  metastore: "redis://localhost:6379/0",
  entitystore: "redis://localhost:6379/1"

# セッションミドルウェア設定  
use Rack::Session::Redis,
  redis_server: "redis://localhost:6379/2",
  expire_after: 3600

# アプリケーション
app = lambda do |env|
  request = Rack::Request.new(env)
  
  # セッションの使用
  session = request.session
  session[:visit_count] = (session[:visit_count] || 0) + 1
  
  [200, {}, ["訪問回数: #{session[:visit_count]}"]]
end

run app

I18n バックエンドとしての使用

# config/initializers/i18n_redis.rb
require 'redis-store'

# Redis を I18n バックエンドとして設定
I18n.backend = I18n::Backend::Chain.new(
  I18n::Backend::Redis.new("redis://localhost:6379/3"),
  I18n.backend
)

# カスタム翻訳の動的追加
class TranslationManager
  def self.add_translation(locale, key, value)
    I18n.backend.backends.first.store_translations(locale, { key => value })
  end
  
  def self.update_translations(translations_hash)
    translations_hash.each do |locale, translations|
      I18n.backend.backends.first.store_translations(locale, translations)
    end
  end
end

# 使用例
TranslationManager.add_translation(:ja, 'custom.greeting', 'こんにちは!')
TranslationManager.add_translation(:en, 'custom.greeting', 'Hello!')

puts I18n.t('custom.greeting', locale: :ja)  # => "こんにちは!"

Sinatra アプリケーションでの使用

# sinatra_app.rb
require 'sinatra'
require 'redis-store'

# セッション設定
use Rack::Session::Redis, 
    redis_server: "redis://localhost:6379/0",
    expire_after: 3600

# キャッシュ設定
configure do
  set :cache, Redis::Store.new("redis://localhost:6379/1")
end

# ルート定義
get '/cached_data/:id' do
  cache_key = "data:#{params[:id]}"
  
  # キャッシュから取得を試行
  cached_data = settings.cache.get(cache_key)
  return cached_data if cached_data
  
  # データベースから取得(重い処理)
  data = fetch_data_from_database(params[:id])
  
  # キャッシュに保存(1時間)
  settings.cache.setex(cache_key, 3600, data)
  
  data
end

get '/session_demo' do
  session[:counter] = (session[:counter] || 0) + 1
  "Session counter: #{session[:counter]}"
end

def fetch_data_from_database(id)
  # 重いデータベース処理をシミュレート
  sleep(2)
  "Data for ID: #{id} - Generated at #{Time.now}"
end

名前空間とマルチテナント対応

class MultiTenantCache
  def initialize(tenant_id)
    @tenant_id = tenant_id
    @redis = Redis::Store.new(
      url: "redis://localhost:6379/0",
      namespace: "tenant:#{tenant_id}"
    )
  end
  
  def set(key, value, expiration = 3600)
    @redis.setex(key, expiration, serialize(value))
  end
  
  def get(key)
    data = @redis.get(key)
    data ? deserialize(data) : nil
  end
  
  def delete(key)
    @redis.del(key)
  end
  
  def exists?(key)
    @redis.exists(key)
  end
  
  def flush_tenant_cache
    pattern = "tenant:#{@tenant_id}:*"
    keys = @redis.keys(pattern)
    @redis.del(*keys) if keys.any?
  end
  
  private
  
  def serialize(value)
    Marshal.dump(value)
  end
  
  def deserialize(data)
    Marshal.load(data)
  end
end

# 使用例
tenant1_cache = MultiTenantCache.new('tenant_1')
tenant2_cache = MultiTenantCache.new('tenant_2')

tenant1_cache.set('user_data', { name: 'Alice', email: '[email protected]' })
tenant2_cache.set('user_data', { name: 'Bob', email: '[email protected]' })

puts tenant1_cache.get('user_data')  # => {:name=>"Alice", :email=>"alice@tenant1.com"}
puts tenant2_cache.get('user_data')  # => {:name=>"Bob", :email=>"bob@tenant2.com"}

カスタムシリアライザー

class JsonRedisStore < Redis::Store
  def set(key, value, options = {})
    serialized_value = JSON.dump(value)
    if options[:expires_in]
      setex(key, options[:expires_in], serialized_value)
    else
      super(key, serialized_value)
    end
  end
  
  def get(key)
    value = super(key)
    value ? JSON.parse(value) : nil
  end
  
  def mget(*keys)
    values = super(*keys)
    values.map { |v| v ? JSON.parse(v) : nil }
  end
end

# 使用例
redis_store = JsonRedisStore.new("redis://localhost:6379/0")

# ハッシュの保存と取得
user_data = {
  id: 123,
  name: "Alice",
  preferences: {
    theme: "dark",
    language: "ja"
  },
  created_at: Time.now.iso8601
}

redis_store.set("user:123", user_data, expires_in: 3600)
retrieved_data = redis_store.get("user:123")

puts retrieved_data['name']  # => "Alice"
puts retrieved_data['preferences']['theme']  # => "dark"

パフォーマンス監視

class MonitoredRedisStore
  def initialize(redis_config)
    @redis = Redis::Store.new(redis_config)
    @stats = {
      hits: 0,
      misses: 0,
      writes: 0,
      deletes: 0
    }
  end
  
  def get(key)
    value = @redis.get(key)
    if value
      @stats[:hits] += 1
    else
      @stats[:misses] += 1
    end
    value
  end
  
  def set(key, value, expiration = nil)
    @stats[:writes] += 1
    if expiration
      @redis.setex(key, expiration, value)
    else
      @redis.set(key, value)
    end
  end
  
  def delete(key)
    @stats[:deletes] += 1
    @redis.del(key)
  end
  
  def hit_rate
    total = @stats[:hits] + @stats[:misses]
    return 0 if total.zero?
    (@stats[:hits].to_f / total * 100).round(2)
  end
  
  def stats
    @stats.merge(hit_rate: "#{hit_rate}%")
  end
  
  def reset_stats
    @stats = @stats.keys.zip([0] * @stats.size).to_h
  end
end

# 使用例
monitored_cache = MonitoredRedisStore.new("redis://localhost:6379/0")

# キャッシュ操作
monitored_cache.set("key1", "value1")
monitored_cache.get("key1")  # hit
monitored_cache.get("key2")  # miss
monitored_cache.get("key1")  # hit

puts monitored_cache.stats
# => {:hits=>2, :misses=>1, :writes=>1, :deletes=>0, :hit_rate=>"66.67%"}