Redis – wprowadzenie do cache’owania

TL;DR: Redis to baza danych w pamięci RAM, używana głównie do cache’owania danych. Działa jak gigantyczny HashMap – przechowujesz pary klucz-wartość, ale jest super szybki bo wszystko trzyma w pamięci. Idealne do przyspieszania aplikacji przez cache’owanie wyników zapytań do bazy.

Jeśli Twoja aplikacja robi te same zapytania do bazy danych setki razy na sekundę, marnujesz czas i zasoby. Redis rozwiązuje ten problem – to błyskawicznie szybka baza danych w pamięci, której głównym zadaniem jest cache’owanie często używanych danych.

Dlaczego Redis jest ważny

W 2016 roku Redis zyskuje coraz większą popularność jako rozwiązanie cache’ujące i message broker. Używa go Twitter, GitHub, Instagram, Stack Overflow i tysięce innych firm. Redis jest open source, stabilny i ma aktywną społeczność deweloperską.

Performance to pieniądze w biznesie. Aplikacja która ładuje się 3 sekundy zamiast 1 sekundy traci użytkowników i przychody. Redis potrafi przyspieszyć aplikację z sekund do milisekund przez eliminację powtarzalnych zapytań do bazy danych.

Co się nauczysz:

  • Czym jest cache i dlaczego aplikacje go potrzebują
  • Jak zainstalować i skonfigurować Redis
  • Podstawowe operacje: SET, GET, DELETE
  • Rodzaje danych w Redis: strings, lists, sets, hashes
  • Jak używać Redis w aplikacji Java

Wymagania wstępne:

  • Podstawowa znajomość programowania
  • Rozumienie pojęć: baza danych, aplikacja web
  • Znajomość podstaw SQL i baz relacyjnych
  • Doświadczenie z Javą będzie pomocne w przykładach

Problem wydajności bez cache’a

Analogia: Wyobraź sobie bibliotekę gdzie za każdym razem gdy ktoś pyta o bestseller, bibliotekarz idzie do magazynu w piwnicy. Redis to jak trzymanie najpopularniejszych książek na ladzie – od razu pod ręką.

Zobaczmy typowy problem wydajnościowy:

// Bez cache - każde wywołanie idzie do bazy danych
@GetMapping("/users/{id}")
public User getUser(@PathVariable Long id) {
    // Ta operacja może trwać 50-200ms
    return userRepository.findById(id);
}

// Problem: jeśli ten endpoint jest wywoływany 1000 razy/sekundę
// dla tego samego użytkownika, wykonujemy 1000 identycznych zapytań!
Problem: Bez cache’owania aplikacja wykonuje te same kosztowne operacje wielokrotnie. To marnowanie zasobów bazy danych i wolne odpowiedzi dla użytkowników.

Czym jest Redis

Redis (Remote Dictionary Server) to:

  • In-memory database – wszystkie dane w pamięci RAM
  • Key-value store – przechowuje pary klucz-wartość
  • NoSQL – nie używa SQL, ma własne komendy
  • Persistent – może zapisywać dane na dysk
  • Distributed – wspiera clustering
Cache – tymczasowe przechowywanie często używanych danych w szybkim miejscu (pamięć RAM) żeby uniknąć powtarzania wolnych operacji (zapytania SQL).

Dlaczego Redis jest szybki?

AspektRedis (RAM)Tradycyjna baza (Dysk)
Czas dostępu~0.1ms~10-50ms
Operacje/sek100,000+1,000-10,000
Typ dostępuRandom accessSequential (optymalne)
LatencjaMikrosecundyMilisekundy

Instalacja Redis

Ubuntu/Debian:

# Instalacja Redis
sudo apt-get update
sudo apt-get install redis-server

# Uruchomienie Redis
sudo systemctl start redis-server

# Sprawdzenie czy działa
redis-cli ping
# Odpowiedź: PONG

macOS (Homebrew):

# Instalacja
brew install redis

# Uruchomienie
redis-server

# Test połączenia
redis-cli ping

Windows:

Redis oficjalnie nie wspiera Windows, ale możesz użyć:

  • Windows Subsystem for Linux (WSL)
  • Docker Desktop z obrazem Redis
  • Microsoft fork Redis dla Windows
Pro tip: Domyślnie Redis nasłuchuje na porcie 6379 i nie wymaga autoryzacji. W produkcji zawsze skonfiguruj hasło!

Podstawowe operacje Redis

Strings – podstawowy typ danych

# Podstawowe operacje key-value
redis-cli

# Zapisanie wartości
SET user:123:name "Jan Kowalski"
SET user:123:email "jan@example.com"
SET user:123:age 25

# Odczytanie wartości
GET user:123:name
# Zwraca: "Jan Kowalski"

# Sprawdzenie czy klucz istnieje
EXISTS user:123:name
# Zwraca: 1 (tak) lub 0 (nie)

# Usunięcie klucza
DEL user:123:age

