O que é cache?
Analogia da geladeira: quando você quer um copo d'água, vai na geladeira (rápido). Quando a água acaba, você vai no mercado (lento). A geladeira é o cache: tem menos coisa, mas está perto e é rápido.
No backend, banco é o mercado e Redis é a geladeira. Você guarda no Redis aquilo que:
- É lido muito mais do que escrito.
- É caro de calcular ou de buscar (query pesada, chamada externa).
- Pode estar um pouco desatualizado sem o mundo cair.
Quando usar (e quando NÃO usar)
Use cache quando:
- Listagens populares (top 10 produtos, ranking).
- Dados de configuração que mudam pouco.
- Resultados de cálculos pesados.
- Sessão de usuário, rate limiting, locks distribuídos.
NÃO use cache quando:
- O dado precisa ser sempre exato e atual (saldo bancário no momento da transação).
- A complexidade de manter o cache consistente é maior que o ganho.
- O dado é único por usuário e raramente lido de novo.
Existem dois problemas difíceis em computação: invalidar cache e nomear coisas. Cache só parece simples até a primeira vez que alguém vê dado desatualizado em produção.
Padrão Cache-Aside (o mais comum)
1. Pede ao service: "me dá user 123"
2. Service olha o Redis. Tem? Devolve.
3. Não tem? Vai no banco, pega o dado.
4. Coloca o dado no Redis (com TTL).
5. Devolve.
Implementação em Go usando go-redis:
go-redis/v9 requer Go 1.24 ou superior. Certifique-se que seu go.mod declara go 1.24 ou acima antes de instalar.package user
import (
"context"
"encoding/json"
"errors"
"time"
"github.com/redis/go-redis/v9"
)
type CachedRepo struct {
next Repository // o repositório "real" (postgres)
cache *redis.Client
ttl time.Duration
}
func NewCachedRepo(next Repository, cache *redis.Client) *CachedRepo {
return &CachedRepo{next: next, cache: cache, ttl: 5 * time.Minute}
}
func (r *CachedRepo) FindByID(ctx context.Context, id int64) (User, error) {
key := fmt.Sprintf("user:%d", id)
// 1. Tenta o cache primeiro.
if data, err := r.cache.Get(ctx, key).Bytes(); err == nil {
var u User
if err := json.Unmarshal(data, &u); err == nil {
return u, nil // cache hit, voltamos rapidinho
}
}
// 2. Cache miss: busca no banco.
u, err := r.next.FindByID(ctx, id)
if err != nil {
return User{}, err
}
// 3. Guarda no cache para próxima.
// Erro aqui NÃO derruba a request — log e segue.
if data, err := json.Marshal(u); err == nil {
_ = r.cache.Set(ctx, key, data, r.ttl).Err()
}
return u, nil
}Note como o CachedRepo implementa a mesma interface Repository. O service nem fica sabendo que tem cache no meio do caminho. Isso é decoração, e é uma das técnicas mais elegantes de arquitetura.
Cuidado com invalidação
Quando você atualizar ou deletar um usuário, lembre de apagar a chave do cache correspondente, ou alguém vai ver dado velho.
func (r *CachedRepo) Update(ctx context.Context, u User) error {
if err := r.next.Update(ctx, u); err != nil {
return err
}
_ = r.cache.Del(ctx, fmt.Sprintf("user:%d", u.ID)).Err()
return nil
}