The labs on this site need JavaScript to run. The same content is written out below.
Fundamentals › Architecture and operations
What is a cache, and what do you gain and lose by using one?
A cache is a device that writes down a result that took a long time to get, in a place close by, and hands out what it wrote down when the same thing is asked for again. In the code of this lab, the Map named cache is the place where results are written down, and the cache lines inside the find method do this: "if there is a written-down value, give it, and only if there is none, do the slow lookup (slowLoad) and write the result down." slowLoad is the slow job of reading a value from the source, and slowCount counts how many times it was called. The lab does not measure real time. It shows "how much slow work was done" by the number of calls to slowLoad. In this lab you remove the cache lines, change the value in the source between the two lookups, or erase the cached value with the cache.remove(1) line. The same code is compiled and run, and you see how many slow lookups happen and whether the second value you receive is the same as the value in the source. (static is read as a keyword that lets main call these without creating an object, and it is not covered here.)
Checked against real execution · Compiled five combinations on JDK 21 with javax.tools, ran them, and checked against the model
import java.util.*;
public class Main {
// The source: where the values originally are
static Map<Integer, String> source = new HashMap<>();
static int slowCount = 0;
// Slow lookup: reads from the source. Counts how many times it is called
static String slowLoad(int id) {
slowCount++;
return source.get(id);
}
// Cache: the place where values that were read are written down
static Map<Integer, String> cache = new HashMap<>();
static String find(int id) {
// If a value is written down, give it; if not, read it and write it down
if (cache.containsKey(id)) return cache.get(id);
String v = slowLoad(id);
cache.put(id, v);
return v;
}
public static void main(String[] args) {
source.put(1, "keyboard");
// Look up key 1 for the first time
find(1);
// Look up key 1 again
String second = find(1);
// Slow lookups / second value received / value in the source
System.out.println(
slowCount + " / " + second + " / " + source.get(1));
}
}
The compiler and runtime are JDK 21.0.12.1 (javac 21). All five combinations (the two choices of having a cache or not, times the three choices of what happens between the two lookups, minus the one that is left out because you cannot use the cache.remove(1) line without a cache) were checked against real runs: the same source as the code on the screen (identical line by line, including the import line) was compiled with javax.tools, the class file was read by a new class loader, Main.main was run, and the standard output that was captured was compared with the model. What was compared is whether the compile succeeded, the three printed values (the number of calls to slowLoad, the value the second find returned, and the value in the source), and the output text that the last step shows. The fact that the step sentences rely on, that "after one call to find(1) at first, slowLoad has been called 1 time and the value received is keyboard", was checked by running separate source (one with a cache and one without) changed to print the result of the first find, in the same way. The code on the screen is a program that is really compiled and run, and nothing was shortened. slowLoad does not really take long and no time was measured. "Slow" is shown only by the number of calls to slowLoad, and the time of a real database or network was not measured. This cache is one Map, and it has no rule for when to throw away a written-down value (expiry time, size limit), so a value stays until it is erased with the cache.remove(1) line. I opened the documentation myself. The introduction of the Cache Abstraction chapter of the Spring Framework Reference (https://docs.spring.io/spring-framework/reference/integration/cache.html) opened only as far as the explanation that caching is support that is added transparently to an existing application, and in the lower page of the same documentation (https://docs.spring.io/spring-framework/reference/integration/cache/strategies.html) I confirmed that if the method has already been invoked with the same arguments, the cached result is returned without executing the method, and otherwise the method is executed and its result is put in the cache, and that there are features to update the contents of the cache or evict entries. For RFC 9111 (HTTP Caching), I confirmed section 1 (a cache stores responses to reduce response time and network use), section 4 (conditions for reusing a stored response), and sections 4.2 and 4.2.4 (a stored response that is past its freshness lifetime is called stale), and I did not read the last 7 thousand or so characters of the document. I did not confirm whether those documents use the words hit and miss, and the glossary uses these words only as general terms. The Map cache in this lab is just one example of the approach the documents describe, and it was not checked against real execution of Spring or of an HTTP cache. Not covered: a cache that several threads write at the same time, what to throw away when the cache is full, the validity time (TTL), many requests rushing to the source when a popular value disappears all at once, @Cacheable of Spring, the HTTP cache between a browser and a server (ETag), and Redis. These are covered in the in-depth labs "Caching and TTL", "Cache abstraction", "HTTP caching and ETag", and "Redis single-threaded execution".
Previous lab: Load balancers · Next lab: Message queues
Struck-through text was removed from the first version of the code; highlighted text was added.