# Ustawienie z czasem wygaśnięcia (TTL)
SETEX session:abc123 3600 "user_data"
# Klucz wygaśnie po 3600 sekundach (1 godzina)

Lists – listy wartości

# Dodawanie do listy (od lewej)
LPUSH notifications:user123 "Nowa wiadomość"
LPUSH notifications:user123 "Komentarz do posta"

# Dodawanie do listy (od prawej)
RPUSH recent_searches "redis tutorial"
RPUSH recent_searches "java spring boot"

# Pobranie elementów z listy
LRANGE notifications:user123 0 -1
# Zwraca wszystkie elementy

# Pobranie i usunięcie pierwszego elementu
LPOP notifications:user123

Hashes – obiekty

# Hash idealny dla obiektów
HSET user:123 name "Jan Kowalski"
HSET user:123 email "jan@example.com"
HSET user:123 age 25

# Lub wszystko na raz
HMSET user:124 name "Anna Nowak" email "anna@example.com" age 30

# Pobranie pojedynczego pola
HGET user:123 name
# Zwraca: "Jan Kowalski"

# Pobranie wszystkich pól
HGETALL user:123
# Zwraca: name "Jan Kowalski" email "jan@example.com" age "25"

# Sprawdzenie czy pole istnieje
HEXISTS user:123 phone
# Zwraca: 0 (nie istnieje)

Sets – zbiory unikalnych wartości

# Dodawanie do zbioru
SADD user:123:skills "Java"
SADD user:123:skills "Spring"
SADD user:123:skills "SQL"
SADD user:123:skills "Java"  # Duplikat - nie zostanie dodany

# Sprawdzenie członkostwa
SISMEMBER user:123:skills "Java"
# Zwraca: 1 (tak)

# Wszystkie elementy zbioru
SMEMBERS user:123:skills
# Zwraca: "Java" "Spring" "SQL"

# Różnica między zbiorami
SADD user:124:skills "Python" "SQL" "Docker"
SDIFF user:123:skills user:124:skills
# Zwraca umiejętności które ma user:123 ale nie ma user:124

Redis w aplikacji Java

Konfiguracja Spring Boot + Redis

<!-- pom.xml -->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>

<dependency>
    <groupId>redis.clients</groupId>
    <artifactId>jedis</artifactId>
</dependency>
# application.properties
spring.redis.host=localhost
spring.redis.port=6379
spring.redis.timeout=2000ms
spring.redis.jedis.pool.max-active=8
spring.redis.jedis.pool.max-idle=8

Serwis z cache’owaniem

@Service
public class UserService {
    
    @Autowired
    private UserRepository userRepository;
    
    @Autowired
    private RedisTemplate<String, Object> redisTemplate;
    
    public User findById(Long id) {
        String cacheKey = "user:" + id;
        
        // Sprawdź cache
        User cachedUser = (User) redisTemplate.opsForValue().get(cacheKey);
        if (cachedUser != null) {
            System.out.println("Cache HIT dla user:" + id);
            return cachedUser;
        }
        
        // Cache MISS - pobierz z bazy danych
        System.out.println("Cache MISS dla user:" + id);
        User user = userRepository.findById(id).orElse(null);
        
        if (user != null) {
            // Zapisz w cache na 1 godzinę
            redisTemplate.opsForValue().set(cacheKey, user, 3600, TimeUnit.SECONDS);
        }
        
        return user;
    }
    
    public void updateUser(User user) {
        userRepository.save(user);
        
        // Usuń z cache żeby następne wywołanie pobrało świeże dane
        String cacheKey = "user:" + user.getId();
        redisTemplate.delete(cacheKey);
    }
}

Annotations-based caching

@EnableCaching
@Configuration
public class CacheConfig {
    
    @Bean
    public CacheManager cacheManager(RedisConnectionFactory redisConnectionFactory) {
        RedisCacheConfiguration config = RedisCacheConfiguration.defaultCacheConfig()
                .entryTtl(Duration.ofHours(1))
                .serializeKeysWith(RedisSerializationContext.SerializationPair
                    .fromSerializer(new StringRedisSerializer()))
                .serializeValuesWith(RedisSerializationContext.SerializationPair
                    .fromSerializer(new GenericJackson2JsonRedisSerializer()));
        
        return RedisCacheManager.builder(redisConnectionFactory)
                .cacheDefaults(config)
                .build();
    }
}

@Service
public class ProductService {
    
    @Cacheable(value = "products", key = "#id")
    public Product findById(Long id) {
        // Ta metoda zostanie wywołana tylko przy cache MISS
        return productRepository.findById(id).orElse(null);
    }
    
    @CacheEvict(value = "products", key = "#product.id")
    public void updateProduct(Product product) {
        productRepository.save(product);
        // Cache zostanie automatycznie wyczyszczony
    }
    
    @Cacheable(value = "products", key = "#category + '_' + #page")
    public List<Product> findByCategory(String category, int page) {
        return productRepository.findByCategory(category, PageRequest.of(page, 20));
    }
}

Strategie cache’owania

Cache-Aside (Lazy Loading)

// Sprawdź cache -> jeśli nie ma, pobierz z DB -> zapisz w cache
public User getUser(Long id) {
    User user = cache.get("user:" + id);
    if (user == null) {
        user = database.findById(id);
        cache.set("user:" + id, user, TTL);
    }
    return user;
}

Write-Through

// Zapisz jednocześnie do DB i cache
public void saveUser(User user) {
    database.save(user);
    cache.set("user:" + user.getId(), user, TTL);
}

Write-Behind (Write-Back)

// Zapisz do cache, do DB asynchronicznie później
public void saveUser(User user) {
    cache.set("user:" + user.getId(), user, TTL);
    // Async task zapisze do DB później
    asyncWriter.schedule(() -> database.save(user));
}

Monitoring i debugging Redis

# Statystyki Redis
INFO memory
INFO stats

# Monitorowanie komend w czasie rzeczywistym
MONITOR

# Lista wszystkich kluczy (UWAGA: wolne na dużych bazach!)
KEYS *

# Lepiej: skanowanie z pattern
SCAN 0 MATCH user:* COUNT 100

# Sprawdzenie TTL klucza
TTL user:123
# Zwraca: pozostały czas w sekundach lub -1 (brak TTL)

# Ile pamięci zajmuje klucz
MEMORY USAGE user:123

Best practices

✅ Dobre praktyki:

  • Używaj TTL – zawsze ustawiaj czas wygaśnięcia dla kluczy cache
  • Konwencja nazw – używaj strukturalnych nazw: „user:123:profile”
  • Monitoruj memory usage – Redis trzyma wszystko w RAM
  • Ustaw maxmemory – zapobiega zjedzeniu całej pamięci serwera
  • Cache tylko często używane dane – nie wszystko musi być w cache
Błąd #1: Brak TTL na kluczach – cache rośnie w nieskończoność i zjada pamięć.
Błąd #2: Cache’owanie dużych obiektów – Redis ma limit na rozmiar wartości (512MB).
Błąd #3: Używanie KEYS * w produkcji – blokuje Redis na dużych bazach danych.
Błąd #4: Brak eviction policy – co się dzieje gdy Redis zużyje całą pamięć?

Redis vs inne rozwiązania cache

RozwiązanieZaletyWady
RedisSzybki, różne typy danych, persistenceSingle-threaded, wymaga dodatkowego serwera
MemcachedBardzo prosty, multi-threadedTylko key-value, brak persistence
In-memory cache (aplikacja)Najszybszy, brak network overheadNie dzielony między instancjami
Database query cacheAutomatyczny, transparentnyOgraniczona kontrola, może być nieefektywny
Czy Redis może zastąpić bazę danych?

Redis może być primary database dla prostych aplikacji, ale zwykle używa się go jako cache lub session store. Brak złożonych zapytań i joins ogranicza zastosowania.

Co się dzieje gdy Redis się restartuje?

Domyślnie Redis zapisuje dane na dysk (RDB snapshots + AOF log). Po restarcie dane zostają przywrócone, ale może być krótka przerwa w dostępności.

Ile danych może przechować Redis?

Praktycznie tyle ile masz RAM. Redis może mieć do 2^32 kluczy (4 miliardy), każda wartość string do 512MB. Całkowity limit to dostępna pamięć serwera.

Czy Redis jest bezpieczny?

Redis domyślnie nie ma autoryzacji i szyfrowania. W produkcji skonfiguruj hasło, SSL/TLS i firewall. Redis 6.0 wprowadza ACL (Access Control Lists).

Jak Redis radzi sobie z wysokim obciążeniem?

Redis jest single-threaded ale bardzo wydajny. Dla większej skali użyj Redis Cluster (sharding) lub Redis Sentinel (high availability).

Kiedy nie używać cache?

Gdy dane zmieniają się bardzo często, są unnikalne dla każdego requestu, albo gdy overhead zarządzania cache jest większy niż korzyści.

Przydatne zasoby:

🚀 Zadanie dla Ciebie

Zaimplementuj system cache’owania dla bloga:

  • Cache artykułów (tytuł, treść, autor, data)
  • Cache listy najpopularniejszych artykułów
  • Cache komentarzy dla każdego artykułu
  • Ustaw TTL: artykuły 1h, popularne 15min, komentarze 5min

Przetestuj:

  • Czy cache działa (sprawdź logi cache HIT/MISS)
  • Czy dane się odświeżają po TTL
  • Czy cache jest czyszczony po aktualizacji artykułu
  • Jaka jest różnica w czasie odpowiedzi z cache i bez

Masz pytania o Redis? Podziel się swoimi doświadczeniami w komentarzach – cache’owanie potrafi dramatycznie przyspieszyć aplikację, ale wymaga przemyślanej strategii!

Zostaw komentarz

Twój adres e-mail nie zostanie opublikowany. Wymagane pola są oznaczone *

Przewijanie do góry