Adicionei Redis ao Deploy Tracker usando o padrão cache-aside. O objetivo era simples: evitar consultas repetidas ao PostgreSQL quando os mesmos dados fossem solicitados várias vezes.
O problema
- Toda vez que um
GETbusca dados no PostgreSQL, existe um custo. Se os dados mudaram, tudo bem: faz sentido consultar novamente. Mas se os dados não mudaram? Aí existe trabalho repetido, e o Redis entra para resolver isso.
Por que Redis
- Uma variável Python não tem expiração automática. Ela também desaparece quando o processo reinicia e não é compartilhada entre múltiplos workers. Redis resolve esses problemas porque é um serviço separado e consegue expirar chaves automaticamente com TTL.
- Redis armazena dados em RAM, o que torna a resposta mais rápida, porque buscar na memória é muito mais rápido do que buscar no disco.
- Quando você é iniciante, é comum pensar: "Por que eu usaria um message broker/database como Redis só para deixar um pouco mais rápido?" Mas lendo "Grokking Algorithms", percebi uma coisa: se você é iniciante, não olhe só para o tamanho atual do seu projeto. Pegue uma parte dele e imagine: "E se isso tivesse 1.000 usuários? Ou 1.000.000? Continuaria rodando bem ou ficaria lento?"
- Cache não é só deixar uma requisição mais rápida. É evitar trabalho repetido quando a resposta já é conhecida.
Como implementei
1. Para implementar Redis, criei o cache.py, responsável pelas operações de cache.
def get_cache(key):
value = r.get(key)
if value is not None:
new_value = json.loads(value)
return new_value
return None
def to_dict(obj):
return {c.name: getattr(obj, c.name) for c in obj.__table__.columns}
def set_cache(key, data):
data = [to_dict(item) for item in data]
value = json.dumps(data, default=str)
r.set(key, value, ex=300)
def delete_cache(key):
r.delete(key)2. GET /applications antes e depois do Redis.
Sem Redis, apenas banco de dados:
@router.get("/applications")
def list_applications(db: Session = Depends(get_session)):
return db.query(Application).all()Com Redis integrado:
@router.get("/applications")
def list_applications(db: Session = Depends(get_session)):
cached = get_cache("applications_all")
if cached:
return cached
applications = db.query(Application).all()
set_cache("applications_all", applications)
return applicationsImportante: quando os dados mudam, o cache precisa ser invalidado para evitar resultados desatualizados. Por exemplo, depois de criar ou atualizar uma aplicação, preciso chamar delete_cache("applications_all").
Invalidação de cache — quando os dados mudam com POST/PATCH/DELETE
Invalidação de cache é um problema chato. Ela acontece porque os dados no banco mudam, mas os dados no cache não. Isso cria inconsistência, também chamada de stale data.
- Banco de dados -> dados originais
- Cache -> cópia dos dados
Para resolver isso, apaguei a chave em cache toda vez que o dado original mudava.
Então depois de POST, PATCH ou DELETE, a API chama:
delete_cache("applications_all")O próximo GET /applications vira um cache miss, consulta o PostgreSQL novamente e salva o resultado atualizado no Redis.
O bug que me surpreendeu
Quando implementei Redis, tentei salvar objetos SQLAlchemy diretamente no cache.
Para minha surpresa, a API retornou 500 Internal Server Error.
No começo, perguntei: "O que fiz errado?" Mas a pergunta mais importante era: "Como eu identifico o bug?"
Nesse caso, minha IDE não mostrou nada útil. Então fui olhar os logs do terminal.
Essa foi uma lição importante: às vezes o erro não aparece no editor. A aplicação está rodando, o código parece certo, mas a explicação real está nos logs.
Depois de ler os logs, encontrei o problema: json.dumps não conseguia serializar objetos SQLAlchemy.
Foi aí que entendi o erro. Redis não estava armazenando meus objetos Python diretamente. Nesta implementação, eu precisava converter meus models SQLAlchemy em dicionários antes de salvar no cache.
Então criei o helper to_dict():
def to_dict(obj):
return {c.name: getattr(obj, c.name) for c in obj.__table__.columns}Meu segundo problema foi com created_at. Eu uso datetime.datetime para salvar a data, mas json.dumps() só entende tipos como:
dict
list
str
int
float
bool
NoneEntão como json.dumps() transforma um objeto do módulo datetime em string?
Para resolver, usei:
value = json.dumps(data, default=str)Isso basicamente significa: se encontrar um objeto que não sabe serializar, converta para string.
Por causa disso, no Deploy Tracker o created_at vira algo como "2026-04-19 23:59:59" em formato de string.
Importante: Redis não estava exigindo JSON especificamente. Eu usei JSON como formato para transformar meus dados Python em uma string antes de salvar no Redis.
O que aprendi
- PostgreSQL -> disco -> mais lento | Redis -> RAM -> mais rápido
- Velocidade pode parecer opcional no começo, mas com muitos dados até um pequeno atraso faz diferença.
- Cache parece simples, mas cria uma nova responsabilidade: manter o cache consistente com o banco.
- O banco de dados é a fonte da verdade. Redis guarda uma cópia temporária.
- Logs fazem parte do debugging. Minha IDE não mostrou o problema real, mas os logs explicaram por que a API retornava
500. - Redis armazena strings/bytes.