Redis Caching Setup for Mobile App Backend
Mobile client makes a request for news feed. Backend hits PostgreSQL, executes JOIN on three tables, applies filters, sorts — 200–400 ms. User refreshes feed — again 200–400 ms, though data hasn't changed. Redis caches the result, and repeat request takes 1–5 ms. The difference is physically noticeable.
What to Cache, What Not To
Good Redis candidates:
- Results of heavy aggregating queries (top users, statistics, catalog)
- Data requested frequently but changes rarely (app configuration, settings, dictionaries)
- Results of external API calls (geocoding, exchange rates)
- Sessions and authentication tokens
Bad candidates:
- Specific user data that changes with every action
- Transactional data (account balance, order status) — risk of showing stale state
- Data with short TTL and high invalidation computation cost
Caching Architecture
Cache-Aside (Lazy Loading)
Most common pattern. App first checks Redis, on miss goes to DB and writes result to cache.
def get_news_feed(user_id: int, page: int):
cache_key = f"feed:{user_id}:page:{page}"
cached = redis.get(cache_key)
if cached:
return json.loads(cached)
data = db.query_feed(user_id, page)
redis.setex(cache_key, 300, json.dumps(data)) # TTL 5 minutes
return data
Downside: first request after TTL expiry or cold start is always slow. On high-load APIs this can cause thundering herd — hundreds of requests simultaneously miss cache and hit DB. Mitigation: mutex lock (via SET NX) or probabilistic early expiration.
Write-Through
On data write — immediately update both DB and cache. Cache always fresh. More expensive on writes, simpler on reads. Good for user profiles, settings.
Read-Through
Cache itself knows how to load data on miss. Implemented via Redis modules or libraries like RedisOM. Overkill for most mobile backends.
Key Strategy and TTL
Key structure: {service}:{entity}:{id}:{variant}. Example: app:feed:user:42:page:1, app:config:global:v2.
TTL chosen by change frequency:
- App configuration: 1–24 hours
- Product catalog: 5–30 minutes
- News feed: 1–5 minutes
- User profile: 5–15 minutes
Without TTL cache grows until memory is full. Redis on memory full with allkeys-lru policy starts evicting keys — can lose important data unexpectedly. Explicit TTL is more reliable.
Invalidation
Most complex part. Patterns:
By event: on profile update delete app:profile:user:42:* via SCAN + DEL. Don't use KEYS * in production — blocking O(N) operation.
Tagged cache: add tags to each key in separate set (SADD tag:user:42 "app:feed:user:42:page:1"). On invalidation — get all tag keys and delete. Cache-Tags libraries automate this.
Versioning: instead of invalidation — change version in key (app:config:global:v3). Old key just expires by TTL.
Production Redis Setup
Minimum requirements for mobile backend:
-
maxmemoryexplicitly limited (e.g., 512 MB) -
maxmemory-policy allkeys-lru— evict least used keys on limit hit - Persistence: can disable for cache (
save "") — cache rebuilds on restart - Sentinel or Redis Cluster for HA in production
Monitoring: redis-cli INFO stats → keyspace_hits vs keyspace_misses. Hit rate below 80% — something wrong with caching strategy. used_memory and evicted_keys — watch constantly.
Timeline: basic Cache-Aside setup for main endpoints — two to three business days. Full strategy with invalidation and monitoring — four to seven days.







