Using Redis Hashes for Caching in Ruby on Rails

This article discusses improving Redis caching performance in Ruby on Rails by replacing #delete_matched with Redis Hashes.

The Problem

We found that Rails’s #delete_matched function became slow at scale and didn’t work properly across Redis clusters, only operating on single nodes.

The Solution

We implemented Redis Hashes as a faster alternative. Deleting a hash is much faster and there is no risk of leaving out undeleted data across multiple nodes.

Technical Implementation

Here’s a RedisHashStore module with basic hash operations:

module RedisHashStore
  class Entry
    attr_accessor :value, :expires_at

    def initialize(value, expires_at: nil)
      @value = value
      @expires_at = expires_at
    end

    def expired?
      expires_at && Time.current > expires_at
    end

    def dump
      Marshal.dump(self)
    end

    def self.load(data)
      return nil if data.nil?
      Marshal.load(data)
    end
  end

  def write_hash(hash_name, key, value, expires_in: nil)
    expires_at = expires_in ? Time.current + expires_in : nil
    entry = Entry.new(value, expires_at: expires_at)
    redis.hset(hash_name, key, entry.dump)
  end

  def read_hash(hash_name, key)
    data = redis.hget(hash_name, key)
    entry = Entry.load(data)
    return nil if entry.nil? || entry.expired?
    entry.value
  end

  def delete_hash(hash_name)
    redis.del(hash_name)
  end

  def delete_hash_key(hash_name, key)
    redis.hdel(hash_name, key)
  end

  private

  def redis
    Redis.current
  end
end

The key insight is that since Redis Hashes don’t natively support TTL on individual fields, we manage expiration ourselves using the Entry class with Marshal.dump and Marshal.load for serialization.

Benchmark Results

Testing with 1 million entries showed dramatic improvements:

MethodTime
delete_matched3.79 seconds
delete_hash0.68 seconds

That’s approximately a 5.5x improvement in deletion speed.

Ruby Gem

We created a Ruby gem implementing these patterns that you can use in your projects:

redis_hash_store on GitHub

Conclusion

If you’re using delete_matched in a Redis cluster environment and experiencing performance issues, consider migrating to Redis Hashes. The performance gains are significant, and you avoid the pitfalls of pattern matching across cluster nodes.


Co-authored by Alex Golubenko. Originally published on engineering.mrsool.co