Fiszki Online Redis (Preview)
Darmowy podgląd 15 z 55 dostępnych pytań
Podstawy Redis
Czym jest Redis i jakie są jego główne zastosowania?
Odpowiedź w 30 sekund: Redis (REmote DIctionary Server) to wysokowydajna, w pełni działająca w pamięci baza danych typu klucz-wartość, używana jako cache, broker wiadomości, kolejka zadań i magazyn sesji. Obsługuje bogaty zestaw struktur danych (stringi, listy, hashe, zbiory, sorted sety, streamy) oraz oferuje opcjonalną trwałość na dysku.
Odpowiedź w 2 minuty: Redis to open-source'owa baza danych typu key-value, która przechowuje całość danych w pamięci RAM, dzięki czemu osiąga ekstremalnie niskie opóźnienia (poniżej milisekundy) i przepustowość rzędu setek tysięcy operacji na sekundę z jednego węzła. W przeciwieństwie do prostych cache'y Redis nie jest tylko mapą stringów — udostępnia kilkanaście wyspecjalizowanych typów danych z atomowymi operacjami na nich.
Główne zastosowania to: cache aplikacyjny (przyspieszanie odczytów z baz relacyjnych), przechowywanie sesji użytkowników (np. w aplikacjach Node.js, Spring), kolejki zadań i pub/sub (Sidekiq, Bull, Celery używają Redisa pod spodem), rate limiting (atomowe INCR + EXPIRE), rankingi i leaderboardy (sorted sets), geolokalizacja (typ Geo), streamy zdarzeń (Redis Streams, alternatywa dla lekkiego Kafki) oraz wyszukiwanie pełnotekstowe i wektorowe (moduły RediSearch i Redis Stack).
Redis może działać jako single node, w trybie replikacji master-replica, w trybie Sentinel (wysoka dostępność) albo jako Redis Cluster (sharding pomiędzy wieloma węzłami). Dane można utrwalać na dysku przez snapshoty RDB i/lub append-only log AOF, co daje kompromis między czystym cache a pełnoprawną bazą danych.
Przykład kodu:
# Prosty cache klucz-wartość z czasem życia 60 sekund
redis-cli SET user:1001 "Anna Kowalska" EX 60
redis-cli GET user:1001
# "Anna Kowalska"
# Licznik atomowy - idealny do rate limitingu
redis-cli INCR api:calls:user:1001
redis-cli EXPIRE api:calls:user:1001 3600
# Lista zadań (kolejka FIFO)
redis-cli LPUSH zadania "wyslij-email:1001"
redis-cli RPOP zadania
# Ranking graczy (sorted set)
redis-cli ZADD ranking 1500 "gracz1" 2300 "gracz2"
redis-cli ZREVRANGE ranking 0 9 WITHSCORES
Diagram:
flowchart TD
A[Aplikacja] --> B{Redis - in-memory}
B --> C[Cache zapytan]
B --> D[Sesje uzytkownikow]
B --> E[Kolejki zadan]
B --> F[Pub/Sub i Streams]
B --> G[Rate limiting]
B --> H[Rankingi i geo]
B -.snapshot.-> I[(Dysk - RDB/AOF)]
Materiały
↑ Powrót na góręDlaczego Redis jest tak szybki w porównaniu z bazami relacyjnymi?
Odpowiedź w 30 sekund: Redis trzyma wszystkie dane w pamięci RAM, używa prostego protokołu binarnego RESP, single-threadowej pętli zdarzeń z IO multiplexingiem oraz wysoko zoptymalizowanych struktur danych w C. Brak dyskowych odczytów, brak parsera SQL, brak złączeń i transakcji ACID — to wszystko daje opóźnienia rzędu mikrosekund.
Odpowiedź w 2 minuty: Pierwszy powód to lokalizacja danych — Redis trzyma wszystko w RAM, a dostęp do pamięci jest około 100 000 razy szybszy niż do losowego sektora na dysku HDD i kilkaset razy szybszy niż do SSD. Bazy relacyjne, nawet jeśli mają duży bufor cache, muszą zarządzać stronami danych, fsynkować WAL na dysk i radzić sobie z cache miss'ami.
Drugi powód to prostota modelu danych. Operacje takie jak GET, SET, INCR to dosłownie wskaźnik w hashmapie i kilkanaście instrukcji procesora. Brak parsera SQL, planera zapytań, optymalizatora, joinów i kontroli więzów referencyjnych radykalnie skraca ścieżkę wykonania.
Trzeci powód to architektura I/O. Redis używa jednego wątku do obsługi komend, ale wraz z mechanizmem epoll/kqueue/io_uring (multiplexing) potrafi obsłużyć dziesiątki tysięcy połączeń jednocześnie. Brak kontekstowych przełączeń wątków, brak locków, brak konkurencji o cache linie CPU. Dodatkowo protokół RESP jest tekstowo-binarny i bardzo tani do sparsowania.
Czwarty powód to optymalizacje niskiego poziomu — Redis ma własne implementacje struktur (ziplist, listpack, quicklist, intset, SDS — Simple Dynamic String), które są kompaktowe pamięciowo i cache-friendly. Wreszcie, pipelining i MULTI/EXEC pozwalają wysłać wiele komend w jednym round-tripie, eliminując opóźnienie sieci.
Przykład kodu:
# Benchmark - typowo 100k-200k ops/sec na jednym rdzeniu
redis-benchmark -t set,get -n 100000 -q
# SET: 142857.14 requests per second
# GET: 158730.16 requests per second
# Pipelining - 10x szybsze niz pojedyncze komendy
redis-benchmark -t set -n 100000 -P 16 -q
# SET: 1428571.40 requests per second (16 komend w pipeline)
# Pomiar opoznienia
redis-cli --latency
# min: 0, max: 1, avg: 0.08 (ms)
Diagram:
flowchart LR
A[Klient] -- RESP --> B[Single thread event loop]
B -- epoll/kqueue --> C[IO multiplexing]
C --> D[Hashmapa w RAM]
D --> E[Odpowiedz w mikrosekundach]
F[Baza SQL] -.parser SQL.-> G[Planer]
G -.join/index.-> H[Bufor + dysk]
H -.ms-dziesiatki ms.-> I[Odpowiedz]
Materiały
↑ Powrót na góręJaka jest różnica między Redis a Memcached?
Odpowiedź w 30 sekund: Memcached to prosty, wielowątkowy cache klucz-wartość obsługujący wyłącznie stringi — szybki, ale „głupi". Redis to baza danych w pamięci z wieloma typami danych, trwałością, replikacją, pub/sub, skryptami Lua, transakcjami i clusteringiem. Memcached wybiera się dla czystego cache'owania, Redis dla wszystkiego ponad to.
Odpowiedź w 2 minuty:
Obie technologie powstały jako szybkie, in-memory key-value store'y, ale ich filozofie się rozeszły. Memcached pozostał minimalistyczny — przechowuje wyłącznie pary string -> string/blob (do 1 MB domyślnie), nie ma trwałości na dysku, nie ma replikacji, nie ma transakcji. Za to jest wielowątkowy z natury i potrafi liniowo skalować się po rdzeniach na jednej maszynie.
Redis poszedł w stronę „in-memory data structure server". Obsługuje stringi, listy, hashe, zbiory, sorted sety, streamy, bitmapy, hyperloglog, geo, JSON (z modułem), wektory (z modułem). Ma persistencję (RDB + AOF), replikację master-replica, sharding (Cluster), Pub/Sub, skrypty Lua, transakcje MULTI/EXEC, mechanizmy expiry i wbudowane eviction policies (LRU, LFU, allkeys-random).
Wybierz Memcached, jeśli potrzebujesz tylko prostego, ulotnego cache'u dla obiektów aplikacyjnych, masz dużo rdzeni i nie zależy ci na żadnych dodatkowych funkcjach. Wybierz Redis, jeśli oprócz cache potrzebujesz kolejek, rate limitingu, rankingów, pub/sub, sesji z trwałością, replikacji albo bogatszych operacji atomowych.
W praktyce większość nowych projektów wybiera Redis — różnica wydajnościowa jest niewielka, a możliwości nieporównywalnie większe.
Tabela porównawcza:
| Cecha | Redis | Memcached |
|---|---|---|
| Typy danych | string, list, hash, set, zset, stream, geo, bitmap, hll, json, vector | tylko string/blob |
| Trwałość | RDB + AOF | brak |
| Replikacja | master-replica, Sentinel | brak natywnie |
| Sharding | Redis Cluster | client-side hashing |
| Wątkowość | single-thread (od 6.0 wątki I/O) | multi-thread |
| Maks. rozmiar wartości | 512 MB | 1 MB (konfigurowalne) |
| Pub/Sub i Streams | tak | nie |
| Skrypty Lua | tak | nie |
| Transakcje | MULTI/EXEC | brak |
| TTL na klucz | tak (sekundy + milisekundy) | tak (sekundy) |
| Eviction policies | 8 polityk (LRU, LFU, ...) | LRU |
Przykład kodu:
# Redis - bogate operacje atomowe
redis-cli HSET user:1001 imie "Anna" wiek 30 miasto "Warszawa"
redis-cli HINCRBY user:1001 wiek 1
redis-cli HGETALL user:1001
# Memcached - tylko proste set/get (przez telnet/CLI)
# set user:1001 0 60 23
# {"imie":"Anna","wiek":30}
# STORED
# get user:1001
# Pub/Sub w Redis - niemozliwe w Memcached
redis-cli SUBSCRIBE kanal:zamowienia &
redis-cli PUBLISH kanal:zamowienia "nowe-zamowienie-12345"
Diagram:
flowchart TD
A[Klient] --> B{Wybor cache}
B -->|prosty key-value| C[Memcached]
B -->|bogate struktury + persistencja| D[Redis]
C --> E[Multi-thread<br/>tylko stringi<br/>brak trwalosci]
D --> F[Single-thread<br/>typy: list/hash/set/zset/stream<br/>RDB + AOF + replika]
Materiały
↑ Powrót na góręCzy Redis jest single-threaded i jak to wpływa na wydajność?
Odpowiedź w 30 sekund: Główna pętla wykonywania komend w Redis jest jednowątkowa, co eliminuje locki i context switche, ale dzięki IO multiplexingowi (epoll/kqueue) obsługuje tysiące połączeń jednocześnie. Od Redis 6.0 można włączyć dodatkowe wątki I/O do parsowania sieci, a od 7.x niektóre operacje (np. UNLINK) są asynchroniczne. Pojedyncze, długie komendy mogą jednak zablokować cały serwer.
Odpowiedź w 2 minuty: Tak — komendy są wykonywane sekwencyjnie w jednym wątku. To celowy wybór projektowy. Dzięki temu wszystkie operacje na strukturach danych są z natury atomowe (nie potrzeba locków), nie ma rywalizacji o pamięć między wątkami, nie ma cache-line bouncingu między rdzeniami, a kod jest dużo prostszy i bardziej niezawodny.
Jak to się skaluje? Redis używa IO multiplexingu (epoll na Linuxie, kqueue na BSD/macOS, IOCP na Windowsie). Jedna pętla zdarzeń pyta jądro: „które z 10 000 socketów mają coś do przeczytania?" i przetwarza tylko te. Dlatego jeden wątek obsługuje setki tysięcy operacji na sekundę i tysiące równoległych klientów.
Od Redis 6.0 wprowadzono opcjonalne threaded I/O — parsowanie i wysyłka odpowiedzi może być rozdystrybuowana na kilka wątków (io-threads 4), podczas gdy samo wykonywanie komend nadal jest jednowątkowe. To podwaja przepustowość na maszynach z wieloma rdzeniami.
Pułapki: skoro jest tylko jeden wątek wykonawczy, długo działające komendy blokują wszystko. KEYS * na bazie z milionem kluczy, SMEMBERS na zbiorze z 500 tys. elementów, ciężki skrypt Lua, DEBUG SLEEP — wszystkie inne komendy czekają. Dlatego w produkcji używa się SCAN zamiast KEYS, SSCAN/HSCAN/ZSCAN zamiast operacji na całych kolekcjach, UNLINK zamiast DEL dla dużych kluczy (asynchroniczne zwalnianie pamięci) i monitoruje się slowlog.
Przykład kodu:
# ZLE - blokuje serwer na duzych bazach
redis-cli KEYS "user:*"
# DOBRZE - iteracyjnie, nieblokujaco
redis-cli SCAN 0 MATCH "user:*" COUNT 100
# UNLINK zamiast DEL dla duzych kluczy
redis-cli UNLINK ogromny-zbior
# Wlaczenie wielowatkowego I/O (redis.conf)
# io-threads 4
# io-threads-do-reads yes
# Diagnostyka wolnych komend
redis-cli CONFIG SET slowlog-log-slower-than 10000
redis-cli SLOWLOG GET 10
Diagram:
flowchart TD
A[Klient 1] --> M[Socket 1]
B[Klient 2] --> N[Socket 2]
C[Klient N] --> O[Socket N]
M --> P[epoll/kqueue<br/>IO multiplexing]
N --> P
O --> P
P --> Q[Event loop<br/>jeden watek]
Q --> R[Wykonanie komendy<br/>na strukturze w RAM]
R --> S[Odpowiedz do klienta]
T[io-threads 4<br/>od Redis 6.0] -.parsowanie/wysylka.-> P
Materiały
↑ Powrót na góręCzym różni się Redis od innych baz NoSQL (MongoDB, Cassandra)?
Odpowiedź w 30 sekund: Redis to in-memory key-value store nastawiony na ekstremalnie niskie opóźnienia i bogate struktury danych. MongoDB to dyskowa baza dokumentowa z zapytaniami ad-hoc, indeksami i agregacjami. Cassandra to rozproszona, masowo skalowalna baza kolumnowa (wide-column) optymalizowana pod ogromne zapisy i wysoką dostępność w wielu centrach danych.
Odpowiedź w 2 minuty: Każda z tych baz reprezentuje inną kategorię NoSQL i rozwiązuje inny problem.
Redis — key-value, w pełni in-memory, single-master (w klastrze sharding po slotach), priorytet to opóźnienie poniżej milisekundy i bogate operacje atomowe na strukturach (list, set, sorted set, stream). Świetny do cache, kolejek, sesji, rate limitingu, rankingów i pub/sub. Nie nadaje się jako jedyne źródło prawdy dla dużych zbiorów danych (RAM jest drogi, choć Redis on Flash i tiering to łagodzi).
MongoDB — dokumentowa baza danych, dane to dokumenty BSON (rozszerzony JSON) w kolekcjach. Wspiera bogate zapytania ad-hoc, indeksy (w tym wielokluczowe, geo, tekstowe), pipeline'y agregacyjne, transakcje multi-document (od 4.0) i replica sets z automatycznym failoverem. Wybierana jako podstawowa baza dla aplikacji o ewoluującym schemacie i potrzebie elastycznych zapytań.
Cassandra — wide-column, zaprojektowana pod ekstremalnie wysoką dostępność i skalowanie liniowe poprzez dodawanie węzłów. Architektura masterless (każdy węzeł jest równy), replikacja multi-region, model danych zoptymalizowany pod konkretne zapytania (modelowanie „query first"). Zapisy są szybsze niż odczyty (LSM-tree, commit log + memtable + SSTable). Idealna dla danych szeregów czasowych, telemetrii, IoT, logów — gdy zapis dominuje i potrzebujesz petabajtów.
W praktyce te bazy często współistnieją: Redis jako cache i kolejki, MongoDB jako główny store dokumentów aplikacyjnych, Cassandra jako warstwa analityczna/eventowa.
Tabela porównawcza:
| Cecha | Redis | MongoDB | Cassandra |
|---|---|---|---|
| Model danych | key-value + struktury | dokumenty BSON/JSON | wide-column |
| Storage | RAM (+ opcjonalny dysk) | dysk (WiredTiger) | dysk (LSM-tree) |
| Język zapytań | własne komendy, Lua | MQL, agregacje | CQL (podobne do SQL) |
| Schemat | brak | elastyczny | zdefiniowany |
| Skalowanie | Redis Cluster (sharding) | sharded cluster | masterless, liniowe |
| Spójność | strong (single-node) | configurable (majority default) | tunable (ONE/QUORUM/ALL) |
| Replikacja | master-replica + Sentinel | replica set | multi-master, multi-DC |
| CAP | CP (w klastrze) | CP | AP |
| Transakcje | MULTI/EXEC, brak ACID | multi-document ACID (4.0+) | lekkie LWT |
| Typowe użycie | cache, sesje, kolejki, rankingi | aplikacje CRUD, katalogi, treści | IoT, time-series, logi, eventy |
| Latencja odczytu | mikrosekundy | milisekundy | milisekundy-dziesiątki ms |
Przykład kodu:
# REDIS - klucz i struktura
redis-cli SET user:1001 '{"imie":"Anna","wiek":30}'
redis-cli ZADD ranking 1500 "anna"
redis-cli LPUSH kolejka:email "user:1001"
# MONGODB - dokument i zapytanie (mongosh)
# db.uzytkownicy.insertOne({_id: 1001, imie: "Anna", wiek: 30, tagi: ["premium"]})
# db.uzytkownicy.find({wiek: {$gte: 18}, tagi: "premium"})
# db.uzytkownicy.aggregate([{$group: {_id: "$miasto", liczba: {$sum: 1}}}])
# CASSANDRA - CQL, partycja + clustering key
# CREATE TABLE pomiary (
# urzadzenie_id uuid,
# czas timestamp,
# wartosc double,
# PRIMARY KEY (urzadzenie_id, czas)
# ) WITH CLUSTERING ORDER BY (czas DESC);
# SELECT * FROM pomiary WHERE urzadzenie_id = ? AND czas > ? LIMIT 100;
Diagram:
flowchart TD
A[Wymagania] --> B{Co priorytet?}
B -->|niska latencja + cache + kolejki| C[Redis<br/>key-value in-memory]
B -->|elastyczne dokumenty + zapytania ad-hoc| D[MongoDB<br/>document store]
B -->|ogromne zapisy + multi-DC + HA| E[Cassandra<br/>wide-column]
C --> F[Cache, sesje, rankingi, pub/sub]
D --> G[Aplikacje CRUD, CMS, katalogi]
E --> H[IoT, time-series, logi, eventy]
Materiały
↑ Powrót na góręTypy danych
Czym są Streams w Redis i jak różnią się od Pub/Sub?
Odpowiedź w 30 sekund:
Stream to append-only log zdarzeń z trwałą historią i consumer groups – każde zdarzenie ma unikalny ID timestamp-seq, można je czytać wielokrotnie, od dowolnego punktu w przeszłości, a konsumenci dostają wiadomości z potwierdzeniem (ACK). Pub/Sub to fire-and-forget broadcasting bez historii – jeśli subscriber jest offline, traci wiadomości. Streams = Kafka-lite w Redis, Pub/Sub = lekki broadcast w czasie rzeczywistym.
Odpowiedź w 2 minuty: Pub/Sub (PUBLISH/SUBSCRIBE) to klasyczny wzorzec wydawca-subskrybent. Redis wysyła wiadomość do wszystkich aktywnych subskrybentów kanału. Plusy: bardzo niska latencja, prostota. Minusy: brak persystencji (offline subscriber nic nie dostanie), brak potwierdzeń, brak filtrowania po offsetcie, brak współdzielenia obciążenia między wielu konsumentów.
Streams (dodane w Redis 5.0, XADD/XREAD/XREADGROUP) to trwały log dopisujący wpisy z auto-generowanym ID typu 1700003700123-0. Strumień pamięta historię – możesz czytać od początku (0), od końca ($), od konkretnego ID lub w blokujący sposób (BLOCK 0). Consumer groups pozwalają wielu konsumentom dzielić ładunek: każda wiadomość trafia do jednego konsumenta z grupy, a Redis śledzi co zostało potwierdzone (XACK) i co czeka w Pending Entries List (PEL) – dzięki temu po awarii konsumenta można zauważyć i przetworzyć nieodebrane wiadomości (XCLAIM, XAUTOCLAIM).
Kluczowe różnice:
| Cecha | Pub/Sub | Streams |
|---|---|---|
| Persystencja | Brak | Tak (RDB/AOF) |
| Replay historii | Nie | Tak (od dowolnego ID) |
| Consumer groups | Nie | Tak (XREADGROUP) |
| Potwierdzenia (ACK) | Nie | Tak (XACK + PEL) |
| Maks. rozmiar | – | XADD MAXLEN do trimowania |
| Złożoność XADD/PUBLISH | O(log N) PUBLISH O(N+M) | XADD O(1) |
| Zastosowanie | live notyfikacje, chat | event sourcing, kolejki, audit log |
Streams są droższe pamięciowo i konceptualnie, ale są niezawodne i nadają się na backbone event-driven architektury. Pub/Sub jest świetny do efemerycznych powiadomień (online status, ticker, livechat) gdzie utrata wiadomości jest akceptowalna.
Przykład kodu:
# --- Pub/Sub ---
# Terminal A: subscriber
SUBSCRIBE notifications # czeka na wiadomości
# Terminal B: publisher
PUBLISH notifications "user:42 logged in" # O(N+M), N=subskrybenci
# --- Streams ---
# Producent dodaje zdarzenie, XADD O(1)
XADD orders * user_id 42 amount 99.99 status "new"
# -> 1700003700123-0 (auto-ID: ms-seq)
# Trim do ostatnich 10 000 zdarzeń (aprox)
XADD orders MAXLEN ~ 10000 * user_id 43 amount 50.00 status "new"
# Konsument bez grupy – czyta od konkretnego ID
XREAD COUNT 10 STREAMS orders 0
XREAD BLOCK 5000 STREAMS orders $ # blokuj 5s czekając na nowe
# Consumer group – wielu konsumentów dzieli obciążenie
XGROUP CREATE orders order-processors $ MKSTREAM
XREADGROUP GROUP order-processors worker-1 COUNT 5 BLOCK 0 STREAMS orders >
XACK orders order-processors 1700003700123-0 # potwierdź przetworzenie
# Sprawdzenie nieprzetworzonych (Pending Entries List)
XPENDING orders order-processors
XCLAIM orders order-processors worker-2 60000 1700003700123-0 # przejmij od zawieszonego
Streams – producent vs consumer group:
sequenceDiagram
participant P as Producent
participant S as Redis Stream "orders"
participant CG as Consumer Group "order-processors"
participant W1 as Worker-1
participant W2 as Worker-2
P->>S: XADD orders * user_id 42 amount 99.99
Note over S: ID: 1700003700123-0
P->>S: XADD orders * user_id 43 amount 50.00
Note over S: ID: 1700003700456-0
W1->>CG: XREADGROUP GROUP order-processors worker-1 COUNT 1
CG-->>W1: 1700003700123-0 user_id=42
Note over CG: PEL: {123-0 -> worker-1}
W2->>CG: XREADGROUP GROUP order-processors worker-2 COUNT 1
CG-->>W2: 1700003700456-0 user_id=43
Note over CG: PEL: {123-0 -> w-1, 456-0 -> w-2}
W1->>CG: XACK orders order-processors 1700003700123-0
Note over CG: PEL: {456-0 -> w-2}
Note over W2: Worker-2 pada (timeout)
W1->>CG: XAUTOCLAIM po 60s
CG-->>W1: 1700003700456-0 (przejęte)
W1->>CG: XACK orders order-processors 1700003700456-0
Materiały
↑ Powrót na góręReplikacja i wysoka dostępność
Czym jest sharding w Redis Cluster (16384 slotów)?
Odpowiedź w 30 sekund: Sharding w Redis Cluster polega na podzieleniu przestrzeni kluczy na 16384 logicznych slotów hash, do których klucze są deterministycznie mapowane przez funkcję CRC16 modulo 16384. Każdy slot jest przypisany do jednego węzła master, co umożliwia poziome skalowanie i przenoszenie partycji danych między węzłami bez przerw w działaniu.
Odpowiedź w 2 minuty:
Redis Cluster zamiast consistent hashingu używa modelu hash slots - przestrzeń kluczy jest podzielona na stałą liczbę 16384 slotów. Algorytm jest prosty: slot = CRC16(klucz) mod 16384. Liczba 16384 została wybrana świadomie - jest wystarczająco duża, aby równomiernie rozkładać dane na setki węzłów, ale na tyle mała, że bitmapa slotów zajmuje tylko 2 KB i mieści się w pakietach gossip.
Każdy węzeł master jest właścicielem przedziału slotów (np. master A: 0-5460, master B: 5461-10922, master C: 10923-16383). Mapowanie slot-to-node jest przechowywane w każdym węźle i propagowane przez protokół gossip. Gdy klient wyśle zapytanie do nieodpowiedniego węzła, otrzymuje odpowiedź MOVED <slot> <host:port> i może zaktualizować swoją lokalną mapę.
Hash tagi to mechanizm wymuszający wspólny slot dla wielu kluczy. Jeśli klucz zawiera podciąg w nawiasach {...}, tylko zawartość nawiasów jest haszowana. Dzięki temu klucze {user:1000}.profile i {user:1000}.orders trafią do tego samego slotu, co pozwala na operacje wielokluczowe i transakcje.
Resharding to proces przenoszenia slotów między węzłami. Slot może być w jednym z trzech stanów: stabilny, migrating (źródło) lub importing (cel). Podczas migracji klucze są przesyłane jeden po drugim, a klienci otrzymują przekierowania ASK dla kluczy już przeniesionych. To pozwala dodawać/usuwać węzły bez przestoju.
Przykład kodu:
# Obliczanie slotu dla klucza
redis-cli -p 7000 CLUSTER KEYSLOT uzytkownik:42
(integer) 9189
# Hash tag wymusza wspólny slot dla wielu kluczy
redis-cli -c -p 7000 CLUSTER KEYSLOT "{order:100}:item"
(integer) 4242
redis-cli -c -p 7000 CLUSTER KEYSLOT "{order:100}:total"
(integer) 4242
# Pokaż przypisanie slotów do węzłów
redis-cli -p 7000 CLUSTER SLOTS
redis-cli -p 7000 CLUSTER NODES
# Ręczna migracja slotu z węzła A do węzła B
redis-cli --cluster reshard 127.0.0.1:7000 \
--cluster-from <node-A-id> \
--cluster-to <node-B-id> \
--cluster-slots 100 \
--cluster-yes
Diagram:
flowchart LR
Klucz["Klucz: uzytkownik:42"] --> CRC{"CRC16 mod 16384"}
Klucz2["Klucz z tagiem: {order:100}:item"] --> Tag{"Hashuj tylko 'order:100'"}
Tag --> CRC
CRC -->|slot 9189| Range1{"Który zakres?"}
Range1 -->|sloty 0-5460| NodeA[(Master A)]
Range1 -->|sloty 5461-10922| NodeB[(Master B - tu trafia)]
Range1 -->|sloty 10923-16383| NodeC[(Master C)]
NodeB -->|"odpowiedź lub MOVED"| KlientWynik[Klient]
Materiały
↑ Powrót na góręStrategie cacheowania
Jak działa LRU/LFU eviction w Redis i kiedy które wybrać?
Odpowiedź w 30 sekund:
Gdy Redis osiąga maxmemory, uruchamia politykę eviction. LRU (Least Recently Used) usuwa najdawniej używane klucze - dobry dla danych z lokalnym wzorcem czasowym. LFU (Least Frequently Used, od Redis 4.0) usuwa najrzadziej używane - lepszy dla długoterminowych hot keys. Konfiguracja w redis.conf: maxmemory-policy allkeys-lru lub allkeys-lfu.
Odpowiedź w 2 minuty:
Gdy Redis osiąga limit maxmemory, polityka eviction decyduje co usunąć. Dostępne są warianty operujące na wszystkich kluczach (allkeys-*) lub tylko na kluczach z TTL (volatile-*). Polityka noeviction (domyślna) zwraca błąd OOM zamiast usuwać dane - bezpieczna dla persystentnego store, niebezpieczna dla czystego cache.
LRU (Least Recently Used) w Redis nie jest implementowane jako klasyczna lista LRU (byłoby to za drogie pamięciowo). Redis stosuje approximated LRU: losuje próbkę kluczy (maxmemory-samples, domyślnie 5, można podnieść do 10) i usuwa ten z najstarszym lru timestamp. Im większa próbka, tym dokładniejszy LRU, ale wolniejszy. LRU jest dobry, gdy "ostatnio używane" dobrze koreluje z "potrzebne w najbliższej przyszłości" - czyli dla typowych wzorców web traffic.
LFU (Least Frequently Used) od Redis 4.0 zlicza częstość dostępu z probabilistic counter (Morris counter). Counter zwiększa się logarytmicznie (lfu-log-factor, domyślnie 10) - aby nie został zdominowany przez chwilowe szczyty. Counter jest też okresowo zmniejszany (lfu-decay-time w minutach, domyślnie 1) - aby stare popularne klucze nie blokowały nowych. LFU jest lepszy dla długoterminowych hot keys (np. ranking top produktów), gdzie nawet jeśli klucz nie był używany od minuty, wiemy że jest ważny.
Wybór polityki:
| Polityka | Co usuwa | Kiedy używać |
|---|---|---|
noeviction |
Nic - zwraca błąd | Redis jako persystentny store (nie cache) |
allkeys-lru |
Najdawniej używane (wszystkie klucze) | Pure cache, typowy web traffic, recency matters |
allkeys-lfu |
Najrzadziej używane (wszystkie klucze) | Hot keys dominują (rankingi, popularne produkty) |
allkeys-random |
Losowe | Testy, równomierny dostęp do wszystkich kluczy |
volatile-lru |
LRU spośród kluczy z TTL | Mieszany store - persystentny + cache z TTL |
volatile-lfu |
LFU spośród kluczy z TTL | Jak wyżej, ale z hot keys |
volatile-random |
Losowo spośród kluczy z TTL | Rzadko używane |
volatile-ttl |
Klucze z najkrótszym TTL | Gdy TTL koreluje z ważnością |
Tuning:
maxmemory 4gb
maxmemory-policy allkeys-lfu
maxmemory-samples 10 # większa próbka = dokładniej, ale wolniej
lfu-log-factor 10 # wyższy = wolniejszy wzrost countera
lfu-decay-time 1 # minuty - jak szybko zapominamy
Monitorowanie: INFO stats - evicted_keys (suma usuniętych), evicted_clients (rozłączeni przez client-output-buffer-limit). Wysoka liczba evictions = za mała pamięć lub zły TTL.
flowchart TD
A[maxmemory<br/>osiągnięty] --> B{maxmemory-policy?}
B -->|noeviction| C[Zwróć błąd OOM]
B -->|allkeys-lru| D[Próbka N kluczy<br/>usuń najdawniej używany]
B -->|allkeys-lfu| E[Próbka N kluczy<br/>usuń z najniższym counterem dostępu]
B -->|allkeys-random| F[Usuń losowy klucz]
B -->|volatile-*| G[Tylko klucze z TTL]
D --> H[Recency matters<br/>web traffic, sesje]
E --> I[Hot keys dominują<br/>rankingi, top produkty]
F --> J[Równomierny dostęp]
G --> K[Mieszany store<br/>persystencja + cache]
style E fill:#ccffcc
style D fill:#ccccff
Przykład kodu:
// Node.js z ioredis - konfiguracja i monitorowanie eviction
const Redis = require('ioredis');
const redis = new Redis();
// === 1. KONFIGURACJA POLITYKI (runtime, lepiej w redis.conf) ===
async function configureCacheEviction() {
// Ustaw maxmemory na 2GB
await redis.config('SET', 'maxmemory', '2gb');
// Wybierz politykę - allkeys-lfu dla typowego cache
await redis.config('SET', 'maxmemory-policy', 'allkeys-lfu');
// Większa próbka = dokładniejszy LFU/LRU (domyślnie 5)
await redis.config('SET', 'maxmemory-samples', '10');
// LFU tuning
await redis.config('SET', 'lfu-log-factor', '10'); // wzrost countera
await redis.config('SET', 'lfu-decay-time', '1'); // zapominanie (minuty)
console.log('Polityka eviction skonfigurowana');
}
// === 2. MONITOROWANIE EVICTION ===
async function monitorEviction() {
const info = await redis.info('stats');
const lines = info.split('\r\n');
const stats = {};
for (const line of lines) {
const [k, v] = line.split(':');
if (['evicted_keys', 'keyspace_hits', 'keyspace_misses', 'expired_keys'].includes(k)) {
stats[k] = parseInt(v);
}
}
const hitRate = stats.keyspace_hits / (stats.keyspace_hits + stats.keyspace_misses);
console.log(`Hit rate: ${(hitRate * 100).toFixed(2)}%`);
console.log(`Evicted keys: ${stats.evicted_keys}`);
console.log(`Expired keys: ${stats.expired_keys}`);
// Memory info
const mem = await redis.info('memory');
const used = mem.match(/used_memory_human:(\S+)/)[1];
const max = mem.match(/maxmemory_human:(\S+)/)[1];
console.log(`Memory: ${used} / ${max}`);
// Alert jeśli za dużo evictions
if (stats.evicted_keys > 1000) {
console.warn('Wysoka liczba evictions - rozważ większą pamięć lub krótsze TTL');
}
}
// === 3. SPRAWDZENIE STATYSTYK LFU DLA KONKRETNEGO KLUCZA ===
async function checkKeyFrequency(key) {
// OBJECT FREQ wymaga polityki allkeys-lfu / volatile-lfu
try {
const freq = await redis.object('FREQ', key);
const idle = await redis.object('IDLETIME', key);
console.log(`${key}: frequency=${freq}, idle=${idle}s`);
return freq;
} catch (e) {
console.log('OBJECT FREQ dostępne tylko przy polityce LFU');
}
}
// === 4. DECYZJA: KIEDY LRU vs LFU? ===
// Przykład: e-commerce
// - Sesje użytkowników (TTL 30 min): volatile-lru lub allkeys-lru
// (recency = aktywny użytkownik)
// - Cache produktów (TTL 1h): allkeys-lfu
// (top 100 produktów generuje 80% ruchu - hot keys)
// - Rankingi (bez TTL): allkeys-lfu z większym lfu-decay-time
// (długoterminowa popularność)
// W redis.conf rekomendacja dla pure cache:
// maxmemory 4gb
// maxmemory-policy allkeys-lfu
// maxmemory-samples 10
Materiały
↑ Powrót na góręTransakcje i skrypty Lua
Jak działają transakcje w Redis (MULTI, EXEC, DISCARD, WATCH)?
Odpowiedź w 30 sekund:
Transakcje Redis pozwalają wykonać sekwencję poleceń atomicznie – wszystkie polecenia są kolejkowane po MULTI, a następnie wykonywane jednym ciągiem przez EXEC bez przerywania innymi klientami. DISCARD anuluje kolejkę, a WATCH dodaje optimistic locking – jeśli obserwowany klucz zmieni się przed EXEC, transakcja zostaje przerwana. Uwaga: w Redis nie ma rollbacka – błędy wykonania polecenia nie cofają wcześniejszych operacji.
Odpowiedź w 2 minuty:
Transakcja w Redis to grupa poleceń wykonywana sekwencyjnie i atomicznie. Po wpisaniu MULTI serwer przełącza klienta w tryb kolejkowania – kolejne polecenia nie są wykonywane od razu, tylko odkładane do bufora i zwracają odpowiedź QUEUED. Dopiero EXEC uruchamia całą kolejkę jako jeden, nieprzerywalny blok. Między pierwszym a ostatnim poleceniem żaden inny klient nie zostanie obsłużony – to gwarantuje atomowość izolacji.
DISCARD czyści bufor i wychodzi z trybu transakcyjnego bez wykonywania niczego. WATCH key [key ...] to mechanizm optymistycznego blokowania – jeżeli którykolwiek obserwowany klucz zostanie zmodyfikowany przez innego klienta między WATCH a EXEC, transakcja zostaje przerwana, a EXEC zwraca nil. Wtedy klient musi powtórzyć całą operację (klasyczny wzorzec read-modify-write).
Najważniejsze ograniczenie: Redis nie wykonuje rollbacku po błędach wykonania. Jeśli np. wywołasz INCR na kluczu zawierającym string, błąd dotyczy tylko tego jednego polecenia – pozostałe operacje w transakcji wykonają się normalnie. Błędy składniowe wykryte przed EXEC (np. nieistniejące polecenie) powodują odrzucenie całej transakcji.
sequenceDiagram
participant C as Klient
participant R as Redis Server
C->>R: MULTI
R-->>C: OK (tryb kolejkowania)
C->>R: SET klucz1 wartosc1
R-->>C: QUEUED
C->>R: INCR licznik
R-->>C: QUEUED
C->>R: LPUSH lista elem
R-->>C: QUEUED
C->>R: EXEC
Note over R: Atomiczne wykonanie<br/>wszystkich poleceń
R-->>C: [OK, 5, 3] (tablica wynikow)
Przykład kodu:
# Podstawowa transakcja MULTI/EXEC
127.0.0.1:6379> MULTI
OK
127.0.0.1:6379(TX)> SET konto:A 100
QUEUED
127.0.0.1:6379(TX)> SET konto:B 200
QUEUED
127.0.0.1:6379(TX)> INCRBY konto:A -50
QUEUED
127.0.0.1:6379(TX)> INCRBY konto:B 50
QUEUED
127.0.0.1:6379(TX)> EXEC
1) OK
2) OK
3) (integer) 50
4) (integer) 250
# Anulowanie transakcji - DISCARD
127.0.0.1:6379> MULTI
OK
127.0.0.1:6379(TX)> SET temp "x"
QUEUED
127.0.0.1:6379(TX)> DISCARD
OK
# Bufor wyczyszczony, nic nie zostalo zapisane
# Blad wykonania NIE cofa transakcji (brak rollbacku!)
127.0.0.1:6379> SET licznik "nie-liczba"
OK
127.0.0.1:6379> MULTI
OK
127.0.0.1:6379(TX)> SET klucz1 "ok"
QUEUED
127.0.0.1:6379(TX)> INCR licznik
QUEUED
127.0.0.1:6379(TX)> SET klucz2 "tez-ok"
QUEUED
127.0.0.1:6379(TX)> EXEC
1) OK
2) (error) ERR value is not an integer or out of range
3) OK
# Polecenia 1 i 3 zostaly wykonane mimo bledu w 2!
Materiały
↑ Powrót na góręIntegracja i klienty
Jak skonfigurować connection pool dla Redis w aplikacji Node.js?
Odpowiedź w 30 sekund:
W Redis dla Node.js zwykle nie używamy klasycznego connection poola jak w bazach SQL — pojedyncze połączenie TCP jest wystarczające dzięki pipelining i asynchronicznemu modelowi I/O. Pooling stosujemy tylko w specyficznych scenariuszach: subskrypcje Pub/Sub (osobne połączenie), blokujące komendy (BLPOP, XREAD) lub bardzo duży throughput. Wtedy używamy bibliotek typu generic-pool lub tworzymy kilka instancji klienta.
Odpowiedź w 2 minuty: Redis to serwer single-threaded obsługujący żądania w trybie sekwencyjnym, a klienty Node.js (ioredis, node-redis) wykorzystują pipelining — wiele komend leci jednym połączeniem TCP bez czekania na odpowiedź. W typowej aplikacji web jedno połączenie do Redisa per proces Node.js jest optymalne i nie potrzebujesz poola.
Kiedy pool ma sens:
- Pub/Sub: klient subskrybujący nie może wykonywać zwykłych komend (jest w trybie subscriber). Trzeba więc trzymać osobne połączenie dla
publishi dlasubscribe. - Blokujące komendy:
BLPOP,BRPOP,XREAD BLOCKblokują połączenie do czasu otrzymania danych. Jeśli używasz Redisa jako kolejki, potrzebujesz dedykowanego klienta dla blokowania. - Bardzo wysoki throughput: powyżej ~50k ops/s na pojedyncze połączenie może wystąpić bottleneck (event loop, parsowanie). Wtedy round-robin po kilku klientach pomaga.
W ioredis można użyć biblioteki generic-pool, ale częstszą praktyką jest po prostu utworzenie kilku named klientów (mainClient, pubsubClient, queueClient) jako singletony w aplikacji. Klucz to reuse — nie twórz nowego klienta przy każdym żądaniu HTTP.
Przykład kodu:
// Node.js - typowy setup w aplikacji Express
import Redis from 'ioredis';
// Singleton - jeden klient dla całej aplikacji (zwykłe komendy)
export const redis = new Redis({
host: process.env.REDIS_HOST,
port: 6379,
// Pool nie jest potrzebny - jedno połączenie + pipelining wystarczy
lazyConnect: true,
maxRetriesPerRequest: 3,
enableOfflineQueue: true, // kolejkuj komendy przed połączeniem
});
// Osobny klient dla Pub/Sub - subscriber nie może wykonywać zwykłych komend
export const subscriber = new Redis({ host: process.env.REDIS_HOST });
// Osobny klient dla blokujących komend (np. kolejka zadań z BLPOP)
export const queueClient = new Redis({ host: process.env.REDIS_HOST });
// Przykład prawdziwego poola - tylko gdy faktycznie potrzebny
import genericPool from 'generic-pool';
const factory = {
create: () => new Redis({ host: process.env.REDIS_HOST }),
destroy: (client) => client.quit(),
};
const pool = genericPool.createPool(factory, {
min: 2,
max: 10, // maks. 10 połączeń
acquireTimeoutMillis: 3000,
});
// Użycie poola
const client = await pool.acquire();
try {
await client.set('klucz', 'wartość');
} finally {
await pool.release(client); // ZAWSZE zwracaj klienta do poola
}
Materiały
↑ Powrót na góręPersystencja danych
Jakie są mechanizmy persystencji w Redis (RDB, AOF)?
Odpowiedź w 30 sekund: Redis oferuje dwa główne mechanizmy persystencji: RDB (Redis Database) tworzy migawki (snapshoty) całej bazy w wyznaczonych momentach, a AOF (Append Only File) loguje każdą operację zapisu do pliku. Można ich używać niezależnie lub łącznie, aby uzyskać najlepszy kompromis między wydajnością a trwałością danych.
Odpowiedź w 2 minuty:
RDB to mechanizm tworzenia migawek w punkcie czasu (point-in-time snapshot). Redis zapisuje w plik binarny dump.rdb cały stan bazy danych zgodnie z regułami save lub po wywołaniu komend SAVE/BGSAVE. Operacja BGSAVE używa wywołania systemowego fork(), dzięki czemu dziecko zapisuje migawkę bez blokowania głównego wątku obsługującego klientów. RDB daje kompaktowe, łatwe do skopiowania pliki i bardzo szybki restart, ale w razie awarii tracimy wszystkie zapisy od ostatniego snapshotu.
AOF działa zupełnie inaczej: każda komenda modyfikująca stan (np. SET, LPUSH, INCR) jest dopisywana do pliku appendonly.aof w formacie protokołu RESP. Plik jest okresowo synchronizowany na dysk poprzez fsync() z konfigurowalną częstotliwością (always, everysec, no). AOF zapewnia znacznie wyższą trwałość danych — typowo można stracić maksymalnie sekundę zapisów. Wadą jest większy rozmiar pliku oraz dłuższy czas restartu, ponieważ Redis musi odtworzyć wszystkie operacje. Mechanizm AOF rewrite okresowo kompaktuje plik, eliminując nadmiarowe komendy.
Od Redis 7.0 AOF używa nowego formatu multi-part (appendonly.aof.1.base.rdb + plik incremental), co łączy zalety RDB jako bazy z dopisywaniem AOF dla świeżych zmian. Domyślnie w najnowszych wersjach Redis preferuje persystencję hybrydową, łączącą oba mechanizmy.
Przykład kodu:
# redis.conf - podstawowa konfiguracja persystencji
# === RDB - migawki w punkcie czasu ===
# Zapis co 900s jeśli wystąpiła min. 1 zmiana
save 900 1
# Zapis co 300s jeśli wystąpiło min. 10 zmian
save 300 10
# Zapis co 60s jeśli wystąpiło min. 10 000 zmian
save 60 10000
# Nazwa pliku migawki
dbfilename dump.rdb
# Katalog na pliki persystencji
dir /var/lib/redis
# === AOF - log każdej operacji zapisu ===
appendonly yes
appendfilename "appendonly.aof"
# fsync co sekundę - rozsądny kompromis
appendfsync everysec
# Wyłącz zatrzymywanie zapisu gdy RDB nie powiedzie się
stop-writes-on-bgsave-error no
Materiały
↑ Powrót na góręPub/Sub i kolejki wiadomości
Czym jest mechanizm Pub/Sub w Redis i jak działa?
Odpowiedź w 30 sekund: Pub/Sub (Publish/Subscribe) to wzorzec wymiany wiadomości, w którym publisherzy wysyłają wiadomości na nazwane kanały, a subskrybenci odbierają je w czasie rzeczywistym. Redis działa jako broker, który natychmiast rozsyła wiadomości do wszystkich aktywnie podłączonych subskrybentów danego kanału. Komunikacja jest jednokierunkowa, asynchroniczna i odbywa się w trybie fire-and-forget.
Odpowiedź w 2 minuty:
Redis Pub/Sub został wprowadzony w wersji 2.0 i implementuje klasyczny wzorzec publish-subscribe znany z systemów rozproszonych. Klient subskrybuje jeden lub więcej kanałów poleceniem SUBSCRIBE channel, a inny klient publikuje na nich wiadomości przez PUBLISH channel message. Redis przekazuje wiadomość do wszystkich aktualnie podłączonych subskrybentów w trybie real-time — wiadomość jest dostarczana raz, do każdego aktywnego klienta, i nie jest nigdzie zapisywana.
Dostępne są również wzorce dopasowywania kanałów: PSUBSCRIBE news.* subskrybuje wszystkie kanały pasujące do wzorca z gwiazdką (glob-style). W Redis 7.0 dodano Sharded Pub/Sub (SSUBSCRIBE/SPUBLISH), który działa w trybie cluster — wiadomości są kierowane tylko do węzła odpowiedzialnego za dany slot, co znacząco poprawia skalowalność (klasyczny Pub/Sub w klastrze broadcastuje przez wszystkie węzły).
Typowe zastosowania to powiadomienia w czasie rzeczywistym (chat, presence systems), invalidacja cache między instancjami aplikacji, broadcast eventów między mikroserwisami, dashboardy live oraz aktualizacje typu WebSocket. Po stronie klienta (np. ioredis w Node.js) używamy osobnego połączenia w trybie subscriber, ponieważ klient w trybie subskrypcji nie może wykonywać innych poleceń poza komendami związanymi z Pub/Sub.
sequenceDiagram
participant P as Publisher
participant R as Redis Broker
participant S1 as Subskrybent 1
participant S2 as Subskrybent 2
participant S3 as Subskrybent 3
S1->>R: SUBSCRIBE powiadomienia
S2->>R: SUBSCRIBE powiadomienia
S3->>R: PSUBSCRIBE pow*
P->>R: PUBLISH powiadomienia "Nowe zamówienie"
R-->>S1: wiadomość dostarczona
R-->>S2: wiadomość dostarczona
R-->>S3: wiadomość dostarczona (wzorzec)
Note over R,S3: Wiadomość nigdzie nie jest zapisywana
Przykład kodu:
# CLI Redis - terminal 1 (subskrybent)
redis-cli
127.0.0.1:6379> SUBSCRIBE powiadomienia
Reading messages... (press Ctrl-C to quit)
1) "subscribe"
2) "powiadomienia"
3) (integer) 1
# CLI Redis - terminal 2 (publisher)
redis-cli PUBLISH powiadomienia "Nowe zamowienie #1234"
# (integer) 1 <-- liczba subskrybentow ktorzy otrzymali wiadomosc
# Node.js - subscriber z ioredis
const Redis = require('ioredis');
const subscriber = new Redis();
// Subskrybent musi miec dedykowane polaczenie
subscriber.subscribe('powiadomienia', 'zamowienia', (err, count) => {
if (err) return console.error('Blad subskrypcji:', err);
console.log(`Zasubskrybowano ${count} kanalow`);
});
subscriber.on('message', (channel, message) => {
console.log(`Kanal: ${channel}, wiadomosc: ${message}`);
});
// Subskrypcja wzorca - wszystkie kanaly zaczynajace sie od "user."
subscriber.psubscribe('user.*');
subscriber.on('pmessage', (pattern, channel, message) => {
console.log(`Wzorzec: ${pattern}, kanal: ${channel}, dane: ${message}`);
});
// Node.js - publisher (osobne polaczenie)
const publisher = new Redis();
await publisher.publish('powiadomienia', JSON.stringify({
typ: 'zamowienie',
id: 1234,
kwota: 199.99
}));
Materiały
↑ Powrót na góręWydajność i optymalizacja
Jak monitorować wydajność Redis (INFO, MONITOR, SLOWLOG)?
Odpowiedź w 30 sekund:
Redis udostępnia trzy podstawowe narzędzia diagnostyczne: INFO zwraca metryki serwera (pamięć, CPU, klienci, replikacja), MONITOR strumieniuje w czasie rzeczywistym wszystkie wykonywane komendy, a SLOWLOG rejestruje zapytania przekraczające zdefiniowany próg czasu. W produkcji najczęściej używa się INFO i SLOWLOG, ponieważ MONITOR mocno obciąża serwer.
Odpowiedź w 2 minuty:
Komenda INFO zwraca sekcje statystyk - INFO memory pokazuje used_memory, used_memory_rss, mem_fragmentation_ratio oraz maxmemory_policy. INFO stats ujawnia total_commands_processed, instantaneous_ops_per_sec, keyspace_hits i keyspace_misses (z których wyliczysz cache hit ratio). INFO clients pokazuje liczbę połączeń i kolejkę wyjściową.
SLOWLOG to wbudowany dziennik wolnych zapytań. Próg ustawiasz przez CONFIG SET slowlog-log-slower-than 10000 (mikrosekundy, czyli 10 ms), a długość bufora przez slowlog-max-len. Komenda SLOWLOG GET 10 zwraca 10 ostatnich wolnych operacji wraz z ID klienta, czasem wykonania i argumentami.
MONITOR jest narzędziem debugowania - wypisuje każdą komendę przechodzącą przez serwer. Używaj go tylko krótko, bo zwiększa zużycie CPU nawet o 50% i blokuje pętlę zdarzeń. Do produkcyjnego monitoringu lepiej podłączyć Prometheus z redis_exporter albo użyć redis-cli --stat i redis-cli --latency.
Do diagnozy opóźnień Redis ma też LATENCY - włącz przez CONFIG SET latency-monitor-threshold 100 i odczytaj przez LATENCY HISTORY event-name lub LATENCY DOCTOR (automatyczna analiza).
Przykład kodu:
# Sprawdź zużycie pamięci i fragmentację
redis-cli INFO memory | grep -E "used_memory_human|fragmentation_ratio|maxmemory"
# Statystyki operacji i cache hit ratio
redis-cli INFO stats | grep -E "ops_per_sec|keyspace_hits|keyspace_misses"
# Konfiguracja SLOWLOG - loguj operacje > 10ms, bufor 1000 wpisów
redis-cli CONFIG SET slowlog-log-slower-than 10000
redis-cli CONFIG SET slowlog-max-len 1000
# Pobierz 10 ostatnich wolnych operacji
redis-cli SLOWLOG GET 10
# Wyczyść log po analizie
redis-cli SLOWLOG RESET
# Monitor opóźnień - włącz próg 100ms
redis-cli CONFIG SET latency-monitor-threshold 100
redis-cli LATENCY DOCTOR # automatyczna diagnoza
# Pomiar opóźnienia w czasie rzeczywistym
redis-cli --latency -h localhost -p 6379
# UWAGA: MONITOR mocno obciąża - używaj tylko do debugowania
redis-cli MONITOR
Materiały
↑ Powrót na góręBezpieczeństwo
Jak zabezpieczyć Redis w produkcji (AUTH, ACL, bind, firewall)?
Odpowiedź w 30 sekund:
Redis nie jest zaprojektowany do bycia eksponowanym publicznie - powinien działać w zaufanej sieci wewnętrznej. Minimum bezpieczeństwa to: bind na konkretnych interfejsach (nie 0.0.0.0), protected-mode yes, silne hasło przez requirepass lub ACL, firewall blokujący port 6379 z zewnątrz oraz TLS dla połączeń przez sieć publiczną.
Odpowiedź w 2 minuty:
Warstwa sieciowa to pierwsza linia obrony. Domyślnie Redis powinien nasłuchiwać tylko na 127.0.0.1 (localhost) lub na prywatnym interfejsie sieciowym. Dyrektywa protected-mode yes (domyślnie włączona od Redis 3.2) blokuje połączenia z zewnątrz, jeśli nie ustawiono hasła i bind jest na wszystkich interfejsach. Firewall (iptables, security groups w AWS, Network Security Groups w Azure) powinien ograniczać dostęp do portu 6379 tylko z konkretnych adresów IP aplikacji.
Warstwa uwierzytelnienia w Redis 6+ to ACL (Access Control List), który zastąpił starsze requirepass. ACL pozwala tworzyć wielu użytkowników z różnymi uprawnieniami - można ograniczyć, do których kluczy i komend ma dostęp dany użytkownik. Każda aplikacja powinna mieć własnego użytkownika z minimalnymi potrzebnymi uprawnieniami (zasada least privilege).
Warstwa transportu to TLS - od Redis 6 wbudowane wsparcie dla szyfrowania połączeń klient-serwer i replikacji master-replica. Wymagane przy komunikacji przez internet lub niezaufaną sieć. Dodatkowo warto wyłączyć lub zmienić nazwy niebezpiecznych komend (FLUSHALL, FLUSHDB, CONFIG, DEBUG) przez dyrektywę rename-command.
Monitoring i audyt: włącz logowanie (loglevel notice), monitoruj nieudane próby uwierzytelnienia, używaj CLIENT LIST do śledzenia połączeń. Aktualizuj Redis regularnie - historia pokazuje wiele krytycznych CVE.
Przykład kodu:
# redis.conf - bezpieczna konfiguracja produkcyjna
# === Sieć ===
# Bind tylko na localhost i prywatnym interfejsie aplikacji
bind 127.0.0.1 10.0.1.5
port 6379
# Tryb chroniony - blokuje połączenia bez auth z zewnątrz
protected-mode yes
# === Uwierzytelnienie (legacy AUTH) ===
# Silne hasło - minimum 32 znaki losowe
requirepass "K8sN3xJp9vQwR2tY7uI4oP6aS5dF1gH"
# === ACL (Redis 6+) - lepsze niż requirepass ===
# Wczytaj plik ACL przy starcie
aclfile /etc/redis/users.acl
# === Zmiana nazw niebezpiecznych komend ===
rename-command FLUSHALL ""
rename-command FLUSHDB ""
rename-command CONFIG "CONFIG_a1b2c3d4"
rename-command DEBUG ""
rename-command SHUTDOWN "SHUTDOWN_xyz123"
# === TLS ===
tls-port 6380
port 0
tls-cert-file /etc/redis/tls/redis.crt
tls-key-file /etc/redis/tls/redis.key
tls-ca-cert-file /etc/redis/tls/ca.crt
tls-auth-clients yes
# === Limity zasobów (ochrona przed DoS) ===
maxmemory 4gb
maxmemory-policy allkeys-lru
maxclients 10000
timeout 300
# === Logowanie ===
loglevel notice
logfile /var/log/redis/redis.log
# Firewall - iptables (Linux)
# Pozwól tylko z konkretnego IP aplikacji
iptables -A INPUT -p tcp -s 10.0.1.10 --dport 6379 -j ACCEPT
iptables -A INPUT -p tcp --dport 6379 -j DROP
Materiały
↑ Powrót na góręUse cases i wzorce projektowe
Jak zaimplementować rate limiting w Redis (token bucket, sliding window)?
Odpowiedź w 30 sekund:
Rate limiting w Redis można zrealizować na kilka sposobów: fixed window (prosty INCR z EXPIRE), sliding window log (Sorted Set z timestampami) oraz token bucket (Hash z liczbą tokenów i czasem ostatniego uzupełnienia). Najdokładniejszy jest sliding window log, ale token bucket pozwala na obsługę bursts. Aby zapewnić atomowość operacji, najlepiej użyć skryptu Lua wykonywanego po stronie Redis.
Odpowiedź w 2 minuty:
Fixed window to najprostsza metoda — kluczem jest rate:userId:minuta, a operacja INCR zwiększa licznik; jeśli przekracza limit, request jest odrzucany. Wadą jest efekt brzegowy: w ostatniej sekundzie okna użytkownik może wysłać pełny limit, a w pierwszej sekundzie kolejnego okna jeszcze raz pełny limit — czyli 2x więcej niż zakładaliśmy.
Sliding window log rozwiązuje ten problem. Każdy request jest zapisywany jako element Sorted Set (ZADD key timestamp uniqueId), a przed sprawdzeniem limitu usuwamy stare timestampy (ZREMRANGEBYSCORE key 0 (now - window)) i liczymy elementy w oknie (ZCARD). To dokładne, ale zużywa więcej pamięci (przechowuje każdy request).
Token bucket symuluje wiadro o pojemności N, do którego co X ms dodawany jest token. Każdy request konsumuje token; brak tokenów oznacza odrzucenie. Algorytm pozwala na bursts (np. 100 requestów naraz, jeśli wiadro pełne), ale utrzymuje średnią przepustowość.
Kluczowa zasada: wszystkie te operacje muszą być atomowe. Bez Lua skryptu wystąpi race condition — między ZCARD a ZADD dwa procesy mogą uznać, że limit nie został przekroczony. Skrypt Lua jest wykonywany jednowątkowo w Redis i gwarantuje atomowość.
flowchart TD
A[Request od użytkownika] --> B[ZADD timestamp:userId now uniqueId]
B --> C[ZREMRANGEBYSCORE key 0 now - window]
C --> D[ZCARD klucz - liczba requestów w oknie]
D --> E{count > limit?}
E -->|Tak| F[Odrzuć: 429 Too Many Requests]
E -->|Nie| G[Przepuść request]
G --> H[EXPIRE klucz window seconds]
Przykład kodu:
// Node.js z ioredis - sliding window log z atomowym skryptem Lua
const Redis = require('ioredis');
const redis = new Redis();
// Skrypt Lua - atomic sliding window rate limiter
const slidingWindowScript = `
local key = KEYS[1]
local now = tonumber(ARGV[1])
local window = tonumber(ARGV[2])
local limit = tonumber(ARGV[3])
-- Usuń stare wpisy spoza okna
redis.call('ZREMRANGEBYSCORE', key, 0, now - window)
-- Policz aktualne requesty w oknie
local count = redis.call('ZCARD', key)
if count < limit then
-- Dodaj nowy timestamp (uniqueId zapobiega kolizjom)
redis.call('ZADD', key, now, now .. ':' .. math.random())
redis.call('EXPIRE', key, math.ceil(window / 1000))
return {1, limit - count - 1} -- {allowed, remaining}
else
return {0, 0} -- {denied, remaining}
end
`;
async function checkRateLimit(userId, limit = 100, windowMs = 60000) {
const key = `rate:${userId}`;
const now = Date.now();
const result = await redis.eval(
slidingWindowScript,
1, // liczba kluczy
key, // KEYS[1]
now, // ARGV[1] - aktualny czas
windowMs, // ARGV[2] - rozmiar okna w ms
limit // ARGV[3] - maksymalna liczba requestów
);
return { allowed: result[0] === 1, remaining: result[1] };
}
// Token bucket z Lua
const tokenBucketScript = `
local key = KEYS[1]
local capacity = tonumber(ARGV[1]) -- pojemność wiadra
local refillRate = tonumber(ARGV[2]) -- tokenów na sekundę
local now = tonumber(ARGV[3])
local requested = tonumber(ARGV[4])
local bucket = redis.call('HMGET', key, 'tokens', 'lastRefill')
local tokens = tonumber(bucket[1]) or capacity
local lastRefill = tonumber(bucket[2]) or now
-- Uzupełnij tokeny w oparciu o czas, który minął
local elapsed = (now - lastRefill) / 1000
tokens = math.min(capacity, tokens + elapsed * refillRate)
if tokens >= requested then
tokens = tokens - requested
redis.call('HMSET', key, 'tokens', tokens, 'lastRefill', now)
redis.call('EXPIRE', key, 3600)
return 1 -- allowed
else
return 0 -- denied
end
`;
// Express middleware
app.use(async (req, res, next) => {
const { allowed, remaining } = await checkRateLimit(req.ip, 100, 60000);
res.setHeader('X-RateLimit-Remaining', remaining);
if (!allowed) return res.status(429).json({ error: 'Too many requests' });
next();
});
Materiały
- Redis rate limiting patterns
- Stripe: Scaling your API with rate limiters
- Cloudflare: Counting things, a lot of different things