π Source:redis-caching-pattern.md β view on GitHub & star β
β‘ Redis Caching Pattern for Speed
Speed up your OpenClaw automations 20x with Redis caching β from 1 second to 50ms! π
π Cache Flow Architecture
B -->|Cache HIT β
| C[β‘ Return Cached Data<br/>~5ms]
B -->|Cache MISS β| D[π Fetch from External API<br/>~800ms]
style C fill:#c8e6c9,stroke:#2e7d32,stroke-width:2px
style D fill:#ffcdd2,stroke:#c62828,stroke-width:2px
D --> E[πΎ Store in Redis<br/>with TTL]
style E fill:#e1bee7,stroke:#6a1b9a,stroke-width:2px
E --> F[π€ Return Fresh Data]
C --> G[β
Client Response]
F --> G
style F fill:#b3e5fc,stroke:#0277bd,stroke-width:2px
style G fill:#a5d6a7,stroke:#388e3c,stroke-width:2px'}
π Cache Lifecycle Sequence
C->>A: Request Data
A->>R: GET cache:key
alt Cache Exists
R-->>A: Return cached value
A-->>C: Response (50ms) β‘
else Cache Expired/Missing
R-->>A: nil (miss)
A->>API: HTTP GET
API-->>A: JSON Response
A->>R: SETEX key TTL value
R-->>A: OK
A-->>C: Response (1s) π’
end'}
π― Before vs After
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β SPEED COMPARISON β
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ€
β β
β WITHOUT CACHE β WITH CACHE β
β
β β
β ββββββββββββββββ ββββββββββββββββ β
β β Request β β Request β β
β ββββββββ¬ββββββββ ββββββββ¬ββββββββ β
β β β β
β βΌ βΌ β
β ββββββββββββββββ ββββββββββββββββ β
β β API Call β 800ms β Check Redis β 5ms β
β β (External) β ββββββββ¬ββββββββ β
β β β β
β βΌ βΌ β
β ββββββββββββββββ ββββββββββββββββ β
β β Parse Data β 200ms β Cache Hit! β 50ms β
β ββββββββ¬ββββββββ ββββββββ¬ββββββββ β
β β β β
β βΌ βΌ β
β ββββββββββββββββ ββββββββββββββββ β
β β Total: 1s β β Total: 50ms β β
β β π« Slow β β π 20x Fasterβ β
β ββββββββββββββββ ββββββββββββββββ β
β β
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
Real Numbers
| Operation | Without Cache | With Cache | Speedup |
|---|---|---|---|
| Gold Price API | 1,200ms | 45ms | 27x π |
| Weather API | 800ms | 12ms | 67x π |
| Health Check | 500ms | 8ms | 62x π |
| User Session | 300ms | 5ms | 60x π |
π― What You'll Build
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β REDIS CACHING ARCHITECTURE β
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ€
β β
β YOUR SCRIPT REDIS SERVER β
β ββββββββββββββββ ββββββββββββββββ β
β β β β β β
β β 1. Check βββββββββββββββΆβ Key exists? β β
β β Cache β β β β
β β ββββββββββββββββ YES β Return β β
β ββββββββ¬ββββββββ β value β β
β β β β β
β β NO ββββββββββββββββ β
β βΌ β² β
β ββββββββββββββββ β β
β β 2. Fetch fromβ β β
β β External β β β
β β API β β β
β ββββββββ¬ββββββββ β β
β β β β
β βΌ β β
β ββββββββββββββββ β β
β β 3. Store in βββββββββββββββββββββββ β
β β Cache β (with TTL) β
β β β β
β ββββββββββββββββ β
β β
β TTL = Time To Live (auto-expire) β
β β
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
π οΈ Installation
Install Redis
# Ubuntu/Debian
sudo apt-get update
sudo apt-get install redis-server
# macOS
brew install redis
# Start Redis
sudo systemctl start redis # Linux
brew services start redis # macOS
# Verify
redis-cli ping
# Should return: PONG
Install Redis Client (Bash)
# redis-cli included with server install
# For scripts, use redis-cli directly
# Test connection
redis-cli set test "hello"
redis-cli get test
# Returns: hello
redis-cli del test
π Step 1: Create Helper Functions
Save this as ~/scripts/redis-utils.sh:
#!/bin/bash
# =============================================================================
# β‘ Redis Helper Functions for OpenClaw
# =============================================================================
# Default Redis connection
REDIS_HOST="${REDIS_HOST:-localhost}"
REDIS_PORT="${REDIS_PORT:-6379}"
# =============================================================================
# π§ CORE FUNCTIONS
# =============================================================================
# Set a key with optional TTL (Time To Live in seconds)
redis_set() {
local key="$1"
local value="$2"
local ttl="${3:-}"
if [ -n "$ttl" ]; then
redis-cli -h "$REDIS_HOST" -p "$REDIS_PORT" setex "$key" "$ttl" "$value" >/dev/null
else
redis-cli -h "$REDIS_HOST" -p "$REDIS_PORT" set "$key" "$value" >/dev/null
fi
}
# Get a key value
redis_get() {
local key="$1"
redis-cli -h "$REDIS_HOST" -p "$REDIS_PORT" get "$key"
}
# Delete a key
redis_delete() {
local key="$1"
redis-cli -h "$REDIS_HOST" -p "$REDIS_PORT" del "$key" >/dev/null
}
# Check if key exists (returns 1 if exists, 0 if not)
redis_exists() {
local key="$1"
redis-cli -h "$REDIS_HOST" -p "$REDIS_PORT" exists "$key"
}
# Get TTL of a key (returns seconds remaining, -1 if no TTL, -2 if not exists)
redis_ttl() {
local key="$1"
redis-cli -h "$REDIS_HOST" -p "$REDIS_PORT" ttl "$key"
}
# List keys matching pattern (default: all)
redis_keys() {
local pattern="${1:-*}"
redis-cli -h "$REDIS_HOST" -p "$REDIS_PORT" keys "$pattern"
}
# =============================================================================
# π― CONVENIENCE FUNCTIONS
# =============================================================================
# Cache JSON data with TTL
redis_cache_json() {
local key="$1"
local json_data="$2"
local ttl="${3:-300}" # Default 5 minutes
# Compress JSON to single line
local compressed
compressed=$(echo "$json_data" | jq -c . 2>/dev/null || echo "$json_data")
redis_set "$key" "$compressed" "$ttl"
}
# Get and parse cached JSON
redis_get_json() {
local key="$1"
local value
value=$(redis_get "$key")
if [ -n "$value" ] && [ "$value" != "nil" ]; then
echo "$value" | jq . 2>/dev/null || echo "$value"
else
echo "null"
fi
}
# Cache with automatic expiration for different data types
redis_cache_weather() {
local location="$1"
local data="$2"
# Cache weather for 30 minutes
redis_cache_json "weather:$location" "$data" 1800
}
redis_cache_price() {
local item="$1"
local data="$2"
# Cache prices for 5 minutes
redis_cache_json "price:$item" "$data" 300
}
redis_cache_health() {
local service="$1"
local data="$2"
# Cache health for 1 minute
redis_cache_json "health:$service" "$data" 60
}
redis_cache_session() {
local session_id="$1"
local data="$2"
# Cache sessions for 1 hour
redis_cache_json "session:$session_id" "$data" 3600
}
# =============================================================================
# π MONITORING FUNCTIONS
# =============================================================================
# Show cache statistics
redis_stats() {
echo "π Redis Statistics"
echo "=================="
# Memory usage
echo -n "Memory Used: "
redis-cli -h "$REDIS_HOST" -p "$REDIS_PORT" info memory | grep "used_memory_human" | cut -d: -f2
# Number of keys
echo -n "Total Keys: "
redis-cli -h "$REDIS_HOST" -p "$REDIS_PORT" dbsize
# Connected clients
echo -n "Connected Clients: "
redis-cli -h "$REDIS_HOST" -p "$REDIS_PORT" info clients | grep "connected_clients" | cut -d: -f2
}
# Clear all cache (use with caution!)
redis_flush() {
echo "β οΈ This will delete ALL cached data!"
read -p "Type 'yes' to confirm: " confirm
if [ "$confirm" = "yes" ]; then
redis-cli -h "$REDIS_HOST" -p "$REDIS_PORT" flushdb
echo "β
Cache cleared"
else
echo "β Cancelled"
fi
}
# Show keys by prefix
redis_list_by_prefix() {
local prefix="$1"
echo "π Keys with prefix '$prefix':"
redis_keys "${prefix}*" | while read -r key; do
local ttl
ttl=$(redis_ttl "$key")
printf " %-40s (TTL: %s)\n" "$key" "$ttl"
done
}
Make it executable:
chmod +x ~/scripts/redis-utils.sh
π Step 2: Use Cases with Code Examples
Use Case 1: Gold/Price Caching
#!/bin/bash
source ~/scripts/redis-utils.sh
fetch_gold_price() {
local cache_key="price:gold:xauusd"
# 1. Check cache first
local cached
cached=$(redis_get_json "$cache_key")
if [ "$cached" != "null" ]; then
echo "π° Cache HIT! Gold price (cached):"
echo "$cached" | jq -r '.price'
return 0
fi
echo "π Cache MISS β Fetching from API..."
# 2. Fetch from external API
local api_response
api_response=$(curl -s "https://api.goldapi.io/v1/XAU/USD" \
-H "x-access-token: YOUR_API_KEY")
# 3. Parse and format
local price
price=$(echo "$api_response" | jq -r '.price')
local formatted_data
formatted_data=$(jq -n \
--arg price "$price" \
--arg time "$(date -Iseconds)" \
'{price: $price, timestamp: $time, source: "goldapi"}')
# 4. Store in cache (5 minutes)
redis_cache_price "gold:xauusd" "$formatted_data"
echo "π° Gold price (fresh): $price"
echo "β
Cached for 5 minutes"
}
# Run
fetch_gold_price
Use Case 2: Weather Caching
#!/bin/bash
source ~/scripts/redis-utils.sh
fetch_weather() {
local city="${1:-Jakarta}"
local cache_key="weather:$city"
# Check cache
local cached
cached=$(redis_get_json "$cache_key")
if [ "$cached" != "null" ]; then
echo "π€οΈ Weather for $city (cached):"
echo "$cached" | jq -r '.condition, .temperature'
return 0
fi
echo "π Fetching weather for $city..."
# API call (example)
local weather_data
weather_data=$(curl -s "https://api.weather.com/v1/current?city=$city" \
-H "Authorization: Bearer YOUR_KEY")
# Cache for 30 minutes
redis_cache_weather "$city" "$weather_data"
echo "π€οΈ Weather for $city:"
echo "$weather_data" | jq -r '.condition, .temperature'
}
fetch_weather "Singapore"
Use Case 3: Health Status Caching
#!/bin/bash
source ~/scripts/redis-utils.sh
check_service_health() {
local service="$1"
local url="$2"
local cache_key="health:$service"
# Check cache first (1 minute TTL)
local cached
cached=$(redis_get_json "$cache_key")
if [ "$cached" != "null" ]; then
local status
status=$(echo "$cached" | jq -r '.status')
echo "[$service] $status (cached)"
return 0
fi
# Check service
local start_time end_time duration
start_time=$(date +%s%N)
if curl -s --max-time 5 "$url" >/dev/null 2>&1; then
end_time=$(date +%s%N)
duration=$(( (end_time - start_time) / 1000000 ))
local result
result=$(jq -n \
--arg status "UP" \
--argjson response_time "$duration" \
--arg checked_at "$(date -Iseconds)" \
'{status: $status, response_time: $response_time, checked_at: $checked_at}')
redis_cache_health "$service" "$result"
echo "[$service] UP (${duration}ms)"
else
local result
result=$(jq -n \
--arg status "DOWN" \
--arg checked_at "$(date -Iseconds)" \
'{status: $status, checked_at: $checked_at}')
redis_cache_health "$service" "$result"
echo "[$service] DOWN"
fi
}
# Check multiple services
echo "π₯ Health Check (with caching):"
check_service_health "api" "https://api.example.com/health"
check_service_health "database" "https://db.example.com/ping"
check_service_health "website" "https://example.com"
Use Case 4: Session Caching
#!/bin/bash
source ~/scripts/redis-utils.sh
# Store user session
save_session() {
local session_id="$1"
local user_data="$2"
redis_cache_session "$session_id" "$user_data"
echo "β
Session saved (1 hour)"
}
# Retrieve user session
get_session() {
local session_id="$1"
local session_data
session_data=$(redis_get_json "session:$session_id")
if [ "$session_data" != "null" ]; then
echo "$session_data"
else
echo "{}"
fi
}
# Example usage
user_session='{"user_id": "123", "name": "Alex", "preferences": {"theme": "dark"}}'
save_session "sess_abc123" "$user_session"
retrieved=$(get_session "sess_abc123")
echo "User: $(echo "$retrieved" | jq -r '.name')"
π Step 3: Complete Working Example
Save this as ~/scripts/cached-api-call.sh:
#!/bin/bash
source ~/scripts/redis-utils.sh
# =============================================================================
# β‘ Generic Cached API Caller
# =============================================================================
cached_api_call() {
local cache_key="$1"
local api_url="$2"
local cache_seconds="${3:-300}" # Default 5 minutes
local api_headers="${4:-}"
echo "π Checking cache for: $cache_key"
# Try cache first
local cached_data
cached_data=$(redis_get_json "$cache_key")
if [ "$cached_data" != "null" ]; then
local cache_age
cache_age=$(redis_ttl "$cache_key")
echo "β
Cache HIT! (expires in ${cache_age}s)"
echo "$cached_data"
return 0
fi
echo "π Cache miss β calling API..."
# Make API call
local response
if [ -n "$api_headers" ]; then
response=$(curl -s -H "$api_headers" "$api_url")
else
response=$(curl -s "$api_url")
fi
# Validate response (simple JSON check)
if ! echo "$response" | jq -e . >/dev/null 2>&1; then
echo "β Invalid API response" >&2
return 1
fi
# Cache the response
redis_cache_json "$cache_key" "$response" "$cache_seconds"
echo "β
Cached for ${cache_seconds} seconds"
echo "$response"
}
# Example usage
echo "Fetching data with caching..."
result=$(cached_api_call "users:list" "https://jsonplaceholder.typicode.com/users" 600)
echo "$result" | jq '.[0].name'
π§ TTL (Time To Live) Guidelines
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β RECOMMENDED CACHE DURATIONS β
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ€
β β
β Data Type TTL Reason β
β βββββββββββββββββββββββββββββββββββββββββββββββββββββββββ β
β π° Stock/Prices 5 min Changes frequently β
β π€οΈ Weather 30 min Updates ~hourly β
β π₯ Health Status 1 min Need fresh status β
β π€ User Sessions 1 hour Security + usability β
β π API Rate Limits 1 hour Static configuration β
β πΊοΈ Locations 24 hours Rarely change β
β β
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
π Performance Testing
Compare cached vs non-cached:
#!/bin/bash
source ~/scripts/redis-utils.sh
API_URL="https://api.example.com/data"
CACHE_KEY="perf:test"
echo "π Performance Test: Cached vs Non-Cached"
echo "=========================================="
# Test 1: Non-cached
echo -e "\nβ Without Cache:"
for i in 1 2 3; do
redis_delete "$CACHE_KEY" # Clear cache
start=$(date +%s%N)
curl -s "$API_URL" > /dev/null
end=$(date +%s%N)
duration=$(( (end - start) / 1000000 ))
echo " Request $i: ${duration}ms"
done
# Test 2: Cached
echo -e "\nβ
With Cache:"
# Pre-populate cache
cached_api_call "$CACHE_KEY" "$API_URL" 300 >/dev/null
for i in 1 2 3; do
start=$(date +%s%N)
redis_get "$CACHE_KEY" > /dev/null
end=$(date +%s%N)
duration=$(( (end - start) / 1000000 ))
echo " Request $i: ${duration}ms"
done
π Best Practices
1. Cache Key Naming
# Good: Hierarchical, descriptive
cache_key="weather:singapore:daily"
cache_key="user:123:profile"
cache_key="api:github:rate_limit"
# Bad: Vague, collision-prone
cache_key="data"
cache_key="temp"
2. Error Handling
fetch_with_cache() {
local key="$1"
local url="$2"
# Try cache first
local cached
cached=$(redis_get_json "$key")
if [ "$cached" != "null" ]; then
echo "$cached"
return 0
fi
# Fetch with error handling
local response
response=$(curl -s --max-time 10 "$url")
if [ $? -ne 0 ] || [ -z "$response" ]; then
# Return stale cache if available (optional)
echo "β οΈ API failed, no cache" >&2
return 1
fi
# Cache successful response
redis_cache_json "$key" "$response" 300
echo "$response"
}
3. Cache Warming
# Pre-populate cache before peak hours
warm_cache() {
echo "π₯ Warming cache..."
# Pre-fetch common data
cached_api_call "config:main" "$API_BASE/config" 3600 >/dev/null
cached_api_call "users:top" "$API_BASE/users/top" 300 >/dev/null
cached_api_call "prices:all" "$API_BASE/prices" 300 >/dev/null
echo "β
Cache warmed"
}
# Run on cron at 8 AM
0 8 * * * ~/scripts/warm-cache.sh
β Verification Checklist
- Redis installed and running (
redis-cli pingreturns PONG) - Helper functions saved and executable
- API calls include error handling
- Appropriate TTL selected for each data type
- Cache keys follow naming convention
- Performance tested (cached vs non-cached)
- Memory usage monitored (
redis_stats)
π Troubleshooting
Redis not running
# Check status
sudo systemctl status redis
# Start Redis
sudo systemctl start redis
# Auto-start on boot
sudo systemctl enable redis
Connection refused
# Check Redis is listening
netstat -tlnp | grep 6379
# Check firewall
sudo ufw allow 6379 # If needed locally
Memory issues
# Check memory usage
redis-cli info memory
# Set max memory in redis.conf
maxmemory 256mb
maxmemory-policy allkeys-lru # Evict least recently used
π Related Tutorials
Questions? Join the OpenClaw Discord β‘