Understanding `@redis_cache()` in Frappe

Understanding @redis_cache() in Frappe

In Frappe, some functions run again and again — even when their output rarely changes.

Examples:

  • Getting published Web Forms
  • Counting records in a table
  • Fetching a list of DocTypes
  • Running heavy calculations

If every request hits the database, the system becomes slow.

So Frappe gives us a very powerful tool:

@redis_cache() — Cache Function Output in Redis

Redis is an in-memory super-fast database.

The @redis_cache() decorator is a function wrapper that intercepts calls to your function, checks Redis for a cached result, and only executes the original function if the cache is empty

So instead of calling your function again and again, Frappe stores the function result in Redis and returns it instantly.


When Should You Use @redis_cache()?

Use it when:

  1. The function result does not change frequently

Example: list of Web Forms, DocType metadata.

  1. The function does something heavy

Example: complex filters, multiple joins, big queries.

  1. Many users are calling the same function repeatedly

Example: page view count, homepage queries.

  1. You want to share cached data with all workers

Unlike @site_cache, this is global.


When NOT to Use @redis_cache()

Do not use it for:

  • Functions that must always show fresh data
  • Functions that modify data
  • Functions returning non-picklable objects
  • Functions with constantly changing results

How @redis_cache() Works Internally?

The @redis_cache() decorator wraps your function so that:

  • It checks Redis before running the function
  • If a cached value exists → returns it
  • Otherwise → runs the function → stores the result → returns it

When Python sees this:

@redis_cache(ttl=3600)
def my_function(...):
   ...

It executes redis_cache() and sets up things:

Step 1: Frappe generates a unique key

Combination of:

  1. Function name
  2. Module path
  3. Function arguments

Step 2: Checks Redis

If cached value exists → return immediately.

Step 3: If not cached

Run your function normally.

Step 4: Store result in Redis

With expiry (TTL = seconds).

Step 5: Next time, return cached value

No need to run the function again.


What Does ttl Mean?

TTL = Time To Live

Example:

  • ttl=3600 → cache stays for 1 hour
  • ttl=60 * 5 → 5 minutes
  • ttl=None → infinite cache

What about user-specific cache (user=True)?

Sometimes function results depend on logged-in user.

Example:
Engineers use a different calculation than Managers.

When you add:

@redis_cache(user=True)

Frappe prefixes cache key with:

user:<username>:

So:

  • Engineer gets their own cache
  • Manager gets their own cache

They don’t overlap.


What about shared cache (shared=True)?

By default, Frappe prefixes keys with site name.

If you set:

@redis_cache(shared=True)

Then same Redis cache is used across all sites in a multi-tenant setup.

Very useful for:

  • Global metadata
  • Shared microservices
  • API-level caching

Example — Cache page view count for 5 minutes

@redis_cache(ttl=5 * 60)
def get_page_view_count(path):
    return frappe.db.count("Web Page View", {"path": path})

Counting thousands of page views is expensive, hence we Cache it for 5 minutes.


Clearing Cache (Very Important)

Every cached function automatically gets:

your_function.clear_cache()

Example:

get_published_web_forms.clear_cache()

Use it when:

  • You update or publish a web form
  • You want fresh data immediately
  • Something in cached data changed manually

Comparing All Cache Decorators

Decorator Stored In Valid For Shared Across Workers Use Case
@request_cache In-memory Single request No Functions repeated in same request
@site_cache In-process Worker lifetime No Static metadata
@redis_cache Redis TTL-based Yes DB-heavy functions

Redis is basically an extremely fast, in-memory key–value database that stores everything in RAM instead of disk, which makes it almost 100x faster than normal databases.

you can think of it like a giant Python dictionary that lives on a server and can be accessed by all your workers, all your processes, and across all requests, so if you store something there, everyone can retrieve it instantly without recalculating it.

It supports strings, lists, sets, hashes, and even sorted sets, but in Frappe we primarily use it as a key–value store to cache expensive function results.

Because Redis keeps data directly in memory, reading and writing are extremely fast, but the trade-off is that memory is limited and expensive, so you must use it wisely.

Redis also supports TTL (time-to-live), meaning values expire automatically after a certain time, making it perfect for caching things that change periodically.

So when Frappe’s @redis_cache() decorator stores your function’s result in Redis, the next time your code calls that function, instead of running the Python logic or hitting the database again, Frappe directly pulls the cached value from Redis, returning it instantly; this reduces CPU, avoids duplicate DB queries, lightens workload on workers, and dramatically speeds up the system. In simple words: Redis is your global super-fast memory store that turns expensive repeated work into a single quick lookup.

In One Line — When Should You Use It?

Use @redis_cache() when your function is expensive, stable, and called frequently.


Resources

4 Likes