# Redis Caching Strategy in the Plane Backend: Architecture and Implementation

> Discover Plane's dual-layer Redis caching strategy. Learn how it uses django-redis and a raw client for HTTP responses, queries, real-time features, and background tasks. Optimize your backend.

- Repository: [Plane/plane](https://github.com/makeplane/plane)
- Tags: architecture
- Published: 2026-06-23

---

**Plane implements a dual-layer Redis caching strategy that combines Django's high-level cache framework via django-redis for HTTP responses and database queries, alongside a raw Redis client for real-time collaboration features and background task processing.**

The open-source project management platform uses Redis as its sole caching infrastructure to optimize API performance and support collaborative editing. The backend code in the `makeplane/plane` repository structures this strategy around two distinct access patterns: declarative view caching through decorators and imperative direct access for WebSocket servers and async workers.

## Cache Architecture Overview

Plane's Redis caching strategy operates through two complementary layers that share the same Redis instance but serve different architectural purposes.

### Django Cache Backend Layer

This layer handles HTTP-level caching for REST API responses, database query results, throttling counters, and temporary authentication tokens like magic links. The implementation uses the `django-redis` library configured as Django's default cache backend in [`apps/api/plane/settings/local.py`](https://github.com/makeplane/plane/blob/main/apps/api/plane/settings/local.py) and [`apps/api/plane/settings/common.py`](https://github.com/makeplane/plane/blob/main/apps/api/plane/settings/common.py).

### Raw Redis Client Layer

This layer provides low-level access for the Hocuspocus real-time collaboration server and custom background tasks requiring direct Redis commands such as Pub/Sub messaging or atomic operations. The client is instantiated through the `redis_instance()` factory function.

## Configuration and Environment Setup

The Redis connection is configured through environment variables and initialized across multiple settings modules.

### Django Cache Configuration

In [`apps/api/plane/settings/local.py`](https://github.com/makeplane/plane/blob/main/apps/api/plane/settings/local.py), the cache backend is defined to use `django_redis.cache.RedisCache`:

```python
CACHES = {
    "default": {
        "BACKEND": "django_redis.cache.RedisCache",
        "LOCATION": REDIS_URL,
        "OPTIONS": {"CLIENT_CLASS": "django_redis.client.DefaultClient"},
    }
}

```

The `REDIS_URL` environment variable specifies the connection string, while `REDIS_SSL` enables TLS encryption when required. This configuration allows Django's standard `cache` API to store and retrieve serialized data transparently.

### Raw Client Initialization

For components requiring direct Redis access, [`apps/api/plane/settings/redis.py`](https://github.com/makeplane/plane/blob/main/apps/api/plane/settings/redis.py) provides the `redis_instance()` function:

```python
from plane.settings.redis import redis_instance

def publish_event(channel: str, payload: dict) -> None:
    r = redis_instance()
    r.publish(channel, json.dumps(payload))

```

This function returns a `redis.Redis` object that respects SSL configuration settings, ensuring consistent connection handling across the application.

## Key Generation and Storage Strategy

The caching implementation uses deterministic key generation to prevent collisions and enable targeted invalidation. Located in [`apps/api/plane/utils/cache.py`](https://github.com/makeplane/plane/blob/main/apps/api/plane/utils/cache.py), the `generate_cache_key()` function constructs keys that incorporate the request path, query parameters, and optionally, the authenticated user's token.

This approach ensures that cached responses remain specific to the exact request context, supporting both global and per-user cache scopes depending on the endpoint requirements.

## HTTP Response Caching with Decorators

Plane implements a decorator-based caching pattern that simplifies adding cache logic to API views without redundant boilerplate code.

### Using @cache_response

The `cache_response` decorator from [`plane/utils/cache.py`](https://github.com/makeplane/plane/blob/main/plane/utils/cache.py) wraps Django REST framework views to automatically cache JSON responses:

```python
from plane.utils.cache import cache_response
from django.views.decorators.cache import cache_control
from django.utils.decorators import method_decorator

class WorkspaceDetail(APIView):
    @cache_response(timeout=60 * 30, user=True)   # 30-minute per-user cache

    @method_decorator(cache_control(private=True, max_age=60))
    def get(self, request, pk):
        workspace = Workspace.objects.get(pk=pk)
        return Response(WorkspaceSerializer(workspace).data)

```

The decorator accepts parameters for `timeout` (in seconds), `path` (custom cache key path), and `user` (boolean indicating whether to include user-specific identifiers in the key). When `user=True`, the cache key incorporates the authentication header to create isolated cache entries per user.

According to the source code in [`apps/api/plane/license/api/views/instance.py`](https://github.com/makeplane/plane/blob/main/apps/api/plane/license/api/views/instance.py), system-wide endpoints like instance configuration use `@cache_response(60 * 60 * 2, user=False)` to cache responses for two hours across all users.

## Cache Invalidation Patterns

The strategy includes explicit invalidation mechanisms to ensure data consistency when mutations occur.

### Explicit Invalidation with @invalidate_cache

The `invalidate_cache` decorator removes cached entries when data changes, forcing subsequent requests to recalculate fresh responses:

```python
from plane.utils.cache import invalidate_cache

class WorkspaceUpdate(APIView):
    @invalidate_cache(path="/api/workspaces/", user=False)
    def post(self, request):
        # …perform update…

        return Response(status=204)

```

This pattern appears throughout the API layer, including in [`apps/api/plane/app/views/workspace/member.py`](https://github.com/makeplane/plane/blob/main/apps/api/plane/app/views/workspace/member.py), where workspace membership changes trigger cache invalidation for affected endpoints. The decorator accepts the same `path` and `user` parameters as `cache_response`, ensuring that invalidation targets exactly the keys that were previously cached.

## Raw Redis Operations for Real-Time Features

Beyond HTTP caching, Plane uses the raw Redis client for the Hocuspocus collaboration server defined in [`apps/live/src/extensions/redis.ts`](https://github.com/makeplane/plane/blob/main/apps/live/src/extensions/redis.ts). This TypeScript extension connects to the same Redis instance to synchronize document states across distributed server nodes.

Background tasks and async workers also utilize `redis_instance()` for direct operations such as:

- Publishing real-time notifications through Pub/Sub channels
- Implementing distributed rate limiting
- Managing task queues and locks

## Summary

- **Plane uses a unified Redis instance** configured through `django-redis` in [`apps/api/plane/settings/local.py`](https://github.com/makeplane/plane/blob/main/apps/api/plane/settings/local.py), serving both high-level Django caching and low-level direct access.
- **HTTP caching relies on custom decorators** (`@cache_response`, `@invalidate_cache`) defined in [`apps/api/plane/utils/cache.py`](https://github.com/makeplane/plane/blob/main/apps/api/plane/utils/cache.py) that generate deterministic keys based on request paths and user contexts.
- **Per-user and global caching scopes** are supported through the `user` parameter, allowing fine-grained control over cache isolation.
- **Raw Redis access** via `redis_instance()` in [`apps/api/plane/settings/redis.py`](https://github.com/makeplane/plane/blob/main/apps/api/plane/settings/redis.py) supports real-time collaboration through the Hocuspocus server and custom background tasks.
- **Explicit invalidation** ensures cache consistency by removing entries immediately after data mutations occur.

## Frequently Asked Questions

### How does Plane handle cache key collisions between different users?

Plane's `generate_cache_key` function in [`apps/api/plane/utils/cache.py`](https://github.com/makeplane/plane/blob/main/apps/api/plane/utils/cache.py) incorporates the authentication header into the cache key when the `user=True` parameter is passed to the `@cache_response` decorator. This creates unique cache entries per user for the same endpoint path, preventing cross-user data leakage while allowing shared global caches for public data when `user=False`.

### What is the difference between the Django cache backend and the raw Redis client in Plane?

The Django cache backend, configured as `django_redis.cache.RedisCache`, provides high-level key-value storage accessed through Django's standard `cache` API and powers the `@cache_response` decorator for HTTP caching. The raw Redis client returned by `redis_instance()` offers direct access to Redis commands like `PUBLISH`, `SUBSCRIBE`, and atomic operations, used by the real-time collaboration server in [`apps/live/src/extensions/redis.ts`](https://github.com/makeplane/plane/blob/main/apps/live/src/extensions/redis.ts) and background workers.

### How does Plane invalidate cached data when database records change?

Plane uses the `@invalidate_cache` decorator applied to mutation endpoints (POST, PUT, DELETE). When these endpoints execute, the decorator removes the specific cache keys matching the `path` and `user` parameters before the view returns. This explicit invalidation strategy ensures that subsequent GET requests generate fresh data rather than serving stale cached responses.

### Can Plane's Redis caching work with SSL/TLS encryption?

Yes, the `redis_instance()` function in [`apps/api/plane/settings/redis.py`](https://github.com/makeplane/plane/blob/main/apps/api/plane/settings/redis.py) checks for the `REDIS_SSL` environment variable when constructing the Redis connection. When enabled, the function configures the `redis.Redis` client with appropriate SSL parameters, ensuring encrypted connections for both the Django cache backend and raw client operations in production environments.