0
0

Ruby on Railsでコネクションプールを導入したredis-rbの導入方法

Posted at

始めに

Ruby on Rails では、Redis を利用して一時的に値を保持するケースが多々あると思います。(たとえば、カート情報の管理やカウント処理など)
もし Redis の String 型のバリューのみで要件を満たせるのであれば、Railsのキャッシュ機構(低レベルキャッシュ)を利用するだけで、Redis を簡単に扱うことができます。

Rails の cache_store:redis_cache_store が設定されている前提で、以下のように Redis を利用できます。

# 取得
Rails.cache.read(key)

# 保存
Rails.cache.write(key, value)

# 削除
Rails.cache.delete(key)

しかし、もしより複雑なデータ型(たとえば、List 型、Set 型、Sorted Set 型など)を Redis で扱いたい場合、利用したいRedisコマンドの種類が増えるため、Rails のキャッシュ機構だけでは対応が難しくなります。

そんなときに役立つのが、redis-rbgemです。

The client does not provide connection pooling. Each Redis instance has one and only one connection to the server, and use of this connection is protected by a mutex.
As such it is heavilly recommended to use the connection_pool gem, e.g.

ただ、上記のGithubのreadmeにもあるように、このgemコネクションプールを提供していません。
そのため、スレッドセーフな実装のためにconnection_poolGemの使用が強く推奨されています。

この記事では、コネクションプール導入したredis-rbgemの実装方法について解説します!

ゴール

この記事の最終目標は、以下のように、RedisConnectionPool.client というクラスを作成し、簡単に Redis コマンドを実行できるようにすることです。

# Sorted Set型に追加
RedisConnectionPool.client.zadd(key, score, member)

# 有効期限を設定
RedisConnectionPool.client.expire(key, ttl)

上記の使用例になります。
RedisConnectionPool.client.{Redisコマンドのメソッド}というように、Redisコマンドのメソッドを呼ぶだけで、コネクションプールを意識せずにRedisコマンドを自由に使用できます。

実装方法

まず、redis-rb gem をインストールします。

$ gem install redis

ちなみに、redis-rb gem は connection_pool gem に依存しているため、redis-rb のインストール時に connection_pool も自動的にインストールされる想定です。

次に、RedisConnectionPool クラスを作成します(今回はlib/redis_connection_pool.rb を作成)。

class RedisConnectionPool
  class << self
    def client
      @client ||= ConnectionPool::Wrapper.new(size:) { Redis.new(url:, password:, timeout: 5) }
    end

    private

    def size
      ENV.fetch('RAILS_MAX_THREADS', 5)
    end

    def url
      ENV.fetch('REDIS_URL', 'redis://localhost:6379/1')
    end

    def password
      ENV.fetch('REDIS_PASSWORD', nil)
    end
  end
end

このクラスは Redis への接続を管理し、接続プールを使用して複数のスレッド間で接続を共有します。
client メソッドは、Redis クライアントをラップした ConnectionPool::Wrapper オブジェクトを返し、スレッドセーフに Redis を操作できるようにします。

実装は以上です。

(補足)パフォーマンスの改善

It's not high-performance so you'll want to port your performance sensitive code to use with as soon as possible.

connection_poolGemのreadmeに記載があるように、ConnectionPool::Wrapper は高パフォーマンスを求める場面では最適ではありません。
よりパフォーマンスを求める場合は、with メソッドを利用して、ConnectionPool を直接使用する方法が推奨されています。

RedisConnectionPool.with do |client|
  # Sorted Set型に追加
  client.zadd(key, score, member)
  # 有効期限を設定
  client.expire(key, ttl)
end

withメソッドを使用するようにRedisConnectionPool.clientを改良した場合の使用イメージは上記のとおりです。

複数の Redis コマンドをまとめて実行する場合、with メソッドを使うことで、1つの接続で効率的に処理できます。

なぜ ConnectionPool::Wrapper を使用するのか

この記事では、あえて ConnectionPool::Wrapper を使用しました。その理由は以下の通りです。

  • 既存の Redis 利用方法からの変更が少なく、運用が容易
  • with メソッドを使うと、毎回ブロックを使用する必要があり、コードの可読性が低下する可能性がある
  • 複数の Redis コマンドを一度に使用するケースが少なく今のところ発生しなさそう

withメソッド使用のためのRedisConnectionPoolクラス改良方法

とはいえ、チームによっては with メソッドの使用が適している場合もあるため、以下に with メソッドを使用する場合の RedisConnectionPool クラスも示しておきます。

class RedisConnectionPool
  class << self
    delegate :with, to: :pool

    private

    def pool
      @pool ||= ConnectionPool.new(size:) { Redis.new(url:, password:, timeout: 5) }
    end

    def size
      ENV.fetch('RAILS_MAX_THREADS', 5)
    end

    def url
      ENV.fetch('REDIS_URL', 'redis://localhost:6379/1')
    end

    def password
      ENV.fetch('REDIS_PASSWORD', nil)
    end
  end
end

まとめ

Redis の利便性を最大限に活用するためには、redis-rbconnection_pool を適切に組み合わせることが重要です。
特に、複雑なデータ型やスレッドセーフな実装が必要な場合、接続プールを導入することでパフォーマンスと安定性を確保できます。

その上、ConnectionPool::Wrapper を利用すれば、既存のコードベースに対する影響を最小限に抑えながら、手軽に接続プールを導入できます。
パフォーマンスを重視する場合は、with メソッドを使用して、より効率的な処理が可能です。

この記事を参考に、ぜひあなたのプロジェクトで Redis をより効率的に活用してください!

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0