All Articles
Performance

Redis Was Already Running Tabeer.ai's Celery Queue — So I Put It to Work as Django's Cache Too

Shahid MalikBy Shahid MalikSeptember 4, 20265 min read

Tabeer.ai already had Redis in production for Celery's broker and result backend. Adding it as Django's cache framework backend needed one settings block, a DEBUG-driven fallback, and a test that actually proves a cache hit, not just that caching code exists.

Tabeer.ai's Redis instance was already doing real work in production — Celery's broker and result backend for background email tasks. It sat unused for anything else, which meant a public, frequently-hit endpoint like the platform stats view was recomputing the same aggregate query on every single request. Redis was right there. Using it as Django's cache backend too, rather than adding a second caching layer, was the obvious move.

Django 5's Built-In Redis Backend, No Extra Package

Older Django projects reach for django-redis as a third-party dependency. Django 5.x ships its own django.core.cache.backends.redis.RedisCache — no separate package needed, one less dependency to track for CVEs and version compatibility:

if DEBUG:
    CACHES = {"default": {"BACKEND": "django.core.cache.backends.locmem.LocMemCache"}}
else:
    CACHES = {
        "default": {
            "BACKEND": "django.core.cache.backends.redis.RedisCache",
            "LOCATION": env("REDIS_CACHE_URL", default="redis://localhost:6379/1"),
        }
    }

Same Redis instance Celery already talks to, but logical database 1 instead of 0 — Redis supports up to 16 numbered databases within one instance by default, so cache keys and Celery's broker/result data never collide even though they share the same server process. This mirrors the DEBUG-driven fallback already in place for Celery's own eager-mode setting: local development runs against in-memory caching with zero external dependencies, production runs against real Redis, and it's the same if DEBUG branch pattern a contributor would already recognize from the Celery config right above it.

Where It Actually Gets Used

PlatformStatsView — the endpoint the homepage calls for question counts, student counts, and mock-test totals — was doing a handful of .count() and .aggregate() queries on every request, for numbers that only meaningfully change a few times a day:

class PlatformStatsView(APIView):
    CACHE_KEY = "content:platform_stats"
    CACHE_TTL_SECONDS = 300

    def get(self, request):
        cached = cache.get(self.CACHE_KEY)
        if cached is not None:
            return Response(cached)

        data = { ... }  # the existing aggregate queries, unchanged
        cache.set(self.CACHE_KEY, data, self.CACHE_TTL_SECONDS)
        return Response(data)

A 5-minute TTL is short enough that a genuinely stale number (say, right after a new question is added) self-corrects quickly, and long enough to absorb the actual request volume this endpoint sees without recomputing on every hit.

Testing a Cache, Not Just Code That Calls .cache.set()

The easy version of this test just checks that cache.set gets called and calls it done — which doesn't actually prove caching works, only that the code compiles. I wanted a test that proves a real cache hit:

def test_response_is_cached_across_requests(self):
    Question.objects.create(...)
    first = self.client.get(url)
    self.assertEqual(first.data["total_questions"], 1)

    Question.objects.create(...)  # a second question now exists in the DB
    second = self.client.get(url)
    self.assertEqual(second.data["total_questions"], 1)  # still 1 — proves the cache, not the DB, answered this

    cache.clear()
    third = self.client.get(url)
    self.assertEqual(third.data["total_questions"], 2)  # recompute works once the cache is empty

The middle assertion is the actual test — if caching weren't working, that request would correctly return 2, matching the database's true state. Getting 1 back is only possible if the first response actually got served from cache instead of recomputed, which is the specific thing worth verifying rather than assuming.

setUp(self): cache.clear() was also necessary — Django's test runner doesn't isolate the cache between tests the way it isolates the database in a transaction, so a stale cached value from an earlier test can silently make a later one pass or fail for the wrong reason.

Why Not django-redis

django-redis adds connection pooling controls and a few extra features (like native support for Redis Sentinel) that this project doesn't need. Django's own RedisCache backend, added in Django 5.0, covers the actual requirement — get/set/delete with a TTL, backed by a real Redis connection — without a third-party dependency whose maintenance and security posture would need separate tracking going forward.

If a hot endpoint in your Django app is doing real database work on every single request and you already have Redis running for something else, this is usually a same-day change. Get in touch.

Related Articles

Security

Rate Limiting, Token Blacklisting, and Admin MFA — Auth Hardening for Tabeer.ai

Part two of the production-readiness checklist: authentication and account security. Five of fifteen items were genuinely missing — rate limiting, real logout, admin MFA, consent records, and self-service data export/deletion — and here's exactly how each got fixed without a new dependency for most of them.

10 min read
Security

The Encryption Checklist for a Small Django Production Stack

Thirteen encryption checks, framed as 'encryption at multiple layers, not just HTTPS.' Most of the layers already existed — TLS to Postgres, client-side-encrypted backups — one item doesn't apply to this architecture at all, and two needed an AWS console check I couldn't run myself.

7 min read
Shahid Malik - AI-First Odoo Consultant

Shahid Malik

AI-First Odoo ERP Specialist

Shahid Malik is an AI-first Odoo consultant helping businesses solve complex ERP and business process challenges. His work combines Odoo consulting, process optimization, automation, integrations, migrations, and practical AI solutions to build scalable and reliable business systems.

Book a consultation for your Odoo project
Discuss Your Odoo Project