以前の記事で、RailsでキャッシュストアとセッションストアをRedisにしてみました。
Rails7.0系で、キャッシュストアとセッションストアをRedisにしてみた - メモ的な思考的な
その時はスタンドアロンRedisだったため、Redisクラスターの場合はどうなるのか気になったことから、試してみたときのメモを残します。
目次
環境
- mac
- RubyMine 2025.3.2
- Redisクラスター
- docker composeで利用
- Redis 8.4のノードを3つ用意
- Ruby 3.4.8
- Rails 8.1.2
- redis 5.4.1
- redis-clustering 5.4.1
なお、Redisクラスターは前回の記事の内容そのままです。事前に起動しておきます。
Rails8.1系で、Redisクラスターをセッションストアとして設定してみた - メモ的な思考的な
Railsアプリの環境構築
Railsアプリも前回記事とほぼ同じですので、差分だけ記載します。
今回、コントローラでキャッシュストアとセッションストアに読み書きすることで、動作確認できるようにしました。
キャッシュストアを読み書きするには、 Rails.cache.read や Rails.cache.writeを使います。
5.2 ActiveSupport::Cache::Store | Rails のキャッシュ機構 - Railsガイド
その際、キャッシュやセッションの有効期限を確認しやすくするため、各ストアに入れる値はタイムスタンプとします。
class HellosController < ApplicationController skip_before_action :verify_authenticity_token, raise: false def index session_timestamp = session[:session_timestamp] cache_timestamp = Rails.cache.read("cache_timestamp") puts session_timestamp puts cache_timestamp render json: { current_timestamp: Time.current.strftime("%Y/%m/%d %H:%M:%S"), session_timestamp:, cache_timestamp: } end def create now = Time.current.strftime("%Y/%m/%d %H:%M:%S") session[:session_timestamp] = "session -> #{now}" Rails.cache.write("cache_timestamp", "cache -> #{now}") head :ok end end
キャッシュストアをRedisクラスターにする
キャッシュストアを設定する場合、config.cache_store を変更します。
3.2.14 config.cache_store | Rails アプリケーションの設定項目 - Railsガイド
開発環境である config/environments/development.rb では :memory_store が設定されていました。
今回はRedisクラスターを使うため、 :redis_cache_store へと変更します。
5.6 ActiveSupport::Cache::RedisCacheStore | Rails のキャッシュ機構 - Railsガイド
それに加え、Redisクラスターの設定も追加します。今回は動作確認しやすいよう、 expires_in は1分としました。
Rails.application.configure do # ... config.cache_store = :redis_cache_store, { redis: Redis::Cluster.new(nodes: %w[ redis://redis-cluster-node-1:6379 redis://redis-cluster-node-2:6379 redis://redis-cluster-node-3:6379 ]), namespace: "app_cache", expires_in: 1.minutes, } # ...
準備ができたので、Railsを起動した後、 curl で動作確認します。以下、見やすくするために抜粋・整形しています。
まずはPOSTでデータをキャッシュストア・セッションストアにデータを保存します。
% curl -X POST http://localhost:3000/hellos -c cookies.txt
続いて、GETで各ストアからデータを取り出します。この時点では両ストアともデータがあります。
% curl -b cookies.txt http://localhost:3000/hellos
{
"current_timestamp":"2026/01/31 12:20:48",
"session_timestamp":"session -> 2026/01/31 12:20:45",
"cache_timestamp":"cache -> 2026/01/31 12:20:45"
}
この時、Redisクラスターの状況を確認します。 namespaceの app_cache + キャッシュへ入れたときのキー cache_timestamp に紐づくデータが保存されています。
1分以上経過した後、再度GETします。すると、キャッシュストアのみデータが消えていました。
% curl -b cookies.txt http://localhost:3000/hellos
{
"current_timestamp":"2026/01/31 12:22:01",
"session_timestamp":"session -> 2026/01/31 12:20:45",
"cache_timestamp":null
}
キャッシュストアとセッションストアをRedisクラスターにする
続いて、セッションストアをデフォルトのCookieからRedisクラスターへと変更します。
3.2.58 config.session_store | Rails アプリケーションの設定項目 - Railsガイド
Redisクラスターのセッションストアでは次の2パターンがありそうだったため、それぞれ試してみます。
:cache_storeのみ指定して、キャッシュストアと同居する:cache_storeに加えnamespaceも設定し、キャッシュストアと分ける
:cache_storeのみ指定する
セッションストアとキャッシュストアを同居させるパターンです。
実運用では発生しづらいと思いますが、どのような挙動になるか気になったため試してみます。
config/initializers/session_store.rbを作成し、次の設定を追加します。
Rails.application.config.session_store :cache_store
準備ができたので、Railsを再起動して、 curl で動作確認します。
すると、キャッシュストアの有効期限を経過すると、キャッシュストアとセッションストアが同時にクリアされました。
% curl -X POST http://localhost:3000/hellos -c cookies.txt
# キャッシュストアの有効期限内
% curl -b cookies.txt http://localhost:3000/hellos
{
"current_timestamp":"2026/01/31 12:23:52",
"session_timestamp":"session -> 2026/01/31 12:23:49",
"cache_timestamp":"cache -> 2026/01/31 12:23:49"
}
# キャッシュストアの有効期限を経過後
% curl -b cookies.txt http://localhost:3000/hellos
{
"current_timestamp":"2026/01/31 12:25:25",
"session_timestamp":null,
"cache_timestamp":null
}
ちなみに、キャッシュストアの有効期限内のRedisクラスターの状況です。同じnamespaceの app_cache で、キーだけ異なってデータが保存されていました。
キャッシュとして登録されたデータ
セッションとして登録されたデータ(セッションIDがキーに含まれる)
:cache_storeに加え、namespaceも指定する
続いて、 :cache_store + namespace を指定する設定です。
キャッシュストアとセッションストアの有効期限を別とするため、 expire_after に90分を設定します。これにより、セッションストアの expire_after 設定の影響を受けず、キャッシュストアは expires_in 設定に従うことを確認できます。
なお、この設定の詳細は前回の記事を参照してください。
# namespaceを付与するため、cache引数をあらためて定義する Rails.application.config.session_store :cache_store, cache: ActiveSupport::Cache::RedisCacheStore.new( redis: Redis::Cluster.new(nodes: %w[ redis://redis-cluster-node-1:6379 redis://redis-cluster-node-2:6379 redis://redis-cluster-node-3:6379 ]), namespace: "session_redis5" ), expire_after: 90.minutes, key: "_redis_cluster_example_session"
準備ができたので、Railsを再起動して、 curl で動作確認します。
すると、キャッシュストアの有効期限を経過すると、セッションストアのみデータが保存されていることがわかりました。良さそうです。
% curl -X POST http://localhost:3000/hellos -c cookies.txt
# キャッシュストア、セッションストアとも有効期限内
% curl -b cookies.txt http://localhost:3000/hellos
{
"current_timestamp":"2026/01/31 13:21:14",
"session_timestamp":"session -> 2026/01/31 13:21:09",
"cache_timestamp":"cache -> 2026/01/31 13:21:09"
}
# キャッシュストアが有効期限を経過
% curl -b cookies.txt http://localhost:3000/hellos
{
"current_timestamp":"2026/01/31 13:23:02",
"session_timestamp":"session -> 2026/01/31 13:21:09",
"cache_timestamp":null
}
ちなみに、キャッシュストア・セッションストアの有効期限内のRedisクラスターの状況です。別namespaceでデータが保存されていました。
キャッシュとして登録されたデータ
セッションとして登録されたデータ(namespaceが session_redis5)
備考
RedisクラスターのDB番号と、redis + redis-clusteringでの挙動について
Redisの公式ドキュメントに
Redis Cluster does not support multiple databases like the standalone version of Redis. We only support database 0; the SELECT command is not allowed.
とあるように、Redisクラスターでは、スタンドアロンRedisのような redis://redis-cluster-node-1:6379/1 というDB番号1の指定ができない仕様です。
では、gem redis + redis-clustering のセッションストアの場合、どのような挙動になるか試してみます。
DB番号0を指定した場合、セッションへ書き込める
session_store.rb の設定で、末尾に /0 を付与してみます。
Rails.application.config.session_store :cache_store, cache: ActiveSupport::Cache::RedisCacheStore.new( redis: Redis::Cluster.new(nodes: %w[ redis://redis-cluster-node-1:6379/0 redis://redis-cluster-node-2:6379/0 redis://redis-cluster-node-3:6379/0 ]), namespace: "session_redis5" ), # ...
curlでアクセスしてRedisの状態を確認したところ、セッションとして値が保存されていました。DB番号 0 のため、仕様通りそうです。
DB番号1を指定した場合、セッションへ書き込めないが、正常終了する
続いて、session_store.rb の設定で、末尾に /1 を付与してみます。
Rails.application.config.session_store :cache_store, cache: ActiveSupport::Cache::RedisCacheStore.new( redis: Redis::Cluster.new(nodes: %w[ redis://redis-cluster-node-1:6379/1 redis://redis-cluster-node-2:6379/1 redis://redis-cluster-node-3:6379/1 ]), namespace: "session_redis5" ), # ...
同じくcurlでアクセスして確認したところ、エラーにならず正常終了しました。
Redisクラスターを確認すると、キャッシュとしての値はあるものの、セッションとしての値が保存されていません。
これより、gem redis + redis-clustering でRedisクラスターを使う場合、DB番号 0 以外は使えないと分かりました。
ただ、ここまで見てきた通り、DB番号 0 を指定しなくてもRedisクラスターへの接続はできることから、わざわざDB番号を付与する必要はなさそうと感じました。
Redis::Cluster.newについて
session_store.rb の設定では、 ActiveSupport::Cache::RedisCacheStore.new の redis キーワードに Redis::Cluster.new の結果を渡していました。
Redis::Cluster.newはどのgemで定義され、どのような値が返るのか気になったことから、調べてみました。
まず、Redis::Clusterは redis-clustering gemで定義され、 redis gemの Redis クラスを継承しています。
https://github.com/redis/redis-rb/blob/v5.4.1/cluster/lib/redis/cluster.rb#L6
また、 new で呼ばれる initialize では super して親クラスを呼んでいます。
https://github.com/redis/redis-rb/blob/v5.4.1/cluster/lib/redis/cluster.rb#L67-L70
require "redis" class Redis class Cluster < ::Redis # @return [Redis::Cluster] a new client instance def initialize(*) super end # ...
その親クラス Redisでは、 initialize の中で initialize_client が呼ばれます。
https://github.com/redis/redis-rb/blob/v5.4.1/lib/redis.rb#L75
class Redis # @return [Redis] a new client instance def initialize(options = {}) # ... @client = initialize_client(@options) # ... end
Redis::Cluster では initialize_client が呼ばれます。そこでは、 redis-cluster-client の RedisClient#clusterが呼ばれます。
https://github.com/redis/redis-rb/blob/v5.4.1/cluster/lib/redis/cluster.rb#L130-L132
def initialize_client(options) cluster_config = RedisClient.cluster(**options, protocol: 2, client_implementation: ::Redis::Cluster::Client) cluster_config.new_client end
RedisClient#clusterでは、 ClusterConfig を生成します。
https://github.com/redis-rb/redis-cluster-client/blob/master/lib/redis_cluster_client.rb#L8
class RedisClient class << self def cluster(**kwargs) ClusterConfig.new(**kwargs) end end end
ClusterConfigでは RedisClient.cluster(**options, ...) で渡された options から nodes が渡され、最終的には client_config インスタンス変数へと設定されます。
また、 #clusterで渡された ::Redis::Cluster::Client は client_implementation インスタンス変数へと設定されます。
https://github.com/redis-rb/redis-cluster-client/blob/4387eabfd4b0dc96e6c7ebada2d7cb4f52fef0e0/lib/redis_client/cluster_config.rb#L11-L65
class RedisClient class ClusterConfig def initialize( # rubocop:disable Metrics/ParameterLists nodes: DEFAULT_NODES, # ... client_implementation: ::RedisClient::Cluster, # for redis gem # ... ) # ... node_configs = build_node_configs(nodes.dup) @client_config = merge_generic_config(client_config, node_configs) @startup_nodes = build_startup_nodes(node_configs) @client_implementation = client_implementation end # ...
これより、 Redis::Cluster.new(nodes: ...) で渡したRedisクラスターの情報がRedisクライアントへ引き渡され、 Redis::Cluster のインスタンスが生成されると分かりました。
ソースコード
GitHubに上げました。
https://github.com/thinkAmi-sandbox/rails_session_and_cache_with_redis_cluster-example
今回のプルリクはこちら。 cache_storeとsession_storeにRedisクラスターが設定されている状態になっています。
https://github.com/thinkAmi-sandbox/rails_session_and_cache_with_redis_cluster-example/pull/1






