What is a cache miss?
Using cache memory is an essential part of building a robust, high-performance application. Caches store information that has been frequently or recently used, hoping to predict which items users are most likely to access.
However, not every search through a cache returns the item that a user is looking for—which results in what’s known as a cache miss. So what is a cache miss exactly, and how do cache misses work?
What are cache misses?
A cache miss occurs when the application attempts to retrieve information from cache memory, but the given item is not actually present in the cache. This is the opposite of a cache hit, in which the system does discover the requested item in the cache.
Why do cache misses occur?
Cache misses may occur for three possible reasons:
- The data was never present in cache memory.
- The data was once present in cache memory, but was evicted after its time to live (TTL) expired.
- The data was once present in cache memory, but was evicted at some point based on the cache policy. When the cache is full, LRU (“least recently used”) caches evict information that users accessed in the most distant past. LFU (“least frequently used”) caches evict information that users access the least.
How can you reduce cache misses?
To improve system performance, you should seek to keep the number of cache misses as low as possible. When a cache miss occurs, the application needs to search for the information in the database, which is a much slower operation than a cache lookup. The time difference between looking up the information in the cache, and looking it up in the main database, is referred to as the miss penalty.
However, once the information is found in the database, the application may choose to store it in cache memory, which should help reduce the likelihood of future cache misses. You can monitor the frequency of cache misses by using a metric known as the cache hit ratio. This statistic is usually calculated as the number of cache hits divided by the total number of cache lookups. For example, if you have a cache hit ratio of 75 percent, then you know that 25 percent of your application’s cache lookups are actually cache misses.
There are multiple ways to reduce the number of cache misses and improve your cache hit ratio. Your options include:
- Vertically scaling the system by increasing the size of the cache and/or the amount of random-access memory (RAM).
- Testing the use of different cache policies, depending on what fits best with your applications. In addition to LRU and LFU caches, you can also use MRU (“most recently used”) caches, as well as FIFO (“first in, first out”) and LIFO (“last in, first out”) cache policies.
Cache misses in Redis
Redis is an in-memory, open-source data structure store used to implement NoSQL key-value databases, caches, and message brokers. The Redis project website provides a guide for using Redis as an LRU cache, which automatically evicts data in the cache that has been least recently requested. However, Redis also includes support for an LFU cache policy.
If you notice that your Redis cache has a higher percentage of cache misses, another solution is to increase the size of the cache, as discussed above. The maxmemory configuration directive controls the size of the Redis cache. You can adjust the Redis cache size by editing the redis.conf configuration file. For example, the following line sets the maximum Redis cache size to 100 megabytes:
By default, the maximum Redis cache size for 32-bit systems is 3 gigabytes, while there is no memory limit for 64-bit systems.
Given the tremendous utility of Redis, it’s no surprise that developers of all kinds want to make use of Redis’ cache functionality. But there’s one issue: Redis isn’t automatically compatible with programming languages such as Java out of the box.
To enjoy access to Java objects and collections, and to lower the Redis learning curve, many Java developers choose to install third-party Redis Java clients such as Redisson. Redisson implements the cache functionality of Redis using the RLocalCachedMap interface. Below is an example of how to build a Redis cache in Java:
RLocalCachedMap<String, Integer> map = redisson.getLocalCachedMap("test", LocalCachedMapOptions.defaults()); String prevObject = map.put("123", 1); String currentObject = map.putIfAbsent("323", 2); String obj = map.remove("123"); // use fast* methods when previous value is not required map.fastPut("a", 1); map.fastPutIfAbsent("d", 32); map.fastRemove("b"); RFuture<String> putAsyncFuture = map.putAsync("321"); RFuture<Void> fastPutAsyncFuture = map.fastPutAsync("321"); map.fastPutAsync("321", new SomeObject()); map.fastRemoveAsync("321");