redis-store
GitHub Overview
redis-store/redis-store
Namespaced Rack::Session, Rack::Cache, I18n and cache Redis stores for Ruby web frameworks
Topics
Star History
Cache Library
redis-store
Overview
redis-store is a library family that provides comprehensive Redis stores (Cache, I18n, Session, HTTP Cache) for modern Ruby frameworks like Ruby on Rails, Sinatra, and Rack, though Rails 5.2+ built-in Redis cache store is now recommended for caching purposes.
Details
redis-store is a library family that provides a full set of Redis stores (Cache, I18n, Session, HTTP Cache) for modern Ruby frameworks like Ruby on Rails, Sinatra, Rack, Rack::Cache, and I18n. It supports object marshalling, timeouts, single or multiple node support, and namespace functionality. The redis-store family includes redis-rails for Rails integration, redis-actionpack for session management, and redis-rack-cache for HTTP cache storage. However, Rails 5.2.0 introduced a built-in Redis cache store, making this gem unnecessary if you only need to store fragment cache in Redis. Rails' built-in RedisCacheStore supports multiple Redis clients including vanilla Redis, hiredis, and Redis::Distributed, offering advanced features like fault tolerance, Redis-based sharding, local cache, batch operations (read_multi/write_multi), and compression. For new Rails applications (5.2+), it's recommended to use the built-in Rails Redis cache store instead of redis-store gems for caching purposes. However, maintenance on the redis-activesupport gem continues for security and compatibility issues, but new features are no longer being accepted.
Pros and Cons
Pros
- Comprehensive Stores: Integrated support for cache, session, I18n, and HTTP cache
- Framework Integration: Deep integration with Rails, Sinatra, and Rack
- Namespace Support: Namespace functionality to prevent key collisions
- Object Marshalling: Automatic serialization of Ruby objects
- Multi-Node Support: Support for both single and multiple Redis nodes
- Timeout Configuration: Flexible timeout settings
Cons
- Deprecated for Rails 5.2+: Built-in Redis cache store is now standard
- Reduced Maintenance: New feature development has stopped
- Complexity: Excessive features for simple caching needs
- Dependencies: Structure dependent on multiple gems
- Performance: Overhead compared to built-in stores
Key Links
- redis-store GitHub Repository
- redis-rails GitHub Repository
- Rails Caching Guide
- RedisCacheStore Rails API
- Redis Official Documentation
Code Examples
Rails Configuration (Legacy - Using Redis-store)
# Gemfile
gem 'redis-rails'
# config/environments/production.rb
Rails.application.configure do
# Cache configuration using Redis-store
config.cache_store = :redis_store, {
host: 'localhost',
port: 6379,
db: 0,
namespace: 'my_app_cache',
expires_in: 90.minutes
}
# Session store configuration
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+ Recommended Configuration (Built-in Redis Cache Store)
# Gemfile
gem 'redis', '~> 5.0'
# config/environments/production.rb
Rails.application.configure do
# Basic configuration
config.cache_store = :redis_cache_store, { url: "redis://localhost:6379/0" }
# Advanced configuration
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 configuration with password
config.cache_store = :redis_cache_store, {
url: 'redis://localhost:6379/0',
password: 'your_redis_password'
}
Redis-store Session Management
# 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"
}
# Session usage example
class ApplicationController < ActionController::Base
def store_user_preference
session[:theme] = params[:theme]
session[:language] = params[:language]
redirect_to root_path, notice: 'Settings saved'
end
def current_theme
session[:theme] || 'default'
end
end
Usage in Rack Applications
# config.ru
require 'rack'
require 'redis-store'
require 'redis-rack-cache'
# Cache middleware configuration
use Rack::Cache,
metastore: "redis://localhost:6379/0",
entitystore: "redis://localhost:6379/1"
# Session middleware configuration
use Rack::Session::Redis,
redis_server: "redis://localhost:6379/2",
expire_after: 3600
# Application
app = lambda do |env|
request = Rack::Request.new(env)
# Using sessions
session = request.session
session[:visit_count] = (session[:visit_count] || 0) + 1
[200, {}, ["Visit count: #{session[:visit_count]}"]]
end
run app
Using as I18n Backend
# config/initializers/i18n_redis.rb
require 'redis-store'
# Configure Redis as I18n backend
I18n.backend = I18n::Backend::Chain.new(
I18n::Backend::Redis.new("redis://localhost:6379/3"),
I18n.backend
)
# Dynamic addition of custom translations
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
# Usage example
TranslationManager.add_translation(:ja, 'custom.greeting', 'こんにちは!')
TranslationManager.add_translation(:en, 'custom.greeting', 'Hello!')
puts I18n.t('custom.greeting', locale: :ja) # => "こんにちは!"
Usage in Sinatra Applications
# sinatra_app.rb
require 'sinatra'
require 'redis-store'
# Session configuration
use Rack::Session::Redis,
redis_server: "redis://localhost:6379/0",
expire_after: 3600
# Cache configuration
configure do
set :cache, Redis::Store.new("redis://localhost:6379/1")
end
# Route definitions
get '/cached_data/:id' do
cache_key = "data:#{params[:id]}"
# Try to get from cache
cached_data = settings.cache.get(cache_key)
return cached_data if cached_data
# Fetch from database (expensive operation)
data = fetch_data_from_database(params[:id])
# Save to cache (1 hour)
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)
# Simulate heavy database processing
sleep(2)
"Data for ID: #{id} - Generated at #{Time.now}"
end
Namespace and Multi-Tenant Support
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
# Usage example
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"}
Custom Serializer
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
# Usage example
redis_store = JsonRedisStore.new("redis://localhost:6379/0")
# Store and retrieve hash
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"
Performance Monitoring
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
# Usage example
monitored_cache = MonitoredRedisStore.new("redis://localhost:6379/0")
# Cache operations
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%"}