Skip to content
About
Main content

The governing arithmetic: two inequalities

When does a cache pay for itself, and how far does the layer below come to depend on it?

Two short inequalities run this mechanism. Every topic later is a resolution of one of them.

Amortization: does the cache pay?

Use these symbols:

  • P = the cost to produce one version of the cached object (for example, to run a query).
  • S = the saving for each read that is served from the .
  • λ (lambda) = the rate of reads on the object.
  • w = the rate of writes that invalidate it (make it wrong) — the churn rate.

One cached version lives for 1/w time units on average, and during that time it serves λ/w reads. Call R = λ/w the reuse factor (reads per version). The cache pays if and only if

the rate of reads on the object
the rate of writes that invalidate it; summed over its dependencies
the cost of computing one version
what serving one read from the cache saves
reads per version
Data table
Reads per version by dependencies (20 reads/h, each input committed hourly)
Inputs the result depends onReuse factor R
120
1.2416.2
1.4813.6
1.7111.7
1.9510.3
2.199.14
2.428.25
2.667.51
2.96.9
3.146.37
3.385.93
3.615.54
3.855.19
4.094.89
4.334.62
4.564.38
4.84.17
5.043.97
5.283.79
5.513.63
5.753.48
5.993.34
6.223.21
6.463.09
6.72.99
6.942.88
7.172.79
7.412.7
7.652.61
7.892.54
8.132.46
8.362.39
8.62.33
8.842.26
9.072.2
9.312.15
9.552.09
9.792.04
102
10.31.95
10.51.9
10.71.86
111.82
11.21.78
11.41.75
11.71.71
11.91.68
12.21.64
12.41.61
12.61.58
12.91.55
13.11.53
13.31.5
13.61.47
13.81.45
14.11.42
14.31.4
14.51.38
14.81.35
151.33
15.31.31
15.51.29
15.71.27
161.25
16.21.23
16.41.22
16.71.2
16.91.18
17.11.17
17.41.15
17.61.13
17.91.12
18.11.1
18.31.09
18.61.08
18.81.06
19.11.05
19.31.04
19.51.02
19.81.01
201

The value of one version is (λ/w)·S − P, and it must be positive.

The ratio P/S is a threshold: the number of reads you need before the cache is worth building. The inequality contains a lesson that practitioners often get wrong:

A million reads per second against a table written a million times per second cannot be cached usefully. A hundred reads an hour against an file can. Caching converts a read-rate problem into a write-rate problem.

Leverage: how much do you depend on the cache?

If the cache sustains h, the layer below is asked for only (1 − h)λ requests per second. Sooner or later someone will size that lower layer for exactly that smaller load. Then the cache has leverage

RelationshipCache leverage →
the fraction of reads the cache serves
reads arriving at the cache
Data table
How many reads one backing-store read stands for, by hit rate
Hit rate hLeverage 1/(1 − h)
01×
0.0121.01×
0.0251.03×
0.0371.04×
0.051.05×
0.0621.07×
0.0751.08×
0.0871.1×
0.11.11×
0.1121.13×
0.1241.14×
0.1371.16×
0.1491.18×
0.1621.19×
0.1741.21×
0.1871.23×
0.1991.25×
0.2111.27×
0.2241.29×
0.2361.31×
0.2491.33×
0.2611.35×
0.2741.38×
0.2861.4×
0.2981.43×
0.3111.45×
0.3231.48×
0.3361.51×
0.3481.53×
0.3611.56×
0.3731.6×
0.3861.63×
0.3981.66×
0.411.7×
0.4231.73×
0.4351.77×
0.4481.81×
0.461.85×
0.4731.9×
0.4851.94×
0.4971.99×
0.512.04×
0.5222.09×
0.5352.15×
0.5472.21×
0.562.27×
0.5722.34×
0.5852.41×
0.5972.48×
0.6092.56×
0.6222.64×
0.6342.73×
0.6472.83×
0.6592.93×
0.6723.05×
0.6843.17×
0.6973.29×
0.7093.44×
0.7213.59×
0.7343.76×
0.7463.94×
0.7594.14×
0.7714.37×
0.7844.62×
0.7964.9×
0.8085.22×
0.8215.58×
0.8336×
0.8466.48×
0.8587.05×
0.8717.73×
0.8838.55×
0.8959.57×
0.90810.9×
0.9212.6×
0.93314.9×
0.94518.3×
0.95823.6×
0.9733.5×
0.98357.3×
0.995200×

against the backing store. At h = 95% the leverage is 1/0.05 = 20 to 1. At h = 99% it is 1/0.01 = 100 to 1. And look at what happens at the top: a drop from h = 0.99 to h = 0.98 changes the load below from 0.01λ to 0.02λ. A one-point drop in hit rate at 99% doubles the load on the layer below.

This is how an optimization quietly turns into a load-bearing dependency that appears on no dependency diagram. It is exactly what happened to Ifeoma's database when the cache was restarted.