Adicionando Cache no Backend
O conceito de Cache
Primeiro vamos analisar um conceito importante: o cache. O cache é uma técnica de armazenamento temporário de dados que são frequentemente acessados. Ele é utilizado para melhorar a performance e a eficiência de um sistema.
O que temos na imagem é a descrição do processo de cache. Quando um cliente faz uma requisição para o servidor, o servidor verifica se a resposta para essa requisição está no cache. Se estiver, o servidor retorna a resposta do cache para o cliente. Se não estiver, o servidor processa a requisição e armazena a resposta no cache.
Essa abordagem traz uma série de benefícios, como a redução do tempo de resposta, a diminuição do tráfego de rede e a economia de recursos do servidor. O serviço de cache é muito utilizado em aplicações que precisam de alta performance e escalabilidade.
Diversos serviços podem ser utilizados para implementar o cache em uma aplicação. Vamos utilizar o Redis, um banco de dados em memória que é muito utilizado para cache e armazenamento de dados temporários. Extremamente importante ressaltar, o Redis tem muitas outras funcionalidades também, como armazenamento de dados em memória, suporte a estruturas de dados complexas, entre outras.
Adicionando o Redis ao nosso projeto
Pessoal conseguimos utilizar o Redis de diversas formas diferentes, mas para o nosso projeto vamos utilizar o Redis como um serviço de cache. Sua utilização pode acontecer por uma instalação local, serviço de cloud ou ainda utilizando um container Docker. Vamos trabalhar com a última opção. A imagem que vamos utilizar é a oficial do Redis, disponível no Docker Hub.
Vamos adicionar o Redis ao nosso projeto utilizando o Docker. Primeiro, vamos criar um arquivo chamado docker-compose.yml na raiz do nosso projeto. Adicione o seguint:
version: '3.8'
services:
redis:
image: redis
container_name: redis
ports:
- "6379:6379"
backend:
build: .
container_name: backend
ports:
- "8000:8000"
restart: on-failure
depends_on:
- redis
Pessoal o que vai acontecer aqui, vamos iniciar nosso backend e também vamos iniciar o Redis. O Redis vai estar disponível na porta 6379 e o backend na porta 8000. O backend vai depender do Redis, ou seja, o Redis vai ser iniciado antes do backend. Por hora, ainda não estamos utilizando o Redis no nosso backend, mas vamos fazer isso agora.
Utilizando o Redis
Pessoal aqui vai ser uma seção de utilização do Redis, para compreender como ele pode ser utilizado. Se vocês quiserem apenas ver a implementação do cache, podem pular para a próxima seção. O que é necessário fazer aqui é adicionar a dependência do Redis ao nosso projeto. Para isso, vamos instalar o pacote redis no arquivo requirements.txt.
fastapi==0.111.0
plusminus==0.7.0
redis==5.0.4
Utilizando o Redis por linha de comando
- TODO
Utilizando o Redis com Python
- TODO
Implementando o Cache no Backend
Agora vamos para nossa implementação de cache no backend. Vamos adicionar o Redis ao nosso projeto e utilizar o cache para armazenar os resultados das requisições. Primeiro, vamos adicionar o Redis ao nosso projeto. Para isso, vamos criar um arquivo chamado cache.py na pasta backend e adicionar o seguinte código:
import redis
redis_client = redis.Redis(host='redis', port=6379, db=0)
O que foi realizado aqui:
- Importamos a biblioteca
redis. - Criamos uma instância do cliente do Redis, passando o host e a porta do Redis.
Vamos utilizar agora o Redis para armazenar os resultados das requisições. Vamos adicionar o cache na rota de listagem de produtos. Para isso, vamos modificar o arquivo main.py na pasta backend e adicionar o seguinte código:
# main.py
from fastapi import FastAPI
from pydantic import BaseModel
from cache import redis_client
# Define a classe de modelo base utilizada
class Pedido(BaseModel):
expressao:str
app = FastAPI()
@app.post("/evaluate")
async def evaluate(pedido: Pedido):
# Verifica se a requisição já foi feita
resultado = redis_client.get(pedido.expressao)
if resultado:
return {"resultado": resultado.decode()}
# Calcula o resultado da expressão
try:
resultado = eval(pedido.expressao)
# Armazena o resultado no cache
redis_client.set(pedido.expressao, resultado)
return {"resultado": resultado}
except Exception as e:
# Em caso de erro, retorna uma mensagem de erro, mas armazena ela no cache também
redis_client.set(pedido.expressao, f"Erro na expressão informada: {e}")
return {"resultado": f"Erro na expressão informada: {e}"}
O que foi realizado aqui:
- Importamos a biblioteca
redis. - Criamos uma instância do cliente do Redis, passando o host e a porta do Redis.
- Modificamos a rota de listagem de requisições para verificar se ela já foi feita. Se sim, retorna o resultado do cache. Se não, calcula o resultado da expressão e armazena no cache.
Agora, vamos testar a nossa aplicação. Para isso, vamos iniciar o nosso projeto utilizando o Docker. Abra o terminal na raiz do projeto e execute o seguinte comando:
docker-compose up --build
Agora, abra o navegador e acesse a URL http://localhost:8000/docs. Você verá a documentação da nossa API. Vamos testar a nossa rota de listagem de produtos. Clique no botão Try it out e insira a expressão 2+2. Clique em Execute e veja o resultado. Agora, insira a expressão 2+2 novamente e clique em Execute. Você verá que o resultado foi retornado do cache.
Para verificar se o cache está funcionando, abra o terminal e execute o seguinte comando:
docker exec -it redis redis-cli
Agora para pegar todos os valores armazenados no cache, execute o seguinte comando:
keys "*"
Você vai conseguir ver os dados que foram armazenados no cache.
Mas Murilo, esse cache não vai ficar ai enquanto os servidor estiver rodando? E se eu quiser limpar o cache? Ou ainda se eu quiser invalidar o cache após um valor específico de tempo?
Vamos lá pessoal, essas são perguntas muito importantes. O Redis tem uma série de funcionalidades que podem ser utilizadas para manipular o cache. Vamos ver algumas delas:
Expiração do Cache
Para definir um tempo de expiração para um valor no cache, podemos utilizar o comando EXPIRE. Vamos adicionar um tempo de expiração de 10 segundos para os valores armazenados no cache. Para isso, vamos o arquivo fonte da nossa aplicação e adicionar o seguinte código:
# main.py
from fastapi import FastAPI
from pydantic import BaseModel
from cache import redis_client
# Define a classe de modelo base utilizada
class Pedido(BaseModel):
expressao:str
app = FastAPI()
@app.post("/evaluate")
async def evaluate(pedido: Pedido):
# Verifica se a requisição já foi feita
resultado = redis_client.get(pedido.expressao)
if resultado:
return {"resultado": resultado.decode()}
# Calcula o resultado da express ão
try:
resultado = eval(pedido.expressao)
# Armazena o resultado no cache com tempo de expiração de 10 segundos
redis_client.setex(pedido.expressao, resultado, 10)
return {"resultado": resultado}
except Exception as e:
# Em caso de erro, retorna uma mensagem de erro, mas armazena ela no cache também
redis_client.setex(pedido.expressao, f"Erro na expressão informada: {e}", 10)
return {"resultado": f"Erro na expressão informada: {e}"}
O que aconteceu aqui foi que utilizamos o método setex do Redis para armazenar o valor no cache com um tempo de expiração de 10 segundos. Dessa forma, o valor será automaticamente removido do cache após 10 segundos. Essa funcionalidade é muito útil para armazenar valores temporários no cache. Ela evita a necessidade de limpar o cache manualmente e garante que os valores antigos sejam removidos automaticamente.
Agora se for necessário limpar todo o cache, podemos utilizar o comando FLUSHALL. Para isso, execute o seguinte comando no terminal:
# Para conectar no container do Redis
docker exec -it redis redis-cli
# Para limpar o cache
FLUSHALL
Pessoal espero que assim vocês consigam compreender como o cache pode ser utilizado e como ele pode ser manipulado. O Redis é uma ferramenta muito poderosa e versátil, que pode ser utilizada de diversas formas. Nesse encontro, utilizamos o Redis como um serviço de cache, mas ele pode ser utilizado para muitas outras finalidades, como armazenamento de dados em memória, suporte a estruturas de dados complexas, entre outras.