Caching

The Immersive application uses Redis for caching in production, and Memcached in development.

Migration note: production previously used Memcached Cloud (a Heroku add-on). The switch to Redis is part of the move off Heroku to Kamal/Hetzner — see issue #288. It reuses the same Redis instance Sidekiq already depends on (see docs/technical/production/kamal.md), removing a separate caching service entirely. Development still uses local Memcached for now — that’s an independent, un-migrated convenience setup, not tied to the Heroku move.

Overview

Production caches frequently accessed data (fragment caches, verb pages, etc.) in Redis to reduce database load and improve response times.

Configuration

Production

Configured in config/environments/production.rb, sharing the Redis accessory used by Sidekiq (see config/initializers/sidekiq.rb) but on a separate logical database index so cache keys and Sidekiq’s queue data never collide:

  • DB 0 — Sidekiq queues
  • DB 1 — Rails cache store
config.cache_store = :redis_cache_store, {
  url: "redis://#{ENV.fetch("REDIS_URL_HOST")}:#{ENV.fetch("REDIS_URL_PORT")}/1",
  namespace: Rails.env,
  expires_in: 1.day
}

REDIS_URL_HOST/REDIS_URL_PORT point at the redis Kamal accessory (immersive-redis:6379 — see config/deploy.yml).

Development

Development uses a local Memcached instance running on localhost:11211. Ensure Memcached is installed and running:

# Install with Homebrew (macOS)
brew install memcached

# Start the service
brew services start memcached

The cache store is configured in config/environments/development.rb:

config.cache_store = :mem_cache_store, "localhost:11211", {
  namespace: Rails.env,
  expires_in: 1.day,
  compress: true
}

Test

The test environment uses :null_store to avoid caching during tests. This ensures:

  • Test isolation: No cache state persists between tests
  • No external dependencies: Tests don’t require a running Memcached instance
  • Predictable behavior: No caching side effects that could affect test results

If you need to test caching behavior specifically, you can temporarily use :memory_store in your test setup, but :null_store is the recommended default for most test scenarios.

Dependencies

Production uses the redis gem (already a dependency for Sidekiq). Development still uses the dalli gem for local Memcached connectivity.

Cache Features

  • Namespace: Each environment uses its own namespace (Rails.env) to prevent cache collisions
  • Expiration: Default cache expiration is set to 1 day
  • LRU Eviction: Redis’ maxmemory-policy (unset here, defaults to no eviction) governs behavior if the instance runs out of memory — not a practical concern at current traffic and data volumes, but worth revisiting if usage grows

Usage

Rails provides several methods for caching:

# Cache a value
Rails.cache.write("key", "value")
Rails.cache.write("key", "value", expires_in: 1.hour)

# Read a cached value
Rails.cache.read("key")

# Check if a key exists
Rails.cache.exist?("key")

# Delete a cached value
Rails.cache.delete("key")

# Clear the entire cache
Rails.cache.clear

# Fetch with block (caches if not present)
Rails.cache.fetch("key") do
  expensive_operation
end

Fragment Caching

Fragment caching is enabled in production. Use it in views:

<% cache @resource do %>
  <%= render @resource %>
<% end %>

Troubleshooting

Development: Connection Refused

If you see connection errors, ensure Memcached is running:

brew services start memcached

Production: Cache Not Working

  1. Confirm the redis accessory is running: bin/kamal accessory logs redis
  2. Confirm REDIS_URL_HOST/REDIS_URL_PORT are set for the app containers: bin/kamal app exec --reuse "env | grep REDIS"
  3. Test from the Rails console:
    bin/kamal app exec --reuse "bin/rails console"
    # Then in console:
    Rails.cache.write("test", "value")
    Rails.cache.read("test")
    

History

  • Originally used Solid Cache (database-backed)
  • Switched to Memcached Cloud when the app moved to Heroku
  • Switched to Redis (:redis_cache_store) in production as part of the Heroku → Kamal/Hetzner migration (issue #288); development kept Memcached since it isn’t part of that migration

References


This site uses Just the Docs, a documentation theme for Jekyll.