Redis Lazy Loading Cache with Guava Local Cache: Design, Implementation, and Evaluation

This article introduces a Redis lazy‑loading caching strategy combined with Guava local cache, presents design diagrams and Java code examples, analyzes its advantages and disadvantages, and discusses suitable scenarios such as precise query workloads and micro‑service architectures.

Architecture Digest
Architecture Digest
Architecture Digest
Redis Lazy Loading Cache with Guava Local Cache: Design, Implementation, and Evaluation

In many backend systems Redis is used as a high‑frequency cache to improve performance and reduce pressure on relational databases, but heavy read/write traffic can still cause I/O bottlenecks. To alleviate this, the article proposes using a local Guava cache as a supplementary layer, enabling lazy loading of data from Redis only when needed.

Design Example

The "Redis Lazy Loading Cache" pattern stores data in Redis only after a precise query; new data is not cached on insertion. A flow diagram illustrates the process of checking the cache first, falling back to the database, and then populating Redis for future queries.

// Pseudo‑code example Xx represents your business object (e.g., User, Goods)
public class XxLazyCache {
    @Autowired
    private RedisTemplate<String, Xx> redisTemplate;
    @Autowired
    private XxService xxService; // your business service

    /**
     * Query: check cache first, load from DB if missing, then cache the result.
     */
    public Xx getXx(int id) {
        // 1. Check cache
        Xx xxCache = getXxFromCache(id);
        if (xxCache != null) {
            return xxCache; // fast return
        }
        // 2. Query DB (assume id always exists)
        Xx xx = xxService.getXxById(id);
        // 3. Populate cache (lazy load)
        setXxFromCache(xx);
        return xx;
    }

    /**
     * Delete or update: remove cache after DB operation.
     */
    public void deleteXxFromCache(long id) {
        String key = "Xx:" + xx.getId();
        redisTemplate.delete(key);
    }

    private void setXxFromCache(Xx xx) {
        String key = "Xx:" + xx.getId();
        redisTemplate.opsForValue().set(key, xx);
    }

    private Xx getXxFromCache(int id) {
        String key = "Xx:" + id;
        return redisTemplate.opsForValue().get(key);
    }
}

// Business class
public class XxServie {
    @Autowired
    private XxLazyCache xxLazyCache;

    public Xx getXxById(long id) {
        // query DB (implementation omitted)
        return xx;
    }

    public void updateXx(Xx xx) {
        // update DB (omitted)
        xxLazyCache.deleteXxFromCache(xx.getId());
    }

    public void deleteXx(long id) {
        // delete DB (omitted)
        xxLazyCache.deleteXxFromCache(id);
    }
}

@Data
public class Xx {
    private Long id; // primary key
    // ... other fields
}

Advantages

Minimizes cache size while satisfying precise‑query workloads, avoiding cold data occupying memory.

Low intrusion on CRUD operations; cache invalidation occurs automatically on delete.

Plug‑in design allows upgrading legacy systems without pre‑loading all data.

Disadvantages

Requires controlled data volume; not suitable for unbounded growth scenarios.

Less appropriate for global caching in micro‑service environments where a shared cache is needed.

Redis + Local Cache in Micro‑services

In high‑frequency streaming scenarios, multiple micro‑services share a large Redis cache, causing heavy load. By adding a Guava local cache, each service reduces Redis reads, achieving lower latency without network overhead.

/**
 * Demonstrates how to combine Redis auto‑increment, hash, and Guava local cache
 * for generating and caching per‑device incremental IDs.
 */
public class DeviceIncCache {
    // Guava local cache
    private Cache<String, Integer> localCache = CacheBuilder.newBuilder()
            .concurrencyLevel(16)
            .initialCapacity(1000)
            .maximumSize(10000)
            .expireAfterAccess(1, TimeUnit.HOURS)
            .build();

    @Autowired
    private RedisTemplate<String, Integer> redisTemplate;

    private static final String DEVICE_INC_COUNT = "device_inc_count"; // Redis key for global counter
    private static final String DEVICE_INC_VALUE = "device_inc_value"; // Redis hash key for device → inc mapping

    /**
     * Get the incremental number for a device.
     */
    public int getInc(String deviceCode) {
        // 1. Try local cache
        Integer inc = localCache.getIfPresent(deviceCode);
        if (inc != null) {
            return inc;
        }
        // 2. Try Redis hash
        inc = (Integer) redisTemplate.opsForHash().get(DEVICE_INC_VALUE, deviceCode);
        // 3. If not present, generate a new global increment
        if (inc == null) {
            inc = redisTemplate.opsForValue().increment(DEVICE_INC_COUNT).intValue();
            redisTemplate.opsForHash().put(DEVICE_INC_VALUE, deviceCode, inc);
        }
        // 4. Populate local cache
        localCache.put(deviceCode, inc);
        return inc;
    }
}

Pros of This Combined Approach

Redis guarantees persistence while the local cache provides ultra‑fast reads, reducing overall Redis pressure.

Guava offers rich APIs for expiration policies and size limits, keeping memory usage under control.

Cache loss on service restart does not affect correctness because data remains in Redis.

In distributed micro‑service setups, each instance caches only its own device IDs, optimizing memory usage.

The incremental IDs support uniform partitioning of Kafka topics and device‑count statistics.

Cons

Adds implementation complexity compared to a pure Redis solution.

Only suitable for data that is append‑only (no updates) because cache invalidation logic assumes immutability.

Conclusion

Redis’s versatile data structures (strings, hashes, bitmaps, lists) make it ideal for many business scenarios, while Guava’s local cache complements it by delivering high‑speed reads and fine‑grained control over memory usage. Together they form a powerful, controllable caching layer for precise‑query workloads and micro‑service architectures.

Original Source

Signed-in readers can open the original source through BestHub's protected redirect.

Sign in to view source
Republication Notice

This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactadmin@besthub.devand we will review it promptly.

JavaCacheredisspringGuavalazy loading
Architecture Digest
Written by

Architecture Digest

Focusing on Java backend development, covering application architecture from top-tier internet companies (high availability, high performance, high stability), big data, machine learning, Java architecture, and other popular fields.

0 followers
Reader feedback

How this landed with the community

Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.