How to Replace CouchDB with Redis for High‑Traffic Report Caching in Rails

This article explains why a legacy Rails 3 application switched from CouchDB to Redis for caching a heavy daily‑updated report, detailing the design, configuration files, service class, producer and consumer code, and the resulting performance improvements.

ITPUB
ITPUB
ITPUB
How to Replace CouchDB with Redis for High‑Traffic Report Caching in Rails

A complex report generated by a multi‑hundred‑line SQL query was causing severe performance issues on a high‑traffic page. Because the report only needed to be refreshed once a day, the team decided to cache the query results.

Initial Setup and Problems

The original cache used Rails.cache with a CouchDB backend, configured in config/environments/production.rb and config/couch.yml. Although Redis was already part of the system, the old Rails version (3.0.9) prevented the use of redis‑rails and redis‑store, which were commented out in the Gemfile. Upgrading Rails was deemed too risky, so the team kept the CouchDB cache until the CouchDB server crashed, causing a site‑wide 502 error.

Redesign with Redis

The new design replaces CouchDB with Redis, leveraging existing familiarity and the stability of Redis‑based asynchronous queues. Front‑end requests read only from Redis; if the cache is empty, an empty array is returned to avoid hitting the database. The cache expiration is set to 999 days (effectively never expiring), and a daily Rake task refreshes the cache.

Producer‑Consumer Model

The solution follows a classic producer‑consumer pattern: a Rake task (producer) generates the report data each night and stores it in Redis, while front‑end requests (consumers) read the pre‑computed data from Redis.

Redis Configuration

Because integrating Redis into Rails.cache with Rails 3 would be cumbersome, the team uses a direct Redis client.

# 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 provides easy access to Redis throughout the application.

ReportCacheService

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

  def call(&block)
    res = @refresh_cache ? 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)
    $redis.set(@key, res.to_json)
    $redis.expire(@key, @expire_in)
    true
  end
end

This service abstracts cache reads, writes, and optional forced refreshes.

Producer Code (Report Generation)

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 computation here
      ...
    end
  end
end

A nightly Rake task invokes this method:

# 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

Consumer Code (Report Rendering)

class StmReport
  NON_EXPIRED = 999.days.seconds

  def self.create(params = {})
    cs = ReportCacheService.new(key: build_cache_key(params))
    items = cs.read || []
    # further processing
    ...
  end
end

The front‑end now reads cached data from Redis, eliminating expensive database queries and keeping the site responsive even under heavy load.

Result

After deploying the Redis‑based cache, the report page became stable and fast, confirming the effectiveness of the redesign.

Cache design diagram
Cache design diagram
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.

RedisRubyRails
ITPUB
Written by

ITPUB

Official ITPUB account sharing technical insights, community news, and exciting events.

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.