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.
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
endThis 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
endA 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"
endConsumer 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
endThe 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.
Signed-in readers can open the original source through BestHub's protected redirect.
This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactand we will review it promptly.
ITPUB
Official ITPUB account sharing technical insights, community news, and exciting events.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.
