Stack: FastAPI · Docker Compose · fastapi-cache2 · Redis 7

¿Por qué Redis?
Sin caché, cada petición va a la base de datos aunque los datos no hayan cambiado. Con Redis, la primera llamada consulta la BD y guarda el resultado. Las siguientes peticiones devuelven Redis directamente, sin tocar la BD.
| Escenario | Sin Redis | Con Redis |
|---|---|---|
| GET /libros (1ª vez) | ~80ms (BD) | ~80ms (BD) |
| GET /libros (2ª vez) | ~80ms (BD) | ~2ms (caché) |
| GET /categorias x 100 | 8.000ms total | ~2ms total |
1. Docker Compose
Añade el servicio Redis y actualiza tu API para que dependa de él.
Añade el servicio Redis
redis_bb:
image: redis:7-alpine
restart: unless-stopped
container_name: redis_bb
networks:
- docker-network
Actualiza el servicio de tu API
bb_back_api:
...
depends_on:
mariadb:
condition: service_healthy
redis_bb: # ← añade esto
condition: service_started
Levanta los contenedores
docker compose up -d
Verifica que Redis está corriendo:
docker ps | grep redis
2. Dependencias Python
Añade a requirements.txt:
redis==5.2.1 fastapi-cache2==0.2.2
Reconstruye el contenedor:
docker compose build docker compose up -d
3. Configuración en main.py
Inicializa la conexión a Redis al arrancar la app:
from fastapi import FastAPI
from fastapi.responses import ORJSONResponse
from fastapi_cache import FastAPICache
from fastapi_cache.backends.redis import RedisBackend
from redis import asyncio as aioredis
app = FastAPI(
title="API",
description="API REST",
default_response_class=ORJSONResponse,
)
@app.on_event("startup")
async def startup():
redis = aioredis.from_url("redis://redis_bb:6379")
FastAPICache.init(RedisBackend(redis), prefix="bb-cache")
Nota: El nombre del host
redis_qbbes el nombre del servicio en docker-compose.yml, nolocalhost.
4. Cachear endpoints
Caso A — TTL corto (más simple)
Ideal para datos que cambian poco. La caché expira sola, sin código extra.
from fastapi_cache.decorator import cache
@app.get("/categorias")
@cache(expire=300) # refresca cada 5 minutos
async def get_categorias():
return await db.fetch_all("SELECT * FROM categorias")
Caso B — Invalidación manual (más preciso)
Ideal para datos que cambian y necesitas que se refleje al instante.
Marca el endpoint con un namespace:
@app.get("/libros")
@cache(expire=3600, namespace="libros") # 1 hora, pero se puede limpiar
async def get_libros():
return await db.fetch_all("SELECT * FROM libros")
Limpia la caché cuando añaden o editan un libro:
from fastapi_cache import FastAPICache
@app.post("/libros")
async def crear_libro(libro: LibroSchema):
await db.execute(insert_query, libro.dict())
# Invalida la caché al modificar datos
await FastAPICache.clear(namespace="libros")
return {"status": "ok"}
5. ¿Qué estrategia usar?
| Situación | Estrategia recomendada |
|---|---|
| Categorías, géneros, editoriales | TTL largo (1 hora+) |
| Listado de libros general | TTL corto (1-5 min) o invalidación manual |
| Búsquedas y filtros | TTL corto (30-60 seg) |
| Perfil de usuario | Sin caché (datos personales) |
| Datos en tiempo real | Sin caché |
6. Verificar que funciona
Haz dos peticiones al mismo endpoint y compara los tiempos de respuesta en los logs. La segunda debe ser notablemente más rápida.
También puedes conectarte a Redis y ver las claves guardadas:
docker exec -it redis_bb redis-cli > KEYS * > TTL bb-cache:libros

Ingeniero en Informática, Investigador, me encanta crear cosas o arreglarlas y darles una nueva vida. Escritor y poeta. Más de 20 APPs publicadas y un libro en Amazon.