Resposta rápida: para um projeto Go novo em que Valkey é o servidor escolhido, github.com/valkey-io/valkey-go é uma opção natural por oferecer command builder, auto pipelining e client-side caching. Se a aplicação já usa Redis com github.com/redis/go-redis/v9, não existe urgência técnica para reescrever o cliente: a API é madura, familiar e cobre standalone, Sentinel, Cluster, Pub/Sub e Streams. A decisão correta depende menos do nome do produto e mais de compatibilidade, operação, custo de migração e benchmark da carga real.
Valkey e Redis ocupam a mesma categoria: data stores em memória usados para cache, sessões, rate limit, locks, contadores, Pub/Sub e filas. Como compartilham uma origem técnica e grande parte do protocolo, é tentador tratar a troca como apenas mudar o hostname. Em sistemas simples isso pode funcionar; em produção, porém, você precisa verificar comandos, módulos, persistência, cluster, failover e o cliente Go usado pela aplicação.
Este guia compara Valkey vs Redis em Go de forma prática, mostra exemplos com valkey-go e go-redis, implementa cache-aside com TTL e apresenta um roteiro de migração. Se você quer primeiro rever os fundamentos, leia o tutorial de Go com Redis para cache e sessões. Para uso como fila, veja Redis Streams em Go.
Valkey vs Redis: decisão rápida
| Situação | Escolha inicial |
|---|---|
| Projeto novo padronizado em Valkey | valkey-go |
Projeto existente com go-redis/v9 estável | Mantenha go-redis e teste o servidor alvo |
| Time valoriza API tipada e já domina go-redis | go-redis |
| Auto pipelining é central para alta concorrência | Avalie valkey-go e meça |
| Precisa alternar entre servidores durante uma migração | Esconda poucos comandos atrás de uma interface |
| Usa módulos ou comandos específicos | Faça inventário e teste de compatibilidade |
| Cache é apenas uma otimização | Projete fallback para o banco principal |
| Dado não pode desaparecer | Não trate a camada como “só cache”; projete persistência e recuperação |
A recomendação mais importante é simples: não migre só por benchmark de README nem fique preso só por familiaridade. Teste com o mesmo tamanho de valor, concorrência, latência de rede, topologia e distribuição de comandos da sua aplicação.
O que é Valkey?
Valkey é um data store open source compatível com o protocolo usado pelo Redis e mantido como projeto da Linux Foundation. Ele nasceu do código aberto do Redis e preserva comandos e estruturas conhecidas, como strings, hashes, sets, sorted sets, streams, Pub/Sub, transações e scripts.
Para uma aplicação Go, isso significa que muitos padrões continuam iguais:
GETeSETpara cache de objetos;- TTL para expiração;
INCRpara contadores;SET NXpara coordenação simples;- pipelines para reduzir round trips;
- Sentinel ou Cluster para alta disponibilidade e particionamento;
- Streams para processamento assíncrono.
Compatibilidade, no entanto, não significa identidade eterna. Os projetos podem evoluir em ritmos diferentes, oferecer comandos novos e manter integrações distintas. Por isso, a pergunta não deve ser apenas “meus comandos básicos funcionam?”, mas “toda a combinação de servidor, cliente, módulos e operação funciona?”.
go-redis ou valkey-go: qual cliente escolher?
Os dois clientes atendem aplicações sérias, mas expõem estilos diferentes.
go-redis: API familiar e tipada
O go-redis/v9 é o cliente oficial do Redis para Go. Sua API tem métodos como Get, Set, HGetAll e XReadGroup, o que deixa o código próximo dos nomes dos comandos:
go get github.com/redis/go-redis/v9
package main
import (
"context"
"fmt"
"log"
"time"
"github.com/redis/go-redis/v9"
)
func main() {
ctx, cancel := context.WithTimeout(context.Background(), 3*time.Second)
defer cancel()
client := redis.NewClient(&redis.Options{
Addr: "127.0.0.1:6379",
DialTimeout: 2 * time.Second,
ReadTimeout: 1 * time.Second,
WriteTimeout: 1 * time.Second,
})
defer client.Close()
if err := client.Set(ctx, "produto:42", `{"nome":"Café"}`, 5*time.Minute).Err(); err != nil {
log.Fatal(err)
}
value, err := client.Get(ctx, "produto:42").Result()
if err != nil {
log.Fatal(err)
}
fmt.Println(value)
}
O pacote também oferece pooling, pipelines, transações, Sentinel, Cluster, Pub/Sub, Streams, instrumentação e recursos mais recentes como automatic pipelining e client-side caching. Alguns desses recursos novos são marcados como experimentais; valide a versão usada e não dependa de comportamento não estável sem testes.
valkey-go: command builder e auto pipelining
O cliente valkey-go é mantido no ecossistema Valkey. Sua API principal constrói comandos por um builder e envia com Do ou DoMulti:
go get github.com/valkey-io/valkey-go
package main
import (
"context"
"fmt"
"log"
"time"
"github.com/valkey-io/valkey-go"
)
func main() {
client, err := valkey.NewClient(valkey.ClientOption{
InitAddress: []string{"127.0.0.1:6379"},
})
if err != nil {
log.Fatal(err)
}
defer client.Close()
ctx, cancel := context.WithTimeout(context.Background(), 3*time.Second)
defer cancel()
set := client.B().Set().Key("produto:42").Value(`{"nome":"Café"}`).ExSeconds(300).Build()
if err := client.Do(ctx, set).Error(); err != nil {
log.Fatal(err)
}
get := client.B().Get().Key("produto:42").Build()
value, err := client.Do(ctx, get).ToString()
if err != nil {
log.Fatal(err)
}
fmt.Println(value)
}
O builder ajuda a formar comandos válidos e o cliente faz auto pipelining de comandos não bloqueantes concorrentes por padrão. Ele também oferece client-side caching assistido pelo servidor, Cluster, Sentinel, Pub/Sub, Streams, mocks e integração com OpenTelemetry.
Há um detalhe importante: comandos construídos normalmente podem ser reciclados internamente após a execução. Não guarde nem reutilize um comando depois de Do ou DoMulti, a menos que a API usada indique explicitamente como fixá-lo. Esse tipo de regra deve aparecer no review e nos testes do time.
Comparação prática dos clientes Go
| Critério | go-redis/v9 | valkey-go |
|---|---|---|
| Estilo de API | Métodos tipados por comando | Command builder + Do |
| Curva para projetos Redis existentes | Muito baixa | Exige adaptação |
| Auto pipelining | Disponível em API própria | Padrão para comandos concorrentes não bloqueantes |
| Client-side caching | Disponível, com restrições documentadas | Recurso central via DoCache |
| Sentinel e Cluster | Sim | Sim |
| Streams e Pub/Sub | Sim | Sim |
| Integração com código legado | Muito forte | Adaptador de compatibilidade disponível no ecossistema |
| Melhor escolha universal | Não existe | Não existe |
A diferença de ergonomia aparece rápido. go-redis tende a ser mais óbvio para leitura casual: client.Get(...) diz exatamente o que acontece. valkey-go concentra recursos no builder e pode exigir mais familiaridade, mas oferece validação de construção e um caminho explícito para otimizações avançadas.
Cache-aside em Go: o padrão mais seguro
Independentemente do servidor ou cliente, cache-aside é um bom padrão inicial:
- tenta buscar no cache;
- em miss, consulta a fonte principal;
- serializa e grava com TTL;
- se o cache falhar, mantém a resposta do banco quando possível.
Uma interface pequena impede que detalhes do cliente contaminem o domínio:
type Cache interface {
Get(ctx context.Context, key string) ([]byte, error)
Set(ctx context.Context, key string, value []byte, ttl time.Duration) error
Delete(ctx context.Context, key string) error
}
O serviço de produto depende dessa interface, não de *redis.Client nem de valkey.Client:
var ErrCacheMiss = errors.New("cache miss")
type ProductService struct {
cache Cache
repo ProductRepository
}
func (s *ProductService) Get(ctx context.Context, id int64) (Product, error) {
key := fmt.Sprintf("product:v1:%d", id)
if data, err := s.cache.Get(ctx, key); err == nil {
var product Product
if json.Unmarshal(data, &product) == nil {
return product, nil
}
}
product, err := s.repo.Get(ctx, id)
if err != nil {
return Product{}, err
}
if data, err := json.Marshal(product); err == nil {
cacheCtx, cancel := context.WithTimeout(ctx, 150*time.Millisecond)
defer cancel()
_ = s.cache.Set(cacheCtx, key, data, 5*time.Minute)
}
return product, nil
}
O exemplo trata cache como otimização: falhar ao gravar não transforma uma consulta bem-sucedida ao banco em erro HTTP. Essa política não serve para todos os casos — sessão, rate limit e lock têm semântica diferente — mas evita que uma indisponibilidade do cache derrube todo endpoint de leitura.
TTL, invalidação e stampede
Escolher o cliente é a parte fácil. Os incidentes aparecem na política de cache.
Use chaves versionadas
Prefira product:v1:42 em vez de product:42. Quando o JSON mudar de formato, incremente a versão e deixe as chaves antigas expirarem. Isso reduz erro de decode durante deploy gradual.
Adicione jitter ao TTL
Se milhares de chaves expiram exatamente a cada cinco minutos, muitas requisições voltam ao banco ao mesmo tempo. Adicione uma variação aleatória controlada:
base := 5 * time.Minute
jitter := time.Duration(rand.IntN(60)) * time.Second
ttl := base + jitter
Evite cache stampede
Para dados muito acessados, use singleflight dentro do processo ou coordenação distribuída quando necessário. O objetivo é permitir que uma requisição reconstrua a chave enquanto as outras aguardam, em vez de disparar centenas de queries iguais.
Invalide depois da escrita principal
Em um update, confirme a transação no banco e só depois remova ou atualize a chave. Se a invalidação falhar, registre uma métrica e confie no TTL como limite de inconsistência. Para contratos críticos, considere eventos de invalidação por outbox; o outbox pattern em Go evita perder o evento entre banco e mensageria.
Pipeline: quando realmente ajuda
Pipeline reduz round trips ao enviar vários comandos juntos. Ele ajuda quando você tem operações independentes e a latência de rede pesa. Não transforma automaticamente uma sequência dependente em operação atômica.
Use pipeline para buscar várias chaves, atualizar contadores independentes ou gravar um lote. Use transação, script ou função do servidor quando a regra exige atomicidade. E, em Cluster, mantenha atenção aos slots: operações multi-key podem exigir que as chaves usem a mesma hash tag, como cart:{42}:items e cart:{42}:total.
No valkey-go, chamadas concorrentes não bloqueantes podem ser agrupadas automaticamente. No go-redis, pipelines manuais continuam simples e o automatic pipelining deve ser avaliado conforme a API e a versão adotadas. Em ambos, benchmark sem latência de rede e sem concorrência representativa pode produzir uma conclusão inútil.
Observabilidade para Valkey ou Redis
Meça a camada de cache como parte do serviço:
- hit ratio e miss ratio por operação;
- latência de
GET,SETeDEL; - erros e timeouts;
- conexões em uso e espera pelo pool;
- memória, eviction e chaves expiradas no servidor;
- tamanho de payload;
- idade e taxa de reconstrução de chaves quentes.
Evite colocar a chave completa em labels de Prometheus: IDs criam cardinalidade ilimitada. Use atributos como cache.operation=get, cache.result=hit e cache.namespace=product. Integre traces com o guia de OpenTelemetry em Go e registre falhas com slog estruturado.
Timeout também precisa ser menor que o orçamento total da requisição. Se seu endpoint tem 500 ms, não faz sentido esperar três segundos pelo cache antes de consultar o banco. Propague context.Context conforme o padrão de timeout e cancelamento em Go.
Como migrar de Redis para Valkey
Uma migração segura pode seguir este roteiro:
- Inventarie o uso real. Liste comandos, Lua, módulos, Pub/Sub, Streams, Cluster, Sentinel, autenticação e métricas.
- Separe servidor de cliente. Você pode testar Valkey mantendo temporariamente o cliente atual, se a combinação for compatível com seu uso.
- Crie testes de integração. Rode o mesmo conjunto contra a versão exata do servidor alvo usando Testcontainers em Go.
- Valide dados e operação. Teste AOF ou snapshots, réplica, failover, backup, restore, eviction e limites de memória.
- Faça canário. Direcione uma pequena parcela de tráfego ou um serviço não crítico primeiro.
- Compare métricas. Observe erros, p50/p95/p99, hit ratio, conexões, CPU e memória.
- Mantenha rollback. Não apague a infraestrutura anterior antes de concluir a janela de validação.
Se também trocar de go-redis para valkey-go, faça isso como uma segunda variável sempre que possível. Trocar servidor e cliente ao mesmo tempo aumenta o espaço de diagnóstico: quando algo falha, você não sabe imediatamente qual camada causou a diferença.
Armadilhas comuns
- Cache sem TTL: vira armazenamento permanente acidental e consome memória até a eviction.
- TTL igual para tudo: ignora volatilidade e custo de reconstrução de cada dado.
- Erro de cache derruba a API inteira: aceitável para alguns controles, ruim para cache-aside comum.
- Lock distribuído tratado como transação: expiração, pausa do processo e falha de rede exigem desenho cuidadoso.
- Benchmark de localhost usado como capacidade: não representa rede, cluster nem payload real.
KEYS *em produção: pode bloquear trabalho relevante; useSCANpara inspeção gradual.- Dados sensíveis em chave ou log: nomes de chave também aparecem em telemetria e ferramentas operacionais.
- Múltiplas responsabilidades no mesmo servidor sem limites: cache, sessão e fila podem competir pela mesma memória e política de eviction.
Para rate limiting, defina explicitamente o comportamento quando a camada fica indisponível: fail-open preserva disponibilidade, mas reduz proteção; fail-closed protege o recurso, mas pode bloquear usuários legítimos. O guia de rate limiting em APIs Go aprofunda esse trade-off.
Checklist de produção
Antes do deploy, confirme:
- cliente e servidor têm versões pinadas e testadas;
- todas as chamadas usam contexto e timeout deliberado;
- chaves têm namespace e versão;
- TTL e jitter estão definidos;
- aplicação diferencia miss de falha operacional;
- payload inválido é descartado e reconstruído;
- cache stampede foi considerado para chaves quentes;
- memória máxima e eviction estão configuradas;
- persistência e backup correspondem à criticidade dos dados;
- métricas não usam IDs como labels;
- health check não faz operação pesada;
- integração cobre failover e perda completa do cache;
- migração tem canário e rollback documentados.
Perguntas frequentes
Valkey funciona com aplicações Go que usavam Redis?
Frequentemente, sim, especialmente em aplicações baseadas em comandos comuns. Mas “compatível” precisa ser provado para seu conjunto de comandos, módulos, scripts e topologia. Rode testes de integração e canário antes de alterar produção.
Qual cliente Go usar com Valkey?
Use valkey-go quando quiser alinhar o cliente ao ecossistema Valkey e aproveitar seu builder, auto pipelining e client-side caching. Mantenha go-redis/v9 quando a API já é padrão do time, há integrações prontas ou a migração do cliente não entrega benefício mensurável.
Valkey-go é sempre mais rápido?
Não. O projeto publica benchmarks fortes, mas a sua aplicação pode ter baixa concorrência, payload grande, comandos bloqueantes ou latência dominada por rede. Meça o fluxo real e compare também consumo de CPU, memória e complexidade operacional.
Valkey substitui o banco relacional?
Para cache, normalmente não. PostgreSQL ou outro banco continua sendo a fonte principal, e Valkey acelera leituras e coordenação. Se você decidir guardar estado primário, trate persistência, replicação, backup e recuperação como requisitos de primeira classe.
Preciso trocar go-redis ao migrar o servidor?
Não necessariamente. Separar a troca de servidor da troca de biblioteca reduz risco. Primeiro confirme a compatibilidade do cliente atual com os recursos que você usa; depois avalie se outro cliente melhora ergonomia ou performance o suficiente para justificar a reescrita.
Conclusão
Valkey e Redis continuam muito próximos do ponto de vista de uma aplicação Go, mas a escolha não deve ser feita no automático. Para projetos novos em Valkey, valkey-go oferece uma integração direta e recursos fortes de pipeline e cache no cliente. Para bases existentes, go-redis/v9 permanece uma escolha madura e trocar só por tendência pode gerar custo sem benefício.
O melhor desenho mantém a camada pequena, mede hit ratio e latência, usa TTL com jitter, tolera perda total das chaves quando o caso é cache e testa failover antes do incidente. Depois de escolher o cliente, conecte essa base ao guia de API REST em Go, ao material de circuit breaker e resiliência HTTP e ao roadmap de Go para backend. Para aplicar essas práticas profissionalmente, acompanhe também as vagas Go no Brasil.