Redis Cache Configuration
CQL now supports configurable cache stores, allowing you to choose between in-memory caching and Redis-based caching for improved performance and scalability.
Overview
The cache system provides multiple backends:
Memory Cache: Fast in-process caching (default)
Redis Cache: Distributed caching with persistence and advanced features
Quick Start
Environment Variable Configuration
The simplest way to configure Redis caching is through environment variables:
export CQL_CACHE_TYPE=redis
export CQL_REDIS_URL=redis://localhost:6379/0
export CQL_CACHE_PREFIX=myapp
export CQL_CACHE_TTL=3600
require "cql"
# Configure from environment
CQL::Cache::Cache.configure_from_env
# Use the cache
CQL::Cache::Cache.set("user:123", user_data)
cached_user = CQL::Cache::Cache.get("user:123")
Programmatic Configuration
For more control, configure the cache programmatically:
config = CQL::Cache::CacheStoreConfig.new(
type: CQL::Cache::CacheStoreType::Redis,
redis_url: "redis://localhost:6379/1",
key_prefix: "myapp",
default_ttl: 1.hour,
redis_pool_size: 25,
redis_timeout: 5.seconds
)
CQL::Cache::Cache.configure(config)
Configuration Options
General Settings
type
CQL_CACHE_TYPE
String
"memory"
Cache backend: "memory"
or "redis"
key_prefix
CQL_CACHE_PREFIX
String
"cql"
Prefix for all cache keys
default_ttl
CQL_CACHE_TTL
Integer
3600
Default TTL in seconds
Redis-Specific Settings
redis_url
CQL_REDIS_URL
, REDIS_URL
String
"redis://localhost:6379/0"
Redis connection URL
redis_pool_size
CQL_REDIS_POOL_SIZE
Integer
25
Connection pool size
redis_timeout
CQL_REDIS_TIMEOUT
Integer
5
Connection timeout in seconds
Memory Cache Settings
memory_max_size
CQL_MEMORY_CACHE_MAX_SIZE
Integer
nil
Maximum number of entries (unlimited if nil)
Usage Patterns
Basic Cache Operations
# Set a value with default TTL
CQL::Cache::Cache.set("key", value)
# Set a value with custom TTL
CQL::Cache::Cache.set("key", value, 30.minutes)
# Get a value
cached_value = CQL::Cache::Cache.get("key")
# Check if key exists
exists = CQL::Cache::Cache.has_key?("key")
# Delete a key
CQL::Cache::Cache.delete("key")
# Clear all cache
CQL::Cache::Cache.clear
Using CacheStore Factory
Create specific cache instances:
# Create a Redis cache
redis_cache = CQL::Cache::CacheStore.create("redis",
redis_url: "redis://localhost:6379/2",
key_prefix: "specific_app"
)
# Create a memory cache
memory_cache = CQL::Cache::CacheStore.create("memory",
max_size: 1000
)
# Use the cache
redis_cache.set("session:abc", session_data, 15.minutes)
Global Cache Convenience Methods
# Simple get/set
CQL::Cache::GlobalCache.set("config", {theme: "dark"})
config = CQL::Cache::GlobalCache.get("config", Hash(String, String))
# Cache with block execution
result = CQL::Cache::GlobalCache.cache("expensive_operation", 1.hour) do
perform_expensive_calculation()
end
Advanced Redis Features
Batch Operations
# Set multiple values at once
data = {
"user:1" => user1_data,
"user:2" => user2_data,
"user:3" => user3_data
}
redis_cache.set_multi(data, 30.minutes)
# Get multiple values at once
users = redis_cache.get_multi(["user:1", "user:2", "user:3"])
Tag-Based Invalidation
# Set values with tags
redis_cache.set("user:1:profile", profile_data)
redis_cache.set("user:1:settings", settings_data)
# Tag the cache entries
redis_cache.tag_cache("user:1:profile", ["user", "profile"])
redis_cache.tag_cache("user:1:settings", ["user", "settings"])
# Invalidate all entries with a specific tag
redis_cache.invalidate_tags(["user"])
Version-Based Invalidation
# Get current version for a key
version = redis_cache.get_version("data_key")
# Increment version (invalidates related cache entries)
new_version = redis_cache.increment_version("data_key")
Production Configuration
Docker Compose Example
version: "3.8"
services:
app:
build: .
environment:
- CQL_CACHE_TYPE=redis
- CQL_REDIS_URL=redis://redis:6379/0
- CQL_CACHE_PREFIX=myapp_prod
- CQL_REDIS_POOL_SIZE=50
depends_on:
- redis
redis:
image: redis:7-alpine
ports:
- "6379:6379"
volumes:
- redis_data:/data
command: redis-server --appendonly yes
volumes:
redis_data:
Environment Configuration
# Production settings
export CQL_CACHE_TYPE=redis
export CQL_REDIS_URL=redis://redis-cluster:6379/0
export CQL_CACHE_PREFIX=myapp_prod
export CQL_CACHE_TTL=7200
export CQL_REDIS_POOL_SIZE=100
export CQL_REDIS_TIMEOUT=10
High Availability Setup
# Configure with Redis Sentinel or Cluster
config = CQL::Cache::CacheStoreConfig.new(
type: CQL::Cache::CacheStoreType::Redis,
redis_url: "redis://redis-sentinel:26379/mymaster",
redis_pool_size: 100,
redis_timeout: 10.seconds
)
CQL::Cache::Cache.configure(config)
Performance Monitoring
Cache Statistics
# Get detailed statistics
stats = CQL::Cache::Cache.statistics
puts "Cache Type: #{stats["cache_store_type"]}"
puts "Hit Rate: #{stats["hit_rate"]}%"
puts "Total Requests: #{stats["total_requests"]}"
puts "Cache Size: #{stats["cache_size"]} entries"
# Redis-specific stats (if available)
if stats["redis_memory_usage_bytes"]?
puts "Redis Memory: #{stats["redis_memory_usage_bytes"]} bytes"
puts "Connected Clients: #{stats["redis_connected_clients"]}"
end
Performance Summary
# Get formatted performance summary
summary = CQL::Cache::Cache.performance_summary
puts summary
Best Practices
Key Naming Conventions
# Use hierarchical key structure
"app:user:#{user_id}:profile"
"app:session:#{session_id}"
"app:query:#{query_hash}"
# Include version in keys when appropriate
"app:config:v2:feature_flags"
TTL Strategy
# Short TTL for frequently changing data
CQL::Cache::Cache.set("user:online_status", status, 30.seconds)
# Medium TTL for semi-static data
CQL::Cache::Cache.set("user:profile", profile, 15.minutes)
# Long TTL for rarely changing data
CQL::Cache::Cache.set("app:config", config, 24.hours)
Error Handling
begin
cached_data = CQL::Cache::Cache.get("key")
if cached_data.nil?
# Cache miss - fetch from database
data = fetch_from_database()
CQL::Cache::Cache.set("key", data, 1.hour)
data
else
cached_data
end
rescue Redis::Error => e
# Handle Redis errors gracefully
Log.warn { "Cache error: #{e.message}" }
fetch_from_database() # Fallback to database
end
Memory Management
# For memory cache, set reasonable limits
config = CQL::Cache::CacheStoreConfig.new(
type: CQL::Cache::CacheStoreType::Memory,
memory_max_size: 10_000 # Limit to 10k entries
)
# For Redis, monitor memory usage
stats = redis_cache.stats
if stats["redis_memory_usage_bytes"].as(Int64) > 1.gigabyte
Log.warn { "Redis memory usage high: #{stats["redis_memory_usage_bytes"]} bytes" }
end
Migration Guide
From Built-in Cache to Redis
Add Redis dependency (already included):
dependencies: redis: github: stefanwille/crystal-redis version: ~> 2.8
Update configuration:
# Before (built-in cache) CQL::Cache::Cache.enabled = true # After (Redis cache) CQL::Cache::Cache.configure_from_env
Set environment variables:
export CQL_CACHE_TYPE=redis export CQL_REDIS_URL=redis://localhost:6379/0
Test the migration:
# Verify cache is working CQL::Cache::Cache.set("test", "value") result = CQL::Cache::Cache.get("test") puts "Cache working: #{result == "value"}"
Troubleshooting
Common Issues
Redis Connection Errors
# Check Redis connectivity
if redis_cache.is_a?(CQL::Cache::RedisCache)
if redis_cache.ping
puts "Redis connection OK"
else
puts "Redis connection failed"
end
end
High Memory Usage
# Monitor cache size
stats = CQL::Cache::Cache.statistics
if stats["cache_size"].as(Int32) > 100_000
puts "Cache size getting large: #{stats["cache_size"]} entries"
# Consider clearing cache or reducing TTL
end
Performance Issues
# Check hit rates
stats = CQL::Cache::Cache.statistics
hit_rate = stats["hit_rate"].as(Float64)
if hit_rate < 80.0
puts "Low cache hit rate: #{hit_rate}%"
# Consider adjusting TTL or cache keys
end
Debug Mode
# Enable detailed logging
Log.setup(:debug)
# Test cache operations with logging
CQL::Cache::Cache.set("debug_key", "debug_value")
value = CQL::Cache::Cache.get("debug_key")
Examples
See the complete working example in examples/redis_cache_demo.cr
for a comprehensive demonstration of all features.
Last updated
Was this helpful?