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).

pythonasynccachingaiocache

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.