Replacing CouchDB with Redis Cache for Heavy Rails Queries

This article explains how a failing CouchDB‑based Rails cache for a massive SQL report was redesigned using Redis, detailing configuration, a producer‑consumer pattern, service encapsulation, and scheduled rake tasks to achieve stable, fast page loads.

21CTO
21CTO
21CTO
Replacing CouchDB with Redis Cache for Heavy Rails Queries

When a complex report backed by a several‑hundred‑line SQL query caused severe performance problems on a high‑traffic page, the team initially cached the result with Rails.cache using CouchDB as the backend.

# config/environments/production.rb
couch_host = YAML.load_file(Rails.root.join("config/couch.yml")).symbolize_keys[:host]
config.cache_store = :mem_cache_store, couch_host , { :namespace => 'rails_cache' }
# config/couch.yml
host: xxx.xxx.xxx.xxx:xxx

Because the project already used Redis and the old Rails version (3.0.9) prevented the Redis gems from working, the developers decided to replace CouchDB with Redis.

# Gemfile
gem 'rails', '3.0.9'
#gem 'redis-rails', '3.1.3'
#gem "redis-store", "~> 1.0.0"

The new design follows three steps:

Use Redis instead of CouchDB, leveraging existing familiarity and stability.

Front‑end requests read only from Redis; if the cache is empty they receive an empty array, protecting the database from heavy load.

Set the cache expiration to 999 days (effectively never) and run a daily rake task to refresh the cache.

This pattern forms a classic producer‑consumer model: a rake task (producer) generates the query result each day, while front‑end requests (consumers) read the cached data.

Redis configuration:

# config/redis_store.yml
cache:
  host: xxx.xxx.xxx.xxx
  port: xxx
  db: 2
  driver: hiredis
  thread_safe: true
  timeout: 200
# config/initializers/redis.rb
$redis = Redis.new(YAML.load_file("#{Rails.root}/config/redis_store.yml").symbolize_keys[:cache])

A global $redis variable is wrapped in a service to simplify testing and future extensions.

class ReportCacheService
  def initialize(data = {})
    @key = data[:key]
    @expire_in = data[:expire_in]
    @refresh_cache = data[:refresh_cache]
  end

  def call(&block)
    res = @refresh_cache == true ? nil : read()
    if res.nil?
      res = block.call
      write(res)
    end
    res
  end

  def read
    value = $redis.get(@key)
    JSON.parse(value) if value.present?
  end

  def write(res)
    value = res.to_json
    $redis.set(@key, value)
    $redis.expire(@key, @expire_in)
    true
  end
end

The report model uses this service to warm the cache:

# app/models/stm_report.rb
class StmReport
  NON_EXPIRED = 999.days.seconds

  def self.warm_cache(params = {})
    cs = ReportCacheService.new(
      key: build_cache_key(params),
      refresh_cache: true,
      expire_in: NON_EXPIRED
    )
    cs.call do
      # heavy work
      ...
    end
  end
end

A daily rake task triggers the warm‑up:

# lib/tasks/cron.rake
task :warn_stm_report_cache => :environment do
  puts "#{DateTime.now}: start cron warm cache"
  StmReport.warm_cache(qualified: true)
  StmReport.warm_cache(qualified: true, per_page: 50)
  puts "#{DateTime.now}: end cron warm cache"
end

Consumers read the cached data, falling back to an empty array when the cache is missing:

# app/models/stm_report.rb (continued)
def self.create(params = {})
  cs = ReportCacheService.new(key: build_cache_key(params))
  items = cs.read
  items = [] if items.nil?
  # other work
  ...
end

After redeploying with Redis, the report became stable and fast, confirming the success of the redesign.

Original Source

Signed-in readers can open the original source through BestHub's protected redirect.

Sign in to view source
Republication Notice

This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactadmin@besthub.devand we will review it promptly.

RubyRailsRake
21CTO
Written by

21CTO

21CTO (21CTO.com) offers developers community, training, and services, making it your go‑to learning and service platform.

0 followers
Reader feedback

How this landed with the community

Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.