Redis Cache
The Redis cache adapter provides persistent, distributed caching with TTL support. It is the recommended cache provider for production environments.
Overview
The Redis cache is designed for:
- Production environments requiring persistent cache storage
- Multi-process deployments where cache must be shared across workers
- TTL-based expiry with millisecond precision
- Pattern-based operations for bulk retrieval and cleanup
Installation
pip install "protean[redis]"
# Or install the Redis package separately
pip install "redis>=8.0.0,<8.2.0"
Configuration
[caches.default]
provider = "redis"
URI = "redis://localhost:6379/2"
TTL = 300
Configuration Options
| Option | Default | Description |
|---|---|---|
provider |
Required | Must be "redis" for the Redis cache |
URI |
Required | Redis connection string |
TTL |
300 |
Default time-to-live in seconds. See below. |
What counts as a TTL
A TTL must be a positive, finite number of seconds. It may also be a string holding one, so an environment variable works:
[caches.default]
provider = "redis"
TTL = "${CACHE_TTL|3600}"
Substitution runs over already-parsed TOML strings, so a TTL sourced from the environment arrives as a string; that is why the string form is accepted rather than merely tolerated.
Anything else, including 0, a negative, nan or inf, raises a
ConfigurationError naming the cache.
The same rule applies wherever a TTL is passed: cache.add(projection, ttl=...)
and cache.set_ttl(key, ttl) take the same shapes and reject the same ones.
Omitting the TTL (or passing an empty string) uses the cache's configured TTL.
Connection String Format
redis://[[username]:[password]@]host[:port][/database]
# Examples:
redis://localhost:6379/2 # Local Redis, database 2
redis://:password@redis.prod:6379 # With password
Tip
Use a different Redis database number (the /2 suffix) for the cache than
for the broker to keep concerns separated.
Usage
Projections are automatically stored in the cache when a projector writes to a cache-backed projection. You can also interact with the cache directly. This example needs a running Redis server:
# fragment
import os
from protean import Domain
from protean.fields import Float, Identifier
domain = Domain(name="Orders")
domain.config["caches"]["default"] = {
"provider": "redis",
"URI": os.environ.get("REDIS_URL", "redis://localhost:6379/2"),
"TTL": 300,
}
@domain.projection(cache="default")
class OrderSummary:
order_id: Identifier(identifier=True)
total: Float()
domain.init(traverse=False)
with domain.domain_context():
# Get the cache that holds the projection
cache = domain.cache_for(OrderSummary)
# Check connectivity
reachable = cache.ping() # True if Redis is reachable
# Store a projection; its key is "order_summary:::ord-123"
cache.add(OrderSummary(order_id="ord-123", total=42.5))
# Retrieve a cached projection
entry = cache.get("order_summary:::ord-123")
# Count cached entries
count = cache.count("order_summary:::*")
# Set a custom TTL on a specific key
cache.set_ttl("order_summary:::ord-123", ttl=600) # 10 minutes
remaining = cache.get_ttl("order_summary:::ord-123")
# Remove all entries
cache.flush_all()
domain.cache_for(OrderSummary) returns the cache that holds the projection.
Each entry's key is the projection's name in snake case, then :::, then its
identifier. get_ttl returns the seconds left as a float, so right after
set_ttl(..., ttl=600) it returns a value just under or equal to 600.0.
Limitations
- Requires Redis Server: Redis must be installed and running. Use
make upto start Protean's Docker-based development services. - Memory Bound: Redis stores data in memory. Ensure sufficient RAM for your cache working set.
- No Complex Queries: The cache API supports key-based and pattern-based lookups only. For complex queries, use a database-backed projection instead.
Related pages
- Learn about projections
- Understand cache configuration
- Explore the Redis broker for message streaming