The last few weeks Iβve been writing about caching algorithms quite a bit. Iβm going to use this note as a summary of the last few weeksβ work.
We want to determine if a key, , that has just entered a cache is likely to be accessed before being evicted. In other words, given a cache with size and a keyspace with size , what is the probability reappears before at least other keys? If this probability is sufficiently small, letβs preemptively mark the key for deletion for when the cache faces memory pressure.
We can analyze streams/datasets of arbitrary length for the number of one-hit keys in the stream, but a more interesting problem arises when we focus on a cache of a given size.
β I explain why this is the case in this note. Think of as being on a random, exponential βtimerβ which fires (on average) every periods.
Alas, we cannot see into the future, so we must answer this probabilistically. Letβs start by considering just a single pairwise match-up. Given keys with corresponding appearance probabilities , is accessed before with probability .
We can integrate over to get the expectation of the number of unique keys (call this random variable ) which will arrive before .
In this note I sketch out these assumptions that allow us to go from linear the size of to constant.
In practice, to βintegrate over β, we must store for each . This introduces a significant memory overhead and requires iterating! over the keyspace before each insertion. This is not computationally viable.
If we assume is constant on all keys excluding , we can (1) calculate this in constant time the size of and (2) provide an estimate that maximizes .
To get approximate counts of all over a large keyspace we can implement a CountMinSketch alongside our probability oracle.
I didnβt have to do much work to get a wrapper around the Redis protocol, does all the work for me. Though the redcon layer brings my machine from to rps.
I implemented this in Go and got quite nice performance. Placing a middleware between the client and has a significant performance penalty, however this penalty isnβt exacerbated when checking for vs.Β just acting as a proxy.
====== SET ======
# with p_ohw enabled ::: throughput summary: 85778.01 requests per second
1000000 requests completed in 11.66 seconds
latency summary (msec):
avg min p50 p95 p99 max
0.302 0.024 0.303 0.431 0.559 1.735
# without p_ohw enabled ::: throughput summary: 85645.77 requests per second
1000000 requests completed in 11.68 seconds
latency summary (msec):
avg min p50 p95 p99 max
0.302 0.024 0.303 0.431 0.567 1.239Iβm not going to pursue this much further. In practice, the desired result (minimize memory pressure to keep rps high, avoid eviction system from running) really only occurs at . Furthermore, thinking through this idea was a lot more rewarding than trying to obsess over how we can tune the parameters for any given cache.