Caching Concepts

Why Cache

The product detail page of ShopEase is read thousands of times a minute but changes maybe once a day. Reading it from MySQL every time wastes database time. A cache keeps the answer in fast memory and returns it directly next time.

TermMeaning
Hitthe value was found in the cache
Missnot found: call the real method / database, then store the result
TTLtime to live: how long an entry stays valid
Evictionremoving entries (on update, on TTL expiry, or when the cache is full: LRU)
Stale datacache still holds an old value after the database changed

A Cache with TTL in Plain Java

Example

This is what a cache does inside. A fake clock makes the output deterministic, so we can "move time forward" past the TTL:

import java.util.*;
import java.util.function.*;

public class Main {
    static long now = 0;                          // fake clock in seconds

    public static void main(String[] args) {
        TtlCache<Long, String> cache = new TtlCache<>(60, () -> now);
        Function<Long, String> loadFromDb = id -> {
            System.out.println("  DB query for product " + id);
            return "Product#" + id;
        };

        System.out.println(cache.get(1L, loadFromDb));   // miss
        now = 30;
        System.out.println(cache.get(1L, loadFromDb));   // hit
        now = 61;
        System.out.println(cache.get(1L, loadFromDb));   // expired -> miss
        cache.evict(1L);
        System.out.println(cache.get(1L, loadFromDb));   // evicted -> miss
        System.out.println("hits=" + cache.hits + " misses=" + cache.misses);
    }
}

class TtlCache<K, V> {
    private record Entry<V>(V value, long expiresAt) {}
    private final Map<K, Entry<V>> map = new HashMap<>();
    private final long ttl;
    private final LongSupplier clock;
    int hits, misses;

    TtlCache(long ttlSeconds, LongSupplier clock) { this.ttl = ttlSeconds; this.clock = clock; }

    V get(K key, Function<K, V> loader) {
        Entry<V> e = map.get(key);
        if (e != null && e.expiresAt() > clock.getAsLong()) { hits++; return e.value(); }
        misses++;
        V value = loader.apply(key);
        map.put(key, new Entry<>(value, clock.getAsLong() + ttl));
        return value;
    }

    void evict(K key) { map.remove(key); }
}

Output

  DB query for product 1
Product#1
Product#1
  DB query for product 1
Product#1
  DB query for product 1
Product#1
hits=1 misses=3

What to Cache (and What Not)

  • Good: product details, category lists, configuration, exchange rates, results of slow external APIs.
  • Careful: stock count and prices (short TTL, or evict on every change).
  • Never: per-request security decisions, payment status, anything that must be exactly current.

Tip: First measure. Cache the few slow, frequently read things, not everything.