Cache como estrategia de diseno de sistemas para APIs lentas y costosas
Como usar cache para desacoplar nuestro sistema de APIs externas lentas o costosas, reducir latencia y mejorar la experiencia del usuario.
El problema
Si cada request del usuario depende de una API externa lenta o costosa, nuestra aplicacion hereda ese problema.
La idea
No repetir trabajo innecesario. Si una respuesta ya fue obtenida antes, la guardamos y la reutilizamos sin volver a pedirla a la fuente original cada vez.
Aclaracion importante
Cache no es Redis. Cache es la tecnica o estrategia de guardar un dato que ya obtuvimos o calculamos para reutilizarlo despues. Redis es solo una posible implementacion.
El problema: depender de APIs externas lentas y costosas
En muchos sistemas usamos APIs externas para obtener data que nuestra aplicacion necesita mostrar o procesar. El problema es que no todas las APIs responden rapido. Algunas pueden tardar varios segundos, y en ciertos casos incluso cada request puede ser caro.
Si cada vez que un usuario abre una pantalla o hace una consulta nuestra aplicacion depende directamente de esa API, entonces arrastramos ese problema a toda la experiencia del sistema.
Desde el punto de vista de diseno de sistemas, esto genera una dependencia fuerte con un servicio externo: nuestro tiempo de respuesta, nuestro costo y hasta nuestra disponibilidad pasan a depender de algo que no controlamos.
En ese escenario podemos terminar teniendo:
- Respuestas lentas
- Mala experiencia de usuario
- Mayor costo por consumo de la API
- Mas riesgo de errores o timeouts
- Un sistema demasiado acoplado a un proveedor externo
Ejemplo: calcular distancias entre paises
Supongamos que tenemos una aplicacion que necesita calcular distancias entre paises usando una API externa. Esa API devuelve el dato correcto, pero tarda varios segundos en responder.
Si un usuario consulta la distancia entre Argentina y Brasil, hacemos una llamada externa. Pero si despues otro usuario hace exactamente la misma consulta, no tiene sentido volver a pagar el costo completo ni volver a esperar el mismo tiempo por un dato que probablemente ya conocemos.
Ahi aparece una pregunta clasica de diseno de sistemas: como usamos la data externa sin depender de hacer requests directos todo el tiempo.
La solucion: usar cache para desacoplarnos
La estrategia mas simple y efectiva en este caso es cachear resultados. Es decir: si ya obtuvimos una respuesta antes, la guardamos y la reutilizamos.
De esa forma, la API externa sigue existiendo como fuente de datos, pero deja de estar en el camino critico de todos los requests del usuario.
- El primer request paga el costo
- Los siguientes requests reutilizan el dato guardado
- Reducimos latencia
- Reducimos costo
- Desacoplamos parcialmente nuestro sistema de la API externa
Que significa cache realmente
Es importante aclarar que cache no significa necesariamente usar Redis ni una base de datos en memoria en particular. Cache es, ante todo, una tecnica o una estrategia: guardar un dato que ya obtuvimos o calculamos para reutilizarlo despues sin tener que volver a pedirlo a la fuente original.
Eso se puede implementar de distintas maneras: con un diccionario en memoria, con Redis, con una base de datos, con archivos o con cualquier mecanismo que permita recuperar rapidamente un resultado ya conocido.
En otras palabras, Redis es una posible implementacion de cache, pero el concepto de cache existe aunque no usemos Redis. Lo importante no es la herramienta puntual, sino el comportamiento: evitar requests innecesarios, reducir latencia y desacoplar nuestro sistema de una dependencia externa lenta o costosa.
Que pasa si el dato no esta en cache
Si el valor todavia no fue calculado, entonces si hacemos la llamada a la API externa. Pero una vez que obtenemos la respuesta, la guardamos para no repetir el mismo trabajo en el futuro.
En otras palabras: la API externa se consulta solo cuando realmente hace falta.
Implementacion simple en FastAPI
Vamos a resolverlo con FastAPI usando un cache simple en memoria con un diccionario. Para un ejemplo educativo esto alcanza. En produccion, podria reemplazarse por Redis u otra solucion compartida.
from fastapi import FastAPI
import time
app = FastAPI()
# Cache simple en memoria
cache = {}
def slow_external_api(from_country, to_country):
# Simulamos una API lenta
time.sleep(3)
return 2900
def build_key(from_country, to_country):
# Normalizamos la clave para evitar duplicados
countries = sorted([from_country.lower(), to_country.lower()])
return f"{countries[0]}-{countries[1]}"
@app.get("/distance")
def get_distance(from_country: str, to_country: str):
key = build_key(from_country, to_country)
# 1. Buscar primero en cache
if key in cache:
return {
"distance": cache[key],
"source": "cache"
}
# 2. Si no existe, llamar a la API externa
distance = slow_external_api(from_country, to_country)
# 3. Guardar el resultado para futuros requests
cache[key] = distance
return {
"distance": distance,
"source": "external_api"
}
Mejoras para un sistema real
| Mejora | Por que importa |
|---|---|
| Redis | Permite un cache compartido entre multiples instancias de la aplicacion |
| TTL | Evita servir datos demasiado viejos definiendo un tiempo de expiracion |
| Persistencia | Permite conservar resultados utiles aunque la aplicacion se reinicie |
| Cache distribuido | Ayuda a escalar horizontalmente sin inconsistencias entre servidores |
| Background refresh | Permite refrescar datos en segundo plano sin bloquear al usuario |
| Fallbacks | Permite responder con datos previos si la API externa falla temporalmente |
Conclusion
Cuando una API externa es lenta, costosa o poco confiable, no conviene ponerla en el camino critico de todos los requests. Desde el diseno de sistemas, una estrategia de cache nos permite seguir usando esa fuente de datos, pero reduciendo la dependencia directa.
En resumen: usar la API para obtener la data, pero no depender de ella cada vez que mostramos esa data al usuario.