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:
- The function result does not change frequently
Example: list of Web Forms, DocType metadata.
- The function does something heavy
Example: complex filters, multiple joins, big queries.
- Many users are calling the same function repeatedly
Example: page view count, homepage queries.
- 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:
- Function name
- Module path
- 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.