redis-store
GitHub概要
redis-store/redis-store
Namespaced Rack::Session, Rack::Cache, I18n and cache Redis stores for Ruby web frameworks
トピックス
スター履歴
キャッシュライブラリ
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に依存する構造
- パフォーマンス: 組み込みストアに比べてオーバーヘッドが存在
主要リンク
- redis-store GitHub リポジトリ
- redis-rails GitHub リポジトリ
- Rails Caching Guide
- RedisCacheStore Rails API
- Redis 公式ドキュメント
書き方の例
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%"}