Redis básico: tipos de dados e quando usar
1. Visão geral
Redis é um in-memory data store, frequentemente entendido erroneamente como “apenas para cache”. Na verdade, Redis fornece vários tipos de dados (data structures) diferentes, cada um adequado para um grupo de problemas específico.
Este artigo ajuda você a dominar os 5 tipos de dados mais comuns do Redis, como usá-los via Spring Data Redis e, mais importante: quando escolher cada um. Após ler, você saberá como evitar o erro clássico de “usar String para tudo” e depois sofrer ao atualizar os dados.
2. Preparando o ambiente
Adicione a dependency Spring Data Redis ao projeto:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
Declare a conexão no application.yml:
spring:
data:
redis:
host: localhost
port: 6379
Execute o Redis com Docker rapidamente:
docker run -d --name redis-dev -p 6379:6379 redis:7-alpine
Os exemplos abaixo usam RedisTemplate<String, String> que o Spring Boot configura automaticamente, você não precisa declarar o bean manualmente.
3. String: o tipo mais simples
String é o tipo básico, armazena um value associado a uma key. O value pode ser texto, número ou JSON serializado.
@Service
public class SessionTokenService {
private final RedisTemplate<String, String> redisTemplate;
public SessionTokenService(RedisTemplate<String, String> redisTemplate) {
this.redisTemplate = redisTemplate;
}
public void saveToken(String userId, String token) {
redisTemplate.opsForValue()
.set("session:" + userId, token, Duration.ofMinutes(30));
}
public String getToken(String userId) {
return redisTemplate.opsForValue().get("session:" + userId);
}
}
set(key, value, ttl): salva o token com tempo de vida de 30 minutos, Redis o deleta automaticamente quando expira.get(key): retornanullse a key não existe ou já expirou.
Saída ao chamar getToken com token expirado:
null
Quando usar: token de sessão, cache de um objeto já serializado (JSON), contador simples (INCR), feature flag. String também suporta incremento atômico:
Long visits = redisTemplate.opsForValue().increment("page:views:home");
Observação: não salve um objeto grande e serialize/desserialize tudo apenas para atualizar 1 campo — é um sinal de que você deve usar Hash.
4. Hash: objeto com múltiplos campos
Hash é como um Map<String, String> dentro de uma key, permitindo atualizar cada campo individualmente sem precisar ler/escrever o objeto inteiro.
@Service
public class UserProfileCache {
private final RedisTemplate<String, String> redisTemplate;
public UserProfileCache(RedisTemplate<String, String> redisTemplate) {
this.redisTemplate = redisTemplate;
}
public void saveProfile(String userId, Map<String, String> fields) {
redisTemplate.opsForHash().putAll("user:" + userId, fields);
}
public void updateEmail(String userId, String newEmail) {
redisTemplate.opsForHash().put("user:" + userId, "email", newEmail);
}
public Map<Object, Object> getProfile(String userId) {
return redisTemplate.opsForHash().entries("user:" + userId);
}
}
putAll: escreve múltiplos campos ao mesmo tempo, semelhante aHSET.putum campo: atualiza apenas o campoemail, outros campos (name, age…) não são afetados.entries: obtém todos os pares campo-valor do hash.
Comparado com String: se usar String + JSON, toda vez que mudar email você precisa GET o JSON inteiro, fazer parse, editar, serializar novamente e SET. Com Hash, apenas um comando HSET.
Quando usar: objetos com múltiplos campos ou atualizações independentes — perfil de usuário, detalhes de produto, configuração por tenant.
5. List: fila e histórico em ordem
List é uma lista ordenada, suporta push/pop em ambas as extremidades. Ideal para filas ou armazenar os N itens mais recentes.
@Service
public class RecentActivityService {
private final RedisTemplate<String, String> redisTemplate;
public RecentActivityService(RedisTemplate<String, String> redisTemplate) {
this.redisTemplate = redisTemplate;
}
public void logActivity(String userId, String activity) {
String key = "activity:" + userId;
redisTemplate.opsForList().leftPush(key, activity);
redisTemplate.opsForList().trim(key, 0, 9); // mantém os 10 itens mais recentes
}
public List<String> getRecentActivities(String userId) {
return redisTemplate.opsForList().range("activity:" + userId, 0, -1);
}
}
leftPush: adiciona a nova atividade no início da lista.trim(key, 0, 9): reduz a lista para manter apenas os 10 primeiros elementos, evitando que a lista cresça indefinidamente.range(0, -1): obtém todos os elementos, o índice-1significa o último elemento.
Quando usar: timeline de atividades recentes, fila de jobs simples (combine com BLPOP para o consumidor aguardar), pilha de desfazer.
Observação: List não é apropriada se você precisa buscar por value ou acessar aleatoriamente com frequência — a complexidade de acesso por índice é O(n) para listas grandes.
6. Set: conjunto único, sem ordem
Set armazena valores únicos, suporta operações de conjunto (união, interseção, diferença) muito rapidamente.
@Service
public class TagService {
private final RedisTemplate<String, String> redisTemplate;
public TagService(RedisTemplate<String, String> redisTemplate) {
this.redisTemplate = redisTemplate;
}
public void addTags(String articleId, String... tags) {
redisTemplate.opsForSet().add("article:tags:" + articleId, tags);
}
public boolean hasTag(String articleId, String tag) {
return redisTemplate.opsForSet()
.isMember("article:tags:" + articleId, tag);
}
public Set<String> commonTags(String articleId1, String articleId2) {
return redisTemplate.opsForSet()
.intersect("article:tags:" + articleId1, "article:tags:" + articleId2);
}
}
add: adiciona múltiplas tags, Redis remove automaticamente duplicatas.isMember: verifica existência com complexidade O(1), muito mais rápido que varrer uma List.intersect: encontra tags comuns entre dois artigos — usado para sugerir artigos relacionados.
Quando usar: tags/categorias, lista de visitantes únicos por dia (SADD visitors:2026-10-05 userId), verificação rápida de associação (por exemplo: o usuário já se registrou no evento?).
7. Sorted Set: classificação por pontuação
Sorted Set (ZSet) é como um Set, mas cada membro tem um score para ordenação. Este é o tipo mais poderoso para problemas de leaderboard.
@Service
public class LeaderboardService {
private final RedisTemplate<String, String> redisTemplate;
public LeaderboardService(RedisTemplate<String, String> redisTemplate) {
this.redisTemplate = redisTemplate;
}
public void addScore(String playerId, double score) {
redisTemplate.opsForZSet().add("leaderboard:weekly", playerId, score);
}
public Long getRank(String playerId) {
// rank baseado em 0, null se não existe
return redisTemplate.opsForZSet().reverseRank("leaderboard:weekly", playerId);
}
public Set<String> getTop10() {
return redisTemplate.opsForZSet()
.reverseRange("leaderboard:weekly", 0, 9);
}
}
add(key, member, score): adiciona ou atualiza a pontuação do jogador.reverseRank: obtém a classificação em ordem decrescente de pontuação (classificação mais alta tem maior pontuação).reverseRange(0, 9): obtém os top 10, Redis já os ordena, sem necessidade de sort na aplicação.
Saída de exemplo ao chamar getTop10():
["playerA", "playerC", "playerB"]
Quando usar: tabela de classificação, fila de prioridade (score = prioridade), rate limiting por janela de tempo (score = timestamp).
8. Testes com JUnit 5 e Testcontainers
Para testar a lógica relacionada ao Redis sem afetar dados reais, use Testcontainers.
<dependency>
<groupId>org.testcontainers</groupId>
<artifactId>junit-jupiter</artifactId>
<scope>test</scope>
</dependency>
@SpringBootTest
@Testcontainers
class LeaderboardServiceTest {
@Container
static GenericContainer<?> redis = new GenericContainer<>("redis:7-alpine")
.withExposedPorts(6379);
@DynamicPropertySource
static void redisProps(DynamicPropertyRegistry registry) {
registry.add("spring.data.redis.host", redis::getHost);
registry.add("spring.data.redis.port", () -> redis.getMappedPort(6379));
}
@Autowired
private LeaderboardService leaderboardService;
@Test
void shouldReturnTopPlayersInOrder() {
leaderboardService.addScore("playerA", 100);
leaderboardService.addScore("playerB", 50);
leaderboardService.addScore("playerC", 80);
Set<String> top = leaderboardService.getTop10();
assertThat(top).containsExactly("playerA", "playerC", "playerB");
}
}
@Containerinicializa um Redis real em Docker para cada classe de teste.@DynamicPropertySourcesubstitui host/port para que o Spring Boot se conecte ao container correto.containsExactlyverifica a ordem exata — Sorted Set garante que o resultado sempre seja ordenado por score decrescente.
9. Tabela de comparação rápida
| Tipo | Estrutura | Usar quando |
|---|---|---|
| String | key → value | cache de objeto, contador, token de sessão |
| Hash | key → mapa campo-valor | objetos com múltiplos campos, atualizações parciais |
| List | key → lista ordenada | fila, itens recentes, timeline |
| Set | key → conjunto único | tags, verificação de associação, operações de conjunto |
| Sorted Set | key → membro com score | leaderboard, rate limiting, fila de prioridade |
10. Erros comuns
- Usar String para dados que atualizam frequentemente: causa race condition quando múltiplas threads leem-modificam-escrevem JSON simultaneamente. Use Hash para atualizações atômicas por campo.
- List sem
trim: a lista cresce indefinidamente, consome RAM, comandoLRANGEfica cada vez mais lento com o tempo. - Esquecer de definir TTL: Redis não deleta automaticamente as keys por padrão, dados obsoletos se acumulam indefinidamente. Sempre considere
expirepara dados temporários como sessão, cache. - Usar Set para dados que precisam de ordem: Set não garante ordem de iteração, use Sorted Set se precisar ordenar.
Observação: Redis é in-memory — todos os dados residem na RAM. Não armazene dados grandes, de longa duração ou dados que requerem transações ACID complexas (por exemplo: saldo bancário) no Redis como única fonte de verdade (source of truth).
11. Conclusão
Redis não é apenas um “cache grande”, mas um conjunto de ferramentas de data structure flexível. Escolher o tipo de dado certo — String para valores simples, Hash para objetos com múltiplos campos, List para filas, Set para associações, Sorted Set para classificações — resulta em código mais conciso, menos comandos e aproveita o poder das operações atômicas do Redis em vez de lidar com essa lógica na camada de aplicação.