Java Fundamentals Tutorial 0/33 lessons ~6 min read Lesson 27

    Redis

    Redis is an in-memory data store used by Java services for sub-millisecond caching, session storage, rate limiting, and distributed locks.

    Course progress0%
    Focus
    21 guided sections
    Practice signal
    Examples included
    Career prep
    Interview Q&A included

    Introduction

    Redis is an in-memory data store used by Java services for sub-millisecond caching, session storage, rate limiting, and distributed locks. The cache-aside pattern — read cache first, on miss load DB and populate — is the default integration with Spring Cache + Lettuce/Jedis.

    This lesson covers TTL strategy, distributed cache consistency across pods, and eviction policies (LRU, LFU, volatile-lru) when memory fills. Redis is not your source of truth — it's an acceleration layer with explicit expiration and invalidation contracts.

    Business problem

    Database overload without caching:

    • Hot keys: Product detail page — 10K RPS hits Postgres for same SKU — DB CPU 100%.
    • Session state: Sticky sessions tie users to pods — scale-out impossible without shared session store.
    • Rate limiting: Payment API abused — need distributed counter across 20 gateway pods.
    • Latency SLO: p99 catalog read 200ms from Postgres — Redis cache hits < 2ms.
    • Thundering herd: Cache expiry — 1000 threads simultaneously query DB for same key.

    Why this topic exists

    Redis exists because RAM latency (microseconds) beats disk/network DB round-trips (milliseconds) by 100×:

    • Cache-aside: Application manages cache — read Redis, miss → DB → SET with TTL.
    • TTL: Time-to-live — automatic expiry prevents stale data forever; tune per data freshness requirement.
    • Distributed cache: All JVM pods share one Redis cluster — consistent view of sessions and counters.
    • Eviction: When maxmemory reached — Redis evicts keys per policy; wrong policy drops hot data.
    • Not a DB: Persistence optional (RDB/AOF) — design for cache loss on restart.

    Core concepts

    Redis caching concepts for Java:

    • Cache-aside (lazy loading): get(key) → miss → load from DB → set(key, value, TTL).
    • Write-through: Write DB and cache synchronously — consistent but slower writes.
    • Write-behind: Write cache first, async flush to DB — fast but data loss risk.
    • TTL: SET key value EX 300 — expire in 300 seconds; jitter TTL to prevent simultaneous expiry.
    • Eviction policies: allkeys-lru, volatile-lru, allkeys-lfu — choose based on TTL usage.
    • Spring Cache: @Cacheable, @CacheEvict with Redis backend — declarative cache-aside.

    Internal architecture

    Cache-aside with Java Spring Boot:

    text
    Request ──▶ Spring Controller
    @Cacheable("products")
    ProductService.getById(sku)
    ┌──────────┴──────────┐
    ▼ ▼
    Redis GET product:SKU123 Cache MISS
    │ │
    HIT │ ▼
    │ Postgres SELECT
    │ │
    │ ▼
    │ Redis SET EX 600
    ▼ │
    Return Product ◀───────────┘
    Redis Cluster (distributed):
    Master 1 (slots 0-5460) ── replica
    Master 2 (slots 5461-10922) ── replica
    Master 3 (slots 10923-16383) ── replica

    Cache-aside flow, TTL lifecycle, and eviction:

    Cache-aside read path
    GET cache
    Redis lookup
    HIT
    Return immediately
    MISS
    Query database
    SET + TTL
    Populate cache
    Application owns cache logic — Redis does not auto-load from DB.
    TTL and invalidation
    SET EX 300
    5 min freshness
    Jitter
    ±10% spread
    @CacheEvict
    On write/update
    Pub/Sub
    Cross-pod invalidation
    TTL alone causes stale reads — evict on writes that change data.
    Eviction under memory pressure
    maxmemory
    Hard limit
    allkeys-lru
    Evict any key LRU
    volatile-lru
    Evict TTL keys only
    LFU
    Evict least frequent
    volatile-lru protects keys without TTL (e.g. rate limit counters).

    Code walkthrough

    Spring Cache + Redis cache-aside with manual fallback:

    • @Cacheable: Spring intercepts — Redis lookup before method body on hit.
    • @CacheEvict on update: Prevents serving stale product after price change.
    • Lock on miss: SET NX prevents thundering herd — one thread loads DB.
    • Jitter on TTL: Random ±60s spread — avoids simultaneous mass expiry.
    java
    @Configuration
    @EnableCaching
    public class CacheConfig {
    @Bean
    public RedisCacheManager cacheManager(RedisConnectionFactory factory) {
    RedisCacheConfiguration config = RedisCacheConfiguration.defaultCacheConfig()
    .entryTtl(Duration.ofMinutes(10))
    .serializeValuesWith(RedisSerializationContext.SerializationPair
    .fromSerializer(new GenericJackson2JsonRedisSerializer()));
    return RedisCacheManager.builder(factory).cacheDefaults(config).build();
    }
    }
    @Service
    public class ProductService {
    private final ProductRepository repo;
    @Cacheable(value = "products", key = "#sku", unless = "#result == null")
    public Product findBySku(String sku) {
    return repo.findById(sku).orElse(null); // only on cache miss
    }
    @CacheEvict(value = "products", key = "#product.sku")
    public Product update(Product product) {
    return repo.save(product); // invalidate stale cache entry
    }
    }
    // Manual cache-aside with Lettuce (fine-grained control)
    public Optional<Product> getWithLock(String sku) {
    String key = "product:" + sku;
    String cached = redis.get(key);
    if (cached != null) return Optional.of(deserialize(cached));
    if (redis.set(key + ":lock", "1", SetArgs.Builder.nx().ex(5))) {
    try {
    Product p = repo.findById(sku).orElseThrow();
    redis.setex(key, 600 + randomJitter(), serialize(p));
    return Optional.of(p);
    } finally {
    redis.del(key + ":lock");
    }
    }
    Thread.sleep(50); // brief wait, retry get
    return getWithLock(sku);
    }

    Production example

    Redis Cluster + Spring Boot production config:

    • Redis Cluster: Hash slots across masters — horizontal scale beyond single node RAM.
    • allkeys-lfu: Keeps frequently accessed catalog keys under memory pressure.
    • Connection pool: Size pool × pod count < Redis max connections — avoid connection storm.
    • INCR + EXPIRE: Atomic rate limit pattern — no separate GET/SET race.
    java
    # application.yml
    spring:
    data:
    redis:
    cluster:
    nodes:
    - redis-0:6379
    - redis-1:6379
    - redis-2:6379
    lettuce:
    pool:
    max-active: 32
    max-idle: 16
    cache:
    type: redis
    redis:
    time-to-live: 600000 # 10 min default
    # redis.conf (ops)
    maxmemory 4gb
    maxmemory-policy allkeys-lfu # hot catalog SKUs survive pressure
    notify-keyspace-events Ex # optional: expiry listeners
    // Rate limiter — distributed counter
    public boolean allowRequest(String clientId) {
    String key = "ratelimit:" + clientId + ":" + minuteBucket();
    Long count = redis.incr(key);
    if (count == 1) redis.expire(key, 60);
    return count <= 100;
    }

    Enterprise case study

    Twitter/X — Redis at scale: Twitter used Redis for timelines, caching, and rate limiting serving hundreds of millions of users. Java/Spring services at many enterprises mirror this pattern for session and catalog cache. Lesson: monitor hit ratio — below 90% on hot paths means TTL too aggressive or invalidation gaps.

    • Before: Postgres read replicas maxed — p99 latency 400ms on product pages.
    • After: Redis cache-aside — 95% hit rate, p99 15ms, DB load dropped 80%.
    • Incident: Missing @CacheEvict on bulk price update — stale prices for 10 minutes until TTL.
    • Fix: Event-driven cache invalidation via Redis Pub/Sub on price change Kafka event.

    Performance considerations

    Redis performance for Java services:

    • Pipeline/batch: Lettuce async — batch multiple GETs for product page fan-out.
    • Serialization: JSON readable but bulky — consider Kryo or String for simple values.
    • Key design: Short keys, consistent prefix — p:SKU123 not giant JSON keys.
    • Avoid big values: >512KB values block Redis — split or compress.
    • Local L1 + Redis L2: Caffeine in-JVM for hottest keys — reduces network to Redis.

    Security considerations

    Redis security:

    • AUTH + TLS: Never expose Redis without password on public network.
    • No secrets in cache: Don't cache raw JWT or PCI data — cache opaque session ID only.
    • ACLs (Redis 6+): App user READ/WRITE on product:* only.
    • Cache poisoning: Validate data before SET — don't cache user-controlled unvalidated input.

    Scalability considerations

    Scaling Redis with Java fleet:

    • Redis Cluster: Shard by hash slot — scale memory horizontally.
    • Read replicas: Scale reads for session lookup — eventual consistency acceptable.
    • Connection limits: 10K pods × 32 connections = plan Redis maxclients.
    • Multi-region: Active-active cache is hard — prefer regional Redis with sticky routing.

    Production challenges

    Real Redis caching challenges:

    • Cache stampede: Mass expiry → DB overload — lock on miss, jitter TTL, pre-warm.
    • Stale reads: TTL-only without invalidation — user sees old data after update.
    • Redis outage: Cache-aside should degrade to DB — circuit breaker, don't fail requests.
    • Hot keys: Single key maxes one Redis slot — local cache or read replica for viral SKU.
    • Serialization bugs: Jackson polymorphic types break cache — version cache format.

    Common mistakes

    • Caching without TTL — memory fills, eviction drops random hot keys.
    • No invalidation on write — stale data until TTL; finance/catalog unacceptable.
    • Using Redis as primary database — data loss on flush/restart.
    • KEYS * in production — blocks Redis; use SCAN.
    • Same TTL for all keys — thundering herd on simultaneous expiry.

    Debugging guide

    Debug Redis caching issues:

    • Hit ratio: INFO stats — keyspace_hits / (hits + misses).
    • Key exists: redis-cli GET product:SKU123 and TTL product:SKU123.
    • Spring Cache debug: logging.level.org.springframework.cache=TRACE.
    • Memory: INFO memory — used_memory, evicted_keys counter rising = pressure.
    bash
    # Monitor hit rate
    redis-cli INFO stats | grep keyspace
    # Slow log
    redis-cli SLOWLOG GET 10
    # Check eviction policy
    redis-cli CONFIG GET maxmemory-policy
    # Spring — verify cache key
    redis-cli KEYS 'products::*' # dev only; use SCAN in prod

    Best practices

    • Use cache-aside with explicit TTL on every key.
    • @CacheEvict or event-driven invalidation on writes.
    • Add TTL jitter to prevent thundering herd.
    • Monitor hit ratio and evicted_keys.
    • Implement cache miss lock for extremely hot keys.
    • Choose allkeys-lfu for read-heavy catalog; volatile-lru if mix of TTL and permanent keys.
    • Graceful degradation — DB fallback when Redis unavailable.

    Anti-patterns

    • Cache everything — cache only proven hot paths with measured DB cost.
    • Long TTL without invalidation on mutable data — stale reads guaranteed.
    • Distributed lock via Redis for every request — overhead; use for herd protection only.
    • Storing large object graphs — cache DTOs, not entity graphs with lazy collections.
    • Single Redis instance in production — SPOF; use Cluster or Sentinel.

    Staff engineer notes

    • Staff engineers measure before caching — profile DB, identify top 10 queries, cache those first.
    • TTL is a freshness contract — document per cache namespace: catalog 10min, session 24h, rate limit 60s.
    • Redis outage must not outage product — cache is optimization layer, not dependency for availability.
    • Eviction policy is capacity planning — allkeys-lfu beats allkeys-lru for skewed access patterns (e-commerce).
    • Spring @Cacheable is convenient — understand it generates proxy; self-invocation bypasses cache.

    Interview questions

    Interview preparation

    15 questions grouped by difficulty — expand any card for a model answer and follow-up probe.

    Beginner

    5
    1. 1What is cache-aside pattern?
      Beginner

      Model answer

      • Application checks cache first. On hit, return cached value. On miss, load from database, write to cache with TTL, return value. Application manages cache
      • Redis passive store.

      Follow-up probe

      vs read-through?

    2. 2What is Redis TTL?
      Beginner

      Model answer

      Time-to-live in seconds on key.

      After expiry Redis deletes key automatically.

      SET key value EX 300.

      Prevents infinite staleness.

      Tune per data change frequency.

      Follow-up probe

      TTL vs explicit delete?

    3. 3What is a distributed cache?
      Beginner

      Model answer

      Shared cache cluster accessed by all application instances.

      Redis Cluster or single Redis with replicas.

      Enables session sharing, consistent rate limits, cache hit across pods.

      Follow-up probe

      vs local cache?

    4. 4Explain Redis eviction policies.
      Beginner

      Model answer

      When maxmemory reached: noeviction (errors), allkeys-lru (evict least recently used any key), volatile-lru (evict LRU among TTL keys), allkeys-lfu (least frequently used).

      Choose based on whether all keys have TTL.

      Follow-up probe

      LRU vs LFU?

    5. 5How integrate Redis with Spring Boot?
      Beginner

      Model answer

      spring-boot-starter-data-redis + spring-boot-starter-cache.

      @EnableCaching, RedisCacheManager bean.

      @Cacheable on service methods.

      @CacheEvict on updates.

      Lettuce client default.

      Follow-up probe

      Lettuce vs Jedis?

    Intermediate

    5
    1. 6What is cache stampede/thundering herd?
      Intermediate

      Model answer

      g.

      key expired).

      All hit database at once.

      Mitigate: lock on miss (SET NX), jitter TTL, pre-warm, stale-while-revalidate.

      Follow-up probe

      Lock implementation?

    2. 7Cache-aside vs write-through?
      Intermediate

      Model answer

      Cache-aside: app writes DB, invalidates/updates cache on write.

      Write-through: write to cache and DB synchronously on every write.

      Write-through consistent but slower writes.

      Cache-aside most common in Java.

      Follow-up probe

      Write-behind?

    3. 8How invalidate cache on update?
      Intermediate

      Model answer

      @CacheEvict same key on update method.

      Or publish invalidation event (Kafka/Redis Pub/Sub) all pods listen.

      Or versioned keys (product:v2:sku).

      TTL alone insufficient for mutable data.

      Follow-up probe

      Eventual consistency window?

    4. 9Redis Cluster vs Sentinel?
      Intermediate

      Model answer

      • Sentinel: HA for single master with failover
      • one shard. Cluster: multi-master sharding by hash slots
      • horizontal memory scale. Cluster for large cache; Sentinel for moderate size HA.

      Follow-up probe

      Hash slot?

    5. 10How handle Redis failure?
      Intermediate

      Model answer

      • Circuit breaker
      • skip cache, query DB directly. Accept higher latency. Don't fail user requests. Monitor Redis separately. Multi-AZ Cluster for HA.

      Follow-up probe

      Retry storm to DB?

    Advanced

    5
    1. 11Design catalog cache for e-commerce.
      Advanced

      Model answer

      Cache-aside, key product:{sku}, TTL 10min + jitter.

      @CacheEvict on admin update.

      Caffeine L1 for top 100 SKUs.

      Redis Cluster 3 masters.

      allkeys-lfu.

      Hit ratio target 95%.

      Kafka event invalidates on price change.

      DB fallback on Redis down.

      Follow-up probe

      Cache product list?

    2. 12Redis for session vs JWT?
      Advanced

      Model answer

      Redis session: server-side state, instant revoke, shared across pods.

      JWT: stateless, no Redis needed, harder revoke.

      Hybrid: short JWT + Redis blocklist for logout.

      PCI apps often prefer server session.

      Follow-up probe

      Session serialization?

    3. 13When NOT to use Redis cache?
      Advanced

      Model answer

      • Strong consistency required on every read. Data larger than memory budget. Write-heavy with low read ratio. Simple app with low traffic
      • DB sufficient.

      Follow-up probe

      Redis vs Memcached?

    4. 14Implement distributed rate limiter.
      Advanced

      Model answer

      INCR ratelimit:{clientId}:{minute} with EXPIRE 60 on first incr.

      Compare count to limit.

      Sliding window: sorted set with timestamp scores.

      Token bucket: Lua script atomic.

      All pods share Redis counter.

      Follow-up probe

      Race conditions?

    5. 15Cache consistency in microservices?
      Advanced

      Model answer

      Each service owns cache of its data.

      On update, emit domain event; consumers evict their cache keys.

      Avoid shared cache namespace across services.

      TTL as safety net.

      Accept brief staleness or use shorter TTL on critical data.

      Follow-up probe

      Transactional cache?

    Hands-on exercise

    Lab: Redis caching patterns:

    • Run playground — trace cache hit vs miss logic.
    • Add updateProduct method comment with @CacheEvict annotation.
    • Implement jitter: baseTtl + random(0, 60) in comment pseudocode.
    • Explain which eviction policy for mix of session keys (TTL) and catalog keys (TTL).
    • Bonus: sketch rate limiter with INCR pattern.

    JavaRedis Caching for Java

    Starter Templates
    OutputRemote JVM (Piston · Java 15)
    Press Run to execute. Real javac + JVM via remote API. Falls back to simulator on network failure.

    Architecture trade-offs

    • Cache-aside vs write-through: Cache-aside wins write latency; write-through wins read consistency.
    • Redis vs Caffeine local: Redis wins shared state; Caffeine wins zero network latency.
    • Long TTL vs frequent invalidation: Long TTL wins DB load; invalidation wins freshness.
    • allkeys-lru vs allkeys-lfu: LRU wins general; LFU wins skewed hot-key catalogs.

    Summary

    Redis accelerates Java services when used with discipline — cache-aside, explicit TTL, write invalidation, and the right eviction policy. Treat Redis as disposable acceleration, not source of truth. Next: pair with Kafka for cache invalidation events across microservices.

    Key takeaways

    • Cache-aside: app reads Redis, on miss loads DB and SET with TTL.
    • Always set TTL — eviction policy handles memory pressure.
    • @CacheEvict on writes — TTL alone causes stale data.
    • Distributed cache = shared Redis — sessions and rate limits across pods.
    • Monitor hit ratio; degrade to DB when Redis unavailable.
    Ready to mark this lesson complete?Track your journey across the entire course.