DiskCache DjangoCache Benchmarks

DiskCache provides a Django-compatible cache API in diskcache.DjangoCache. A discussion of its options and abilities are described in the tutorial. Here we try to assess its performance compared to other Django cache backends.

Keys and Values

A survey of repositories on Github showed a diversity of cached values. Among those observed values were:

  1. Processed text, most commonly HTML. The average HTML page size in 2014 was 59KB. Javascript assets totalled an average of 295KB and images range dramatically but averaged 1.2MB.
  2. QuerySets, the building blocks of the Django ORM.
  3. Numbers, settings, and labels. Generally small values that vary in how often they change.

The diversity of cached values presents unique challenges. Below, keys and values, are constrained simply to short byte strings. This is done to filter out overhead from pickling, etc. from the benchmarks.

Backends

Django ships with four cache backends: Memcached, Database, Filesystem, and Local-memory. The Memcached backend uses the PyLibMC client backend. Included in the results below is also Redis provided by the django-redis project built atop redis-py.

Not included were four projects which were difficult to setup and so impractical for testing.

  1. Cacheops - incompatible filebased caching.

Other caching related projects worth mentioning:

  1. Cacheback moves all cache store operations to background Celery tasks.
  2. Newcache claims to improve Django’s Memcached backend.
  3. Supports tagging cache entries.

There are also Django packages which automatically cache database queries by patching the ORM. Cachalot has a good comparison and discussion in its introduction.

Filebased

Django’s filesystem cache backend has a severe drawback. Every set operation checks whether a cull operation is necessary. This check requires listing all the files in the directory. To do so a call to glob.glob1 is made. As the directory size increases, the call slows linearly.

Timings for glob.glob1
Count Time
1 1.602ms
10 2.213ms
100 8.946ms
1000 65.869ms
10000 604.972ms
100000 6.450s

Above, the count regards the number of files in the directory and the time is the duration of the function call. At only a hundred files, it takes more than five milliseconds to construct the list of files.

Concurrent Access

The concurrent access workload starts eight worker processes each with different and interleaved operations. None of these benchmarks saturated all the processors. Operations used 1,100 unique keys and, where applicable, caches were limited to 1,000 keys. This was done to illustrate the impact of the culling strategy in locmem and filebased caches.

Get

_images/djangocache-get.png

Under heavy load, DjangoCache gets are very low latency. At the 99th percentile they are on par with the Memcached cache backend.

Set

_images/djangocache-set.png

Not displayed above is the filebased cache backend. At all percentiles, the latency exceeded five milliseconds. Timing data is available below. Though DiskCache is the slowest, its latency remains competitive.

Delete

_images/djangocache-delete.png

Like sets, deletes require writes to disk. Though DjangoCache is the slowest, it remains competitive with latency less than five milliseconds. Remember that unlike Local-memory, Memached, and Redis, it persists all cached data.

Timing Data

Not all data is easily displayed in the graphs above. Miss rate, maximum latency and total latency is recorded below.

Timings for locmem
Action Count Miss Median P90 P99 Max Total
get 712546 140750 36.001us 57.936us 60.081us 10.202ms 28.962s
set 71530 0 36.955us 39.101us 45.061us 2.784ms 2.709s
delete 7916 0 32.902us 35.048us 37.193us 1.524ms 265.399ms
Total 791992           31.936s

Notice the high cache miss rate. This reflects the isolation of local memory caches from each other. Also the culling strategy of local memory caches is random.

Timings for memcached
Action Count Miss Median P90 P99 Max Total
get 712546 69185 87.023us 99.182us 110.865us 576.973us 61.758s
set 71530 0 89.169us 102.043us 114.202us 259.876us 6.395s
delete 7916 0 85.115us 97.990us 108.957us 201.941us 672.212ms
Total 791992           68.825s

Memcached performance is low latency and very stable.

Timings for redis
Action Count Miss Median P90 P99 Max Total
get 712546 69526 160.933us 195.980us 239.134us 1.365ms 116.816s
set 71530 0 166.178us 200.987us 242.949us 587.940us 12.143s
delete 7916 791 143.051us 177.860us 217.915us 330.925us 1.165s
Total 791992           130.124s

Redis performance is roughly half that of Memcached. Beware the impact of persistence settings on your Redis performance. Depending on your use of logging and snapshotting, maximum latency may increase significantly.

Timings for diskcache
Action Count Miss Median P90 P99 Max Total
get 712546 69509 33.855us 56.982us 79.155us 11.908ms 30.078s
set 71530 0 178.814us 1.355ms 5.032ms 26.620ms 34.461s
delete 7916 0 107.050us 1.280ms 4.738ms 17.217ms 3.303s
Total 791992           67.842s

DjangoCache defaults to using eight shards with a 10 millisecond timeout. Notice that cache get operations are in aggregate more than twice as fast as Memcached. And total cache time for all operations is comparable. The higher set and delete latencies are due to the retry behavior of DjangoCache objects. If lower latency is required then the retry behavior can be disabled.

Timings for filebased
Action Count Miss Median P90 P99 Max Total
get 712749 103843 112.772us 193.119us 423.908us 18.428ms 92.428s
set 71431 0 8.893ms 11.742ms 14.790ms 44.201ms 646.879s
delete 7812 0 223.875us 389.099us 679.016us 15.058ms 1.940s
Total 791992           741.247s

Notice the higher cache miss rate. That’s a result of the cache’s random culling strategy. Get and set operations also take three to twenty times longer in aggregate as compared with DjangoCache.