# CacheTower stacks cache layers for .NET so a stale entry is not a miss

> Turner Software's CacheTower puts an in-memory layer over file, MongoDB or Redis layers, adds background refresh of stale data and locks factory calls per key locally and across instances. The core idea comes from Stack Overflow's own two layer cache.

**TurnerSoftware/CacheTower** — An efficient multi-layered caching system for .NET

- Repository: https://github.com/TurnerSoftware/CacheTower
- Stars: 630 · Forks: 38
- Language: C#
- License: MIT
- Published: 2026-10-21 · Updated: 2026-10-21 · Language: en
- Canonical page: https://hysenlabs.com/projects/turnersoftware-cachetower

## The CPU cache analogy, taken literally

CacheTower opens with an observation about hardware. Computers have multiple layers of caching, from the L1, L2 and L3 CPU caches through to RAM and even disk caches, each with a different purpose and performance profile, and then asks why code does not work the same way. The answer the project gives is that CacheTower is not a single type of cache but a multi-layer solution to caching with each layer on top of another. A multi-layer cache is meant to give the performance benefit of a fast cache such as in-memory together with the resilience of a file, database or Redis-backed cache. The inspiration is credited explicitly, a blog post by Nick Craver about how Stack Overflow does caching, where Stack Overflow uses a custom two-layer solution of in-memory and Redis. That provenance is useful to an evaluator, because the design targets a specific situation: a service where the in-process cache is fast but volatile, and the shared cache is durable but slower, and neither alone is the right answer.

## The NuGet split follows the layer boundaries

The core package on NuGet is called CacheTower, and it provides the core infrastructure as well as an in-memory cache layer. Additional layers arrive as separate packages, which is the packaging decision worth understanding before you start. CacheTower.Providers.Redis provides a cache layer for Redis, and CacheTower.Providers.Database.MongoDB provides a cache layer for MongoDB. CacheTower.Extensions.Redis provides distributed locking and eviction via Redis, which are cross-instance concerns rather than storage concerns, so they sit in an extensions package. Serialization is split the same way, with CacheTower.Serializers.NewtonsoftJson for a JSON serializer using Newtonsoft.Json, CacheTower.Serializers.SystemTextJson for one using System.Text.Json, and CacheTower.Serializers.Protobuf for a Protobuf serializer using protobuf-net. The result is that a team can install only the pieces it uses, and the repository layout, with a solution file, src/, tests/, benchmarks/ and a docs/ directory, shows the same split in the source.

## Refresh locking is the feature the layers exist for

The interesting engineering is in what happens when an entry expires while several callers want it. Cache Tower lists local refresh locking, which guarantees only one factory call per key locally, and distributed refresh locking, which guarantees only one factory call per key across multiple application instances. The local part is the ordinary cache stampede fix, one caller does the work and the rest wait on the same promise. The distributed part is the same idea lifted out of the process, and it is what makes the library interesting rather than merely convenient. Without it, a fleet of instances all miss at the same moment and each one recomputes. With it, one instance does the work. The same package, CacheTower.Extensions.Redis, also provides distributed eviction via Redis, which the documentation frames as helping to keep caches across multiple application instances the same. Eviction and locking together are what turn a shared cache from a place where instances disagree into a coherent one.

## Stale data is refreshed in the background

A separate feature addresses the other half of the multi-layer idea: background refreshes of non-expired but stale data, which the documentation says help avoid expired data cache misses. The normal cache contract is that an entry either exists and is valid or it is gone and you pay the cost of the factory call. A multi-layer cache can do better, because it can serve the last known value while one layer is being refreshed underneath. The implementation detail that matters for a user is visible in the table of contents, which includes a subsection called Avoiding Disposed Contexts under the background refreshing section. That heading tells you the refresher outlives the scope that started it, so a refresh that captures a request scope, a database context or a scoped service will find it disposed. The documentation ships a section on it for a reason, and it is the first thing to read before enabling the feature in an application with scoped dependencies.

## An all-async API on .NET Standard 2.0

Cache Tower is all-async, and the documentation says it is ready for high performance workloads, which in practice means the API surface is asynchronous from the first call rather than offering a synchronous wrapper. The compatibility target is .NET Standard 2.0, and the documentation spells out the coverage as .NET Framework 4.6.1 and later, .NET Core 2.0 and later and .NET 5.0 and later. That target is deliberate and conservative: it is the widest range that still includes the older Framework, which is the sort of library you find in an enterprise .NET shop that has not moved everything to modern .NET yet. The performance claim itself is documented rather than measured by a third party, since the feature list points at a comparison document in the docs/ directory and the repository also carries a benchmarks/ directory. The benchmarks being in the repository is the useful part, because it means you can read how the comparison was set up before you accept the conclusion.

## Where CacheTower is the wrong tool

Three cases argue against it. The first is a single instance application that only needs a fast dictionary-like cache, where the layering, the serializer packages and the locking extensions are all weight without benefit. The second is a service that already has a coherent cache abstraction of its own, because Cache Tower is opinionated about how a cache behaves and adopting it means changing how your code asks for cached data. The third, and the one an adopter should weigh most carefully, is the release state. The published versions are 0.14.0 from 2023-08-10, 0.13.0 from 2022-07-10 and 0.12.0 from 2022-06-25, so the package you install from NuGet is a version zero release whose last publication is over three years old, while the last push to the repository was on 2026-09-17. Work is happening in the repository, but there is no new package for you to consume, and a zero major version is the packaging convention for an API the authors still consider unstable. Support is also commercial in part, with paid support plans from Turner Software covering its open source projects and funding the work, which is a reasonable arrangement but worth knowing before you depend on it.

## Conclusion

Adopt CacheTower if you run several .NET instances that need a shared cache with a fast local layer in front, and if you need stale entries refreshed in the background rather than expiring into a miss. Do not adopt it if a single IMemoryCache or a plain Redis client already covers your case, since the layering, the refresh locking and the serializer packages are complexity you would otherwise not carry. Before you upgrade, check the version you would actually install, because the newest published package is 0.14.0 from 2023-08-10 even though the repository was pushed to on 2026-09-17, and read the section on disposed contexts before you enable background refresh.

## FAQ

### Which cache layers does CacheTower support?

Local caching with in-memory and file based layers, and distributed caching with MongoDB and Redis. The library supports one or more cache layers, so a cache can be built from several of them with each layer on top of the last.

### Which NuGet packages do I need to install for CacheTower?

The core CacheTower package provides the infrastructure and an in-memory layer, and it also covers file caching. The optional packages are CacheTower.Providers.Redis, CacheTower.Providers.Database.MongoDB, CacheTower.Extensions.Redis for distributed locking and eviction, and the serializer packages CacheTower.Serializers.NewtonsoftJson, CacheTower.Serializers.SystemTextJson and CacheTower.Serializers.Protobuf.

### How does CacheTower stop several threads or instances recomputing the same value?

Local refresh locking guarantees only one factory call per key within the process, and distributed refresh locking via Redis guarantees only one factory call per key across multiple application instances. Distributed eviction via Redis is provided in the same extensions package to keep those caches consistent.

### Which .NET versions does CacheTower target?

It targets a minimum of .NET Standard 2.0 for wide compatibility, which the documentation lists as .NET Framework 4.6.1 and later, .NET Core 2.0 and later and .NET 5.0 and later.

## Sources

- [Issues](https://github.com/TurnerSoftware/CacheTower/issues)
- [License: MIT](https://github.com/TurnerSoftware/CacheTower/blob/main/LICENSE)
- [README](https://github.com/TurnerSoftware/CacheTower/blob/main/README.md)
- [Releases](https://github.com/TurnerSoftware/CacheTower/releases)
- [TurnerSoftware/CacheTower on GitHub](https://github.com/TurnerSoftware/CacheTower)

---

Hysen Labs editorial analysis, written from the project's own repository and release notes. Cite the canonical page: https://hysenlabs.com/projects/turnersoftware-cachetower
