Python aiocache Caching — Core Concepts
What aiocache Provides
aiocache is an async-native caching library that provides:
- Multiple backends — in-memory, Redis, Memcached
- Decorator-based caching — cache function results with a single decorator
- Serialization — automatic conversion between Python objects and cache storage formats
- Key building — automatic cache key generation from function arguments
- TTL support — time-based cache expiration
- Plugins — extensible hooks for logging, metrics, and custom behavior
Basic Usage
The cached Decorator
The simplest way to use aiocache:
from aiocache import cached
@cached(ttl=300) # Cache for 5 minutes
async def get_user_profile(user_id: int):
# This only runs if the result isn't cached
return await database.fetch_user(user_id)
# First call: hits the database
profile = await get_user_profile(42)
# Second call within 5 minutes: returns cached result instantly
profile = await get_user_profile(42)
Direct Cache Usage
For more control, use cache objects directly:
from aiocache import Cache
cache = Cache(Cache.MEMORY) # or Cache.REDIS, Cache.MEMCACHED
# Set a value
await cache.set("user:42", {"name": "Alice", "role": "admin"}, ttl=600)
# Get a value
user = await cache.get("user:42")
# Check existence
exists = await cache.exists("user:42")
# Delete
await cache.delete("user:42")
# Clear everything
await cache.clear()
Backends
In-Memory (SimpleMemoryCache)
from aiocache import Cache
cache = Cache(Cache.MEMORY)
Fast, no external dependencies, but data is lost when the process restarts and isn’t shared between processes.
Redis
from aiocache import Cache
cache = Cache(
Cache.REDIS,
endpoint="localhost",
port=6379,
namespace="myapp"
)
Shared across processes and servers. Survives restarts. Requires a running Redis instance.
Memcached
from aiocache import Cache
cache = Cache(
Cache.MEMCACHED,
endpoint="localhost",
port=11211,
namespace="myapp"
)
Similar to Redis for pure caching, but with a simpler data model.
Serialization
aiocache needs to convert Python objects to a storable format:
from aiocache import Cache
from aiocache.serializers import JsonSerializer, PickleSerializer
# JSON — human-readable, cross-language compatible
cache = Cache(Cache.REDIS, serializer=JsonSerializer())
# Pickle — handles any Python object, but Python-only
cache = Cache(Cache.REDIS, serializer=PickleSerializer())
JSON is safer and more portable. Pickle handles complex objects (custom classes, dataclasses) but introduces security risks with untrusted data.
Cache Key Building
The @cached decorator generates cache keys from function arguments:
@cached(ttl=300)
async def search_products(category: str, min_price: float):
return await db.search(category, min_price)
# Generates key: "search_products('electronics', 50.0)"
await search_products("electronics", 50.0)
# Different key: "search_products('clothing', 25.0)"
await search_products("clothing", 25.0)
Custom key builders:
def user_key_builder(func, *args, **kwargs):
return f"user:{args[0]}"
@cached(ttl=300, key_builder=user_key_builder)
async def get_user(user_id: int):
return await db.fetch_user(user_id)
Cache Invalidation
Time-Based (TTL)
The simplest strategy — data expires after a set duration:
@cached(ttl=60) # Stale after 1 minute
async def get_stock_price(ticker: str):
return await market_api.get_price(ticker)
Manual Invalidation
When you know data has changed:
cache = Cache(Cache.REDIS)
async def update_user(user_id, new_data):
await db.update_user(user_id, new_data)
await cache.delete(f"user:{user_id}") # Invalidate cached version
Pattern-Based Deletion
Clear all keys matching a pattern (Redis backend):
# Clear all cached data for a specific user
# Note: requires iterating keys, use sparingly
keys = await cache.raw("keys", "myapp:user:42:*")
for key in keys:
await cache.delete(key)
Common Misconception
“Caching always improves performance.” Caching adds complexity and can introduce stale data bugs. If your data changes frequently and stale results cause problems (financial data, real-time inventory), aggressive caching does more harm than good. Cache when the cost of a miss (slow query) significantly outweighs the cost of staleness.
One thing to remember: aiocache provides async-native caching with decorator support, multiple backends (memory, Redis, Memcached), and automatic key generation. Use TTL-based expiration as your baseline strategy, manual invalidation for data consistency, and pick your backend based on whether you need cross-process sharing (Redis) or just per-process speed (memory).
See Also
- Python Actor Model Why treating each piece of your program like a person with their own mailbox makes concurrency way less scary.
- Python Aiofiles Async Io aiofiles lets your async Python program read and write files without freezing — because normal file operations secretly block everything.
- Python Aiohttp Understand Aiohttp through an everyday analogy so Python behavior feels intuitive, not random.
- Python Anyio Portability AnyIO lets your async Python code work with any async library — write once, run on asyncio or Trio without changes.
- Python Anyio Understand Anyio through an everyday analogy so Python behavior feels intuitive, not random.