---
title: Cache backends
url: https://citry.dev/advanced/cache-backends/
description: "Choose in-process or shared cache storage and configure safe keys across workers and deployments."
---
# Cache backends

Choose a cache backend based on where Citry's generated values need to be
available. A single-process application can use the default in-memory store.
Several workers, hosts, or fragment-serving processes need a shared store.

Citry uses this backend for more than rendered-output caching. The same store
can contain generated dependency scripts and optional server-held Events
state. Size and protect it for all of those uses.

## Choose a backend

| Backend | Visible from | Good fit |
| --- | --- | --- |
| [`InMemoryCache`](/reference/citry/#citry-inmemorycache) | One process | Local development or one worker |
| `DiskCache` | Workers on one host | Multi-process deployment without a cache service |
| `RedisCache` | Workers on several hosts | Distributed deployment |
| `DjangoCache` | Django's configured cache | Existing Django application |

Citry's DiskCache and Redis adapters wrap clients that you create. Install and
configure `diskcache` or `redis` in the application that uses the respective
adapter. `DjangoCache` reuses Django's cache framework.

## Use the in-process store

Every [`Citry`](/reference/citry/#citry-citry) instance gets a fresh in-process backend unless
you pass another one:


```python
from citry import Citry, InMemoryCache

app = Citry()
assert isinstance(app.cache, InMemoryCache)
```


The store is thread-safe and unbounded by default. Set `max_entries` to use
least-recently-used eviction:


```python
backend = InMemoryCache(max_entries=1_000)
app = Citry(cache=backend)
```


The limit applies to all values in that backend, not only rendered output.
Each `Citry()` call creates its own store, so one process cannot read values
generated by another.

## Share values across workers

Use Redis when workers may run on different hosts:


```python
import redis

from citry import Citry
from citry.contrib.caches import RedisCache

client = redis.Redis(host="cache.internal")
backend = RedisCache(client, prefix="myapp:")
app = Citry(cache=backend)
```


Use DiskCache when the workers share one host and filesystem:


```python
import diskcache

from citry import Citry
from citry.contrib.caches import DiskCache

store = diskcache.Cache("/var/cache/citry")
app = Citry(cache=DiskCache(store))
```


In a Django application, wrap one of the project's configured caches:


```python
from django.core.cache import caches

from citry import Citry
from citry.contrib.django import DjangoCache

backend = DjangoCache(caches["default"])
app = Citry(cache=backend)
```


Use the same backend configuration in every process that renders or serves
Citry output. This is especially important for
[HTML fragments](/advanced/html-fragments/), because the browser's asset
request may reach a different worker from the render request.

## Share render hits safely

A shared store does not automatically make rendered output reusable between
`Citry` instances. Configure both a stable application namespace and a
deployment generation:


```python
import os

from citry import Citry

app = Citry(
    cache=backend,
    extensions_defaults={
        "cache": {
            "namespace": "storefront-production",
            "generation": os.environ["RELEASE_SHA"],
            "ttl": 300,
        },
    },
)
```


Without both values, render-cache keys remain local to one Citry engine. A
namespace on its own still includes the engine identity. A generation without
a namespace is invalid.

Use the same namespace and generation in every worker. Change the generation
whenever code, templates, extensions, helpers, or configuration can change
rendered output. The change makes old entries unreachable immediately; their
expiry or the backend's eviction policy removes them later.

## Set capacity and artifact limits

Configure the shared store's memory, disk, eviction, and retention in its own
client or service. Citry passes TTL values to the adapter, but it does not
manage the total capacity of Redis, DiskCache, or Django's backend.

The Cache extension separately limits one rendered artifact to 1,000,000
bytes by default:


```python
app = Citry(
    cache=backend,
    extensions_defaults={
        "cache": {
            "max_entry_bytes": 2_000_000,
        },
    },
)
```


Set `max_entry_bytes` to `None` to remove that configured cap. The artifact
format still has an absolute 16 MiB safety limit. An oversized render succeeds
but is not stored.

## Adapt another string store

The [`CitryCache`](/reference/citry/#citry-citrycache) protocol has four synchronous methods:


```python
class ApplicationCache:
    def get(self, key: str) -> str | None: ...

    def set(
        self,
        key: str,
        value: str,
        ttl: float | None = None,
    ) -> None: ...

    def delete(self, key: str) -> None: ...

    def has(self, key: str) -> bool: ...
```


Keys and values are strings. `get()` returns `None` for an absent or expired
entry. `set()` treats `ttl=None` as no expiry and receives seconds for a timed
entry.

Pass an adapter object or an import string to `Citry`:


```python
app = Citry(cache=ApplicationCache())
app = Citry(cache="myapp.cache.ApplicationCache")
```


Citry checks that the four methods exist during construction. Exceptions from
backend operations propagate; implement retries or a cache-as-miss policy in
the adapter if that is what the application requires.

## Clear the right scope

[`Citry.clear`](/reference/citry/#citry-citry-clear) clears an in-process backend and advances
the current engine's local cache revision. The built-in shared adapters do not
offer store-wide `clear()` methods, because the underlying store may contain
other applications' data.

For a deployment-wide change, move every worker to a new generation. For one
rendered-output entry, use the key helpers described in
[Cache rendered output](/advanced/caching/#change-or-remove-entries).

## See also

- [Cache rendered output](/advanced/caching/) for variations, expiry, and
  privacy requirements.
- [HTML fragments](/advanced/html-fragments/) for the multi-worker asset
  delivery case.
- [Events state](/events/state/) for server-held component state.