Sobes.tech
Junior

What is caching?

sobes.tech AI

Answer from AI

Caching is the process of storing copies of data in a fast-access location to reduce the time needed to retrieve it later. It helps avoid repeated costly operations such as reading from a database or performing complex calculations.

There are different levels and strategies of caching:

  • Built-in caching:
    • JVM caches bytecode.
    • The processor caches data and instructions in registers and L1, L2, L3 caches.
  • At the application level:
    • In-memory caches (e.g., using Ehcache, Guava Cache, Caffeine).
    • Distributed caches (e.g., using Redis, Memcached, Hazelcast).
  • At the data level:
    • Caching database query results.
    • Caching ORM objects (e.g., first and second level cache in Hibernate).
  • At the network level:
    • Proxy servers cache responses from web servers.
    • Browsers cache web resources.
  • At the operating system level:
    • Filesystem caching.

Main caching strategies:

  • Read-through: Data is retrieved from the cache. If not present, the cache loads it from the primary source and returns it to the client.
  • Write-through: Data is written simultaneously to the cache and the primary source.
  • Write-back: Data is written only to the cache. Periodically or upon eviction, data is synchronized with the primary source.
  • Write-around: Data is written only to the primary source, bypassing the cache.

The choice of strategy depends on the nature of data usage and consistency requirements.

Example of using cache for frequently accessed data:

// Example using Guava Cache
import com.google.common.cache.CacheBuilder;
import com.google.common.cache.CacheLoader;
import com.google.common.cache.LoadingCache;
import java.util.concurrent.ExecutionException;

public class DataService {

    private LoadingCache<String, String> dataCache; // Cache with automatic loading

    public DataService() {
        dataCache = CacheBuilder.newBuilder()
                .maximumSize(1000) // Maximum number of elements
                .expireAfterWrite(10, java.util.concurrent.TimeUnit.MINUTES) // Cache lifetime
                .build(new CacheLoader<String, String>() {
                    @Override
                    public String load(String key) throws Exception {
                        // Logic to load data from a "slow" source (e.g., database)
                        System.out.println("Loading data for key: " + key);
                        return loadFromDatabase(key);
                    }
                });
    }

    public String getData(String key) throws ExecutionException {
        // Get data from cache or load if absent
        return dataCache.get(key);
    }

    private String loadFromDatabase(String key) {
        // Simulate slow database load
        try {
            Thread.sleep(100);
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
        }
        return "Data for " + key;
    }

    public static void main(String[] args) throws ExecutionException {
        DataService service = new DataService();
        System.out.println(service.getData("user1")); // Load and cache
        System.out.println(service.getData("user1")); // Retrieve from cache
        System.out.println(service.getData("user2")); // Load and cache
    }
}

Caching improves performance and reduces load on primary data sources but also adds complexity (cache lifecycle management, invalidation, consistency).