Redis Caching Setup
Overview
The Pricing Engine uses Redis for caching pricing rules lookups. This provides significant performance improvements for high-traffic API endpoints by reducing database queries.
Configuration
Environment Variables
| Variable | Description | Default |
|---|---|---|
REDIS_URL | Redis connection URL | redis://localhost:6379/0 |
Cache Settings
| Setting | Production | Development |
|---|---|---|
| Namespace | pricing_engine:production | pricing_engine:development |
| Connect Timeout | 1 second | 1 second |
| Read Timeout | 200ms | 200ms |
| Write Timeout | 200ms | 200ms |
| Reconnect Attempts | 2 | 1 |
| Compression | Enabled | Enabled |
Docker Setup
Redis is already configured in docker-compose.yml:
redis:
image: redis:7-alpine
ports:
- "6379:6379"
volumes:
- redis_data:/data
The Rails app is configured with:
REDIS_URL: "redis://redis:6379/0"
Cached Data
Repository Methods
The following PricingRuleRepository methods are cached:
| Method | Cache Key Pattern | TTL |
|---|---|---|
active_rules_for(restaurant_id) | pricing_rules:v1:restaurant:{id}:active_rules | 5 min |
rules_for_date(restaurant_id, date) | pricing_rules:v1:restaurant:{id}:rules_for_date:{date} | 5 min |
rules_for_package(restaurant_id, package_id) | pricing_rules:v1:restaurant:{id}:rules_for_package:{pkg} | 5 min |
Caching Strategy
-
Cache IDs Only: We cache only record IDs, not full ActiveRecord objects, to avoid serialization issues and memory bloat.
-
Reload with Eager Loading: After fetching cached IDs, we reload records from the database with eager-loaded associations.
-
Restaurant-Scoped Keys: All cache keys are scoped by restaurant_id for targeted invalidation.
-
Graceful Degradation: If Redis is unavailable, the app falls back to direct database queries with logging.
Cache Invalidation
Cache is automatically invalidated when:
- A
PricingRuleis created, updated, or destroyed - A
DateTimeConditionis created, updated, or destroyed - A
ConditionItemis created, updated, or destroyed - A
PricingRulesPackageis created, updated, or destroyed
All invalidation uses after_commit callbacks to ensure database changes are complete before cache is cleared.
Manual Invalidation
# Invalidate cache for a specific restaurant
PricingRuleRepository.invalidate_restaurant_cache("restaurant_123")
# Invalidate all pricing rule caches
PricingRuleRepository.invalidate_all_caches
Bypass Cache
To skip caching for a specific query:
PricingRuleRepository.active_rules_for("restaurant_123", skip_cache: true)
PricingRuleRepository.rules_for_date("restaurant_123", Date.current, skip_cache: true)
PricingRuleRepository.rules_for_package("restaurant_123", "package_456", skip_cache: true)
Development
Enable Caching in Development
By default, caching is disabled in development. To enable:
rails dev:cache
This creates tmp/caching-dev.txt and enables Redis caching.
Disable Caching in Development
rails dev:cache
Running the command again removes the file and disables caching.
View Cache Stats (in rails console)
# Check if Redis is connected
Rails.cache.redis.ping
# Get Redis info
Rails.cache.redis.info
# Clear all cache
Rails.cache.clear
Testing
Tests use :null_store for the cache, which doesn’t persist data. This ensures tests are isolated and don’t depend on cache state.
To test caching behavior specifically:
# In spec
allow(Rails.cache).to receive(:fetch).and_call_original
# or
Rails.cache.clear
Performance Considerations
-
TTL Balance: 5-minute TTL balances freshness with cache hit rate. Adjust based on update frequency.
-
Memory Usage: Redis memory is bounded by eviction policies. Monitor with
redis-cli info memory. -
Connection Pool: Production uses connection pooling (default pool size: 5).
-
Timeouts: Short timeouts (200ms) prevent slow Redis from degrading app performance.
Monitoring
Key Metrics to Monitor
- Cache hit rate (
Rails.cache.statsif using redis-store) - Redis memory usage
- Redis connection count
- Cache invalidation frequency
Logging
Cache errors are logged with the [Redis Cache Error] prefix:
[Redis Cache Error] method=read returning=nil error=Redis::CannotConnectError: Connection refused
Cache invalidation errors use [Cache Invalidation Error]:
[Cache Invalidation Error] restaurant_id=rest_123 error=Redis::CommandError: NOSCRIPT