Caching mechanisms

Caching is a technique used to store data in memory for quick access, which helps improve the performance and scalability of applications. In the Java ecosystem, various caching mechanisms can be employed at different levels, such as application-level caching, load balancer caching, and database caching.

  1. Application-level caching in Spring Boot:

Spring Boot provides support for caching through the Spring Cache Abstraction. This abstraction allows you to use different cache providers, such as:

  • In-memory caching (e.g., ConcurrentHashMap, Caffeine)
  • Distributed caching (e.g., Redis, Hazelcast, Memcached)

To enable caching in Spring Boot, follow these steps:

a. Add the necessary dependencies for the cache provider you want to use. b. Enable caching by adding @EnableCaching annotation in the main application class. c. Configure cache properties in the application.properties or application.yml file. d. Use the @Cacheable, @CachePut, @CacheEvict, and @CacheConfig annotations to manage caching behavior in your application.

  1. Load balancer caching:

Load balancers can also use caching mechanisms to store and serve frequently requested content, reducing the load on the backend servers. This is also known as reverse proxy caching. Some popular load balancers that support caching are:

  • NGINX: It provides caching capabilities through the proxy_cache directive, which allows you to cache HTTP responses from backend servers.
  • HAProxy: It supports caching through the http-request cache-use and http-response cache-store directives.
  • AWS Elastic Load Balancer (ELB): If you use AWS, you can also implement caching at the load balancer level using Amazon CloudFront, a CDN service.
  1. Database caching:

Database caching involves storing the results of database queries in memory to reduce the load on the database server and improve query performance. There are two primary types of database caching:

a. Query caching: The database caches the results of frequently executed queries. This is supported by databases like MySQL and PostgreSQL.

b. Application-level database caching: This involves using a caching layer between the application and the database, storing query results in memory. Examples include Hibernate’s second-level cache and MyBatis cache.

To sum up, caching in the Java Spring Boot ecosystem can be implemented at various levels, such as the application, load balancer, and database. By using caching effectively, you can significantly improve your application’s performance and scalability.

Application-level caching in Spring Boot:

Caching in Spring Boot should be used when you have expensive or time-consuming operations that produce the same result when given the same input parameters. This is especially useful when you have read-heavy operations or when the underlying data doesn’t change frequently.

Here’s a real-life example: Let’s say you have an e-commerce application that has a product catalog. The catalog is updated infrequently, but users often browse and search for products. You can use caching to store product details, reducing the number of database queries and improving the overall performance.

To implement caching in Spring Boot, you can follow these steps:

  1. Add the necessary dependencies to your pom.xml or build.gradle file. In this example, we’ll use Caffeine as the cache provider:

For Maven, add the following to pom.xml:

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-cache</artifactId>
</dependency>
<dependency>
    <groupId>com.github.ben-manes.caffeine</groupId>
    <artifactId>caffeine</artifactId>
</dependency>

  • 2. Enable caching by adding the @EnableCaching annotation in your main application class:
@SpringBootApplication
@EnableCaching
public class Application {
    public static void main(String[] args) {
        SpringApplication.run(Application.class, args);
    }
}

  • 3. Configure cache properties in the application.properties or application.yml file. For Caffeine, you can set the maximum size and expiration time:
spring.cache.type=caffeine
spring.cache.caffeine.spec=maximumSize=500,expireAfterAccess=10m

It is important to note that the maximumSize parameter does not represent a metric in terms of memory (e.g., bytes or kilobytes). Instead, it is a limit on the number of entries in the cache. When the cache reaches this limit, it will start evicting the least recently used entries to make room for new ones.

The expireAfterAccess parameter specifies the duration after which an entry in the cache will be automatically removed if it hasn’t been accessed (read or write) during that time. In this case, the duration is set to 10 minutes.

  • 4. Use the @Cacheable annotation in your service class to cache the result of a method. In our example, we’ll cache the product details based on the product ID:
@Service
public class ProductService {

    @Autowired
    private ProductRepository productRepository;

    @Cacheable(value = "productCache", key = "#productId")
    public Product findProductById(Long productId) {
        return productRepository.findById(productId)
            .orElseThrow(() -> new ProductNotFoundException("Product not found with ID: " + productId));
    }
}

The @Cacheable annotation indicates that the result of the findProductById method should be cached with the cache name productCache and the key as the productId. When the method is called with the same productId, the cached result is returned instead of querying the database.

Where cache data is stored ?

The location where cache data is stored depends on the caching mechanism being used. Here is a summary of where cache data is stored for some common caching mechanisms:

  1. In-memory caching (e.g., ConcurrentHashMap, Caffeine): In-memory caching stores cache data directly in the application’s JVM heap memory. This provides fast access times but can lead to increased memory consumption, and the cache is not shared across multiple instances of the application.
  2. Distributed caching (e.g., Redis, Hazelcast, Memcached): Distributed caching systems store cache data in their respective data stores, which can be on separate machines or instances. The data can be distributed across multiple nodes, providing better scalability and fault tolerance. In this case, the cache data is not stored in the application’s JVM heap memory but in the memory of the caching system’s instances.
  3. Load balancer caching (e.g., NGINX, HAProxy, AWS ELB with CloudFront): In load balancer caching or reverse proxy caching, the cache data is stored in the memory of the load balancer or reverse proxy server. This can help reduce the load on backend servers and improve response times, especially for static or infrequently changing content.
  4. Database caching (e.g., MySQL query cache, PostgreSQL query cache, Hibernate second-level cache): Database caching can involve storing cache data in the memory of the database server itself (e.g., MySQL and PostgreSQL query caches) or in an intermediate caching layer between the application and the database (e.g., Hibernate second-level cache). In the first case, the cache data is stored in the database server’s memory, while in the second case, the cache data can be stored in the application’s JVM heap memory or in an external caching system, depending on the configuration.

The location where cache data is stored depends on your choice of caching mechanism and the specific requirements of your application, such as performance, scalability, and fault tolerance.