A história de um cache que não cacheava — e o que aprendemos com isso
(TL;DR): Atualmente, nossa API utiliza Grape integrado ao grape-rails-cache para cachear respostas de endpoints. Porém, a estratégia implementada não estava efetivamente armazenando ou reutilizando o cache das requisições, resultando em chamadas repetitivas e desnecessárias às APIs externas e consultas ao banco de dados.
⚠️ Problema Identificado
1. Implementação atual (seguindo padrões da lib)
resources :posts do desc "Return a post" get ":id" do post = Post.find(params[:id]) cache(key: "api:posts:#{post.id}", etag: post.updated_at, expires_in: 2.hours) do post # post.extend(PostRepresenter) etc, any code that renders response end end endend2. Problema técnico
Seguindo a documentação da lib, realizamos a chamada da Interaction/Query/Request antes de abrir o bloco cache.
Isso faz com que todas as vezes, a chamada da Interaction/Query/Request seja realizada, o que faz o método cache do Grape não armazenar o resultado do bloco como esperado — o que significa que o conteúdo é recalculado a cada requisição. Na realidade estamos cacheando apenas a serialização da resposta e não a consulta ao banco…
3. Consequência
- Nenhum ganho real de performance.
- Chamadas redundantes a APIs externas e/ou banco de dados
- Maior tempo de resposta.
- Maior custo de processamento e latência percebida pelos clientes.
✅ Estratégia Corrigida
A implementação correta envolve utilizar as chamadas dentro do bloco cache, garantindo o armazenamento e reutilização dos dados:
resources :resource do desc "Returns a response" get "/" do cache(key: "api:resource:xx", expires_in: 2.hours) do response = Resource::FetchExternalResource.call response end end endendO ponto aqui é quando precisamos cachear uma query ou chamada externa, não apenas uma entidade. Não estamos lidando com HTTP Cache diretamente, mas sim com Server Side Cache. A documentação da lib não deixa isso explícito, o que foi meio que um missdirect, que fez com que o time implementasse sempre dessa forma, deixando as partes pesadas fora do bloco da cache.
Benefícios:
- Cache real.
- Redução de chamadas externas.
- Redução de latência média.
- Diminuição de carga no servidor e banco de dados.
📊 Métricas (Datadog) - Exemplo real
Antes da mudança:

Depois da mudança:
Nem todas as requests vão ao banco de dados
obs: o tempo de expiração do cache do exemplo é curto de 3 min. Então a porcentagem ainda será elevada.

Porcentagem de tempo de espera
Repare que o tempo de espera do endpoint em relação a consultas ao banco começa a diminuir após a mudança, e por consequência o uso do cache começa a aumentar - de fato começa a ser usado.


Conclusão
A correção da estratégia de cache trouxe ganhos expressivos de performance e estabilidade, reduzindo tempo de resposta, carga nos serviços externos e custos operacionais.
Essa iniciativa reforça a importância de auditar implementações de caching e não confiar 100% na documentação de libs e entender se os casos de uso que ela expõe na documentação são os mesmos que queremos aplicar.
Acompanhar após as implementações durante um período para entender se a estratégia de cache está de fato sendo efetiva.