Master Multi-Level Caching with JetCache: Boost Performance and Consistency

JetCache, an open‑source Java caching library, combines local (Caffeine) and distributed (Redis) caches into a multi‑level system, offering annotation‑driven lazy loading, automatic refresh, consistency strategies, cache pre‑warming, monitoring, and safeguards against penetration, avalanche, and memory leaks.

ITPUB
ITPUB
ITPUB
Master Multi-Level Caching with JetCache: Boost Performance and Consistency

What is JetCache?

JetCache is an open‑source Java caching framework that supports both local and distributed caches, providing a unified API and annotation‑driven configuration to simplify cache management.

Three Core Features

(1) Multi‑Level Cache (“layered cake”)

JetCache implements a three‑layer cache hierarchy. The top layer is a local cache (e.g., Caffeine) that stores frequently accessed data in‑process, offering nanosecond‑level latency. The middle layer is a distributed cache (e.g., Redis) that holds a larger set of data accessible across multiple instances. The bottom layer is the database, consulted only when both caches miss. When a request arrives, JetCache first checks the local cache, then the distributed cache, and finally the database, dramatically reducing database load.

(2) Annotation‑Driven Lazy Mode

Developers can enable caching with simple annotations, avoiding boilerplate code. @Cached marks a method whose return value should be cached; @CacheUpdate updates the cache when data changes; @CacheInvalidate removes stale entries. Example:

@RestController
@RequestMapping("/index")
public class IndexController {
    @Autowired
    IndexService indexService;

    @GetMapping("/get")
    @Cached(name = "userCache", key = "#userId")
    public User getUserById(long userId) {
        return indexService.getUserById(userId);
    }
}

The annotation tells JetCache to store the method result in userCache keyed by userId. Subsequent calls retrieve the value directly from the cache.

(3) Data Consistency (“golden shield”)

JetCache provides strategies to keep cache and database in sync. The “delayed double delete” approach removes the cache entry, updates the database, then deletes the cache again after a short delay to avoid stale reads. The “MQ notification” approach publishes a message after a database update, prompting other nodes to invalidate their caches.

@PostMapping("/update")
public String updateUser(User user) {
    // First delete cache
    cache.invalidate(user.getId());
    // Update database
    indexService.updateUser(user);
    // Delay then delete cache again
    try { Thread.sleep(100); } catch (InterruptedException e) { e.printStackTrace(); }
    cache.invalidate(user.getId());
    return "success";
}

Practical Guide

(1) Local Cache + Redis “golden combo”

Add the following Maven dependencies and configuration to enable Caffeine as the local cache and Redis as the distributed cache.

<dependency>
    <groupId>com.github.ben-manes.caffeine</groupId>
    <artifactId>caffeine</artifactId>
    <version>3.1.6</version>
</dependency>
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>

# Local cache configuration
jetcache.local.enabled=true
jetcache.local.type=caffeine
jetcache.local.size=1000
jetcache.local.timeToLiveInSeconds=60

# Redis configuration
jetcache.redis.host=localhost
jetcache.redis.port=6379
jetcache.redis.password=
jetcache.redis.database=0
jetcache.redis.timeToLiveInSeconds=300

With this setup, JetCache automatically manages both caches.

(2) Cache Pre‑warming

Pre‑load hot data during application startup or via a scheduled task to avoid cold‑start latency.

@Component
public class CachePreloader {
    @Autowired
    private ProductService productService;

    @Scheduled(cron = "0 0 2 * * ?") // runs daily at 2 AM
    public void preloadHotProducts() {
        List<Product> hotProducts = productService.getHotProducts();
        for (Product product : hotProducts) {
            productCache.put(product.getId(), product);
        }
    }
}

(3) Monitoring Metrics

Integrate JetCache with Micrometer, Prometheus, and Grafana to expose metrics such as hit rate, memory usage, and latency.

<dependency>
    <groupId>io.micrometer</groupId>
    <artifactId>micrometer-core</artifactId>
</dependency>

management.metrics.export.prometheus.enabled=true
management.endpoints.web.exposure.include=*

These settings enable Prometheus to scrape JetCache metrics, which can be visualized in Grafana dashboards.

Comparison with Other Frameworks

Compared with Spring Cache, JetCache adds native multi‑level support, automatic refresh, and richer consistency options. Caffeine provides only in‑process caching, while Redis offers distributed caching without local‑cache integration. JetCache combines the strengths of both, delivering a concise annotation‑based API.

// Spring Cache multi‑level (manual implementation)
@Cacheable(value = "userCache", key = "#userId", sync = true)
public User getUserById(long userId) {
    User user = redisTemplate.opsForValue().get("user:" + userId);
    if (user == null) {
        user = userDao.getUserById(userId);
        redisTemplate.opsForValue().set("user:" + userId, user);
    }
    return user;
}

// JetCache multi‑level (declarative)
@Cached(name = "userCache", key = "#userId", localCacheType = CaffeineCache.class)
public User getUserById(long userId) {
    return userDao.getUserById(userId);
}

Real‑World Impact

In an e‑commerce platform, adopting JetCache reduced product‑detail page latency from 200 ms to 50 ms and lowered database QPS from 10 k to 1 k. A social‑media service saw feed data load instantly for users, providing a native‑app‑like experience.

Precautions

(1) Cache Penetration

Use a Bloom filter to block requests for non‑existent keys before they hit the database.

@Autowired
private BloomFilter bloomFilter;

@Cached(name = "userCache", key = "#userId", unless = "#result == null")
public User getUserById(long userId) {
    if (!bloomFilter.mightContain(userId)) {
        return null;
    }
    return userDao.getUserById(userId);
}

(2) Cache Avalanche

Assign random TTL values to avoid massive simultaneous expirations.

jetcache.redis.timeToLiveInSeconds=300
jetcache.redis.randomTtlFactor=0.2

This config makes the actual TTL range 240‑360 seconds.

(3) Memory Leak

Limit local‑cache size and set an access‑based expiration.

jetcache.local.size=1000
jetcache.local.expireAfterAccessInSeconds=60

Conclusion

JetCache emerges as a powerful yet easy‑to‑use caching solution for Java back‑ends, delivering significant performance gains, reducing database pressure, and simplifying cache management through annotations and comprehensive features.

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.

JavaperformancerediscachingSpring BootCaffeineJetCache
ITPUB
Written by

ITPUB

Official ITPUB account sharing technical insights, community news, and exciting events.

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.