Non è "un database più veloce": è una cassetta degli attrezzi in RAM con strutture dati che un RDBMS non ha. Cache, sessioni, codeine, pub/sub, rate limiting — e i comandi giusti per non farlo esplodere in produzione.
"Ho lanciato KEYS * su 40 milioni di chiavi in produzione" — il modo più rapido per scoprire che Redis è single-threaded anche quando non vorresti che lo fosse.
REmote DIctionary Server: un archivio chiave-valore che vive (quasi sempre) tutto in RAM, con strutture dati molto più ricche di un semplice "stringa → stringa".
Un RDBMS come PostgreSQL (vedi la guida Database) è l'archivio in cantina: organizzato, durevole, con un registro che non perde nulla nemmeno se salta la corrente, ma per prendere un faldone devi scendere le scale. Redis è il blocco di post-it sulla tua scrivania: tutto a portata di mano, velocissimo da leggere e scrivere, ma se la scrivania prende fuoco (il processo muore) senza aver salvato una copia, i post-it non ci sono più. Redis non sostituisce l'archivio: gli sta accanto, per tutto quello che deve essere letto e scritto migliaia di volte al secondo senza scendere le scale ogni volta.
Redis esegue i comandi uno alla volta su un singolo thread principale (dalla versione 6 in poi alcune operazioni di I/O sono multi-thread, ma l'esecuzione dei comandi resta single-threaded). Sembra una limitazione, mentre è il segreto della sua velocità: zero lock, zero context switch tra thread, zero complessità di concorrenza. Dato che tutto vive in RAM (niente attesa del disco) e ogni comando è pensato per essere O(1) o O(log n), un singolo core basta e avanza per centinaia di migliaia di operazioni al secondo.
| Uso tipico | Perché Redis ci sta bene |
|---|---|
| Cache | Risparmia query costose al database "vero", risposte in microsecondi |
| Session store | Sessioni web condivise tra più server applicativi, con TTL nativo |
| Rate limiting | Contatori atomici con scadenza, perfetti per "max N richieste al minuto" |
| Code & job queue | Liste o Stream come coda di lavoro tra produttori e worker |
| Leaderboard | Sorted Set: classifiche ordinate per punteggio, aggiornate in tempo reale |
| Pub/Sub | Notifiche in tempo reale tra servizi, senza infrastruttura dedicata |
Redis si installa in due minuti. Il config file un po' meno immediato, ma le opzioni che contano davvero sono poche.
sudo apt install redis-server
sudo systemctl status redis-server
redis-cli ping # risposta attesa: PONG
redis-cli # locale, porta di default
redis-cli -h 10.0.0.5 -p 6379 -a segreta
redis-cli -n 1 # seleziona il DB numero 1 (0-15 di default)
bind 127.0.0.1 -::1 # su quali interfacce ascolta (vedi cap. 11 per esporlo in rete)
port 6379
daemonize no # su systemd resta no, e' systemd a gestire il processo
supervised systemd
dir /var/lib/redis # dove finiscono i file di persistenza (RDB/AOF, cap. 5)
logfile /var/log/redis/redis-server.log
maxmemory 2gb # limite di RAM utilizzabile (cap. 6)
maxmemory-policy allkeys-lru
requirepass segreta # AUTH, vedi cap. 11
databases), selezionabili con SELECT n o redis-cli -n n. Non sono "database" nel senso relazionale: sono solo namespace separati nello stesso processo, utili per non far collidere le chiavi di applicazioni diverse sulla stessa istanza.
La differenza rispetto a "un dizionario in RAM" è qui: ogni valore può essere una struttura dati ricca, non solo una stringa.
| Tipo | Cos'è | Comandi tipici |
|---|---|---|
| String | Testo, numeri, o dati binari (fino a 512MB) | SET GET INCR APPEND |
| List | Lista ordinata, ottima come coda/stack | LPUSH RPUSH LRANGE LPOP |
| Hash | Mappa campo→valore, come un "oggetto" | HSET HGET HGETALL |
| Set | Insieme di elementi unici, non ordinato | SADD SMEMBERS SINTER |
| Sorted Set (ZSET) | Come un Set, ma ogni elemento ha un punteggio che lo ordina | ZADD ZRANGE ZINCRBY |
| Bitmap | Stringa trattata come vettore di bit, efficientissima per flag | SETBIT GETBIT BITCOUNT |
| HyperLogLog | Conteggio approssimato di elementi unici, pochi byte fissi | PFADD PFCOUNT |
| Stream | Log append-only con consumer group, simile a un mini-Kafka | XADD XREAD XGROUP |
HSET utente:42 nome "Mario" email "[email protected]" eta 34
HGET utente:42 email
HGETALL utente:42
HINCRBY utente:42 eta 1
ZADD classifica 1500 "mario" 2200 "luigi"
ZINCRBY classifica 300 "mario"
ZREVRANGE classifica 0 2 WITHSCORES -- top 3, punteggio piu' alto prima
ZRANK classifica "mario" -- posizione in classifica
Le operazioni trasversali a tutti i tipi: quanto vive una chiave, come cercarle senza bloccare tutto, e come restare atomici.
SET sessione:abc123 "dati_utente" EX 3600 # scade tra 1 ora
EXPIRE sessione:abc123 7200 # cambia la scadenza a 2 ore
TTL sessione:abc123 # secondi rimanenti (-1 = mai, -2 = non esiste)
PERSIST sessione:abc123 # rimuove la scadenza
Il TTL è il motivo per cui Redis è la scelta naturale per sessioni web e cache: la scadenza è gestita dal server stesso, senza bisogno di un job esterno che ripulisce i dati vecchi.
KEYS * su un database con milioni di chiavi blocca ogni altro client finché la scansione completa non finisce — secondi o minuti di sistema fermo. Usa sempre SCAN, che itera un cursore alla volta senza bloccare: SCAN 0 MATCH utente:* COUNT 100. Non è una best practice opzionale, è la differenza tra un comando innocuo e un incidente.
Molti comandi Redis (INCR, SETNX, HINCRBY) sono già atomici da soli, proprio perché single-threaded: nessun altro client può "intromettersi" a metà. Per raggruppare più comandi in un'unica unità atomica si usa MULTI/EXEC (una transazione "ottimistica": mette in coda i comandi e li esegue tutti insieme, senza però un vero rollback se uno fallisce a runtime).
MULTI
INCR contatore:visite
EXPIRE contatore:visite 86400
EXEC
EVAL): girano interamente sul server, come un singolo comando atomico, senza round-trip multipli col client.
Redis vive in RAM, ma questo non significa che i dati spariscano per forza a ogni riavvio. Due strategie, con compromessi opposti.
| RDB (snapshot) | AOF (Append-Only File) | |
|---|---|---|
| Cosa salva | Una fotografia completa della memoria, a intervalli | Ogni comando di scrittura, in sequenza |
| Dati persi in caso di crash | Tutto quello dopo l'ultimo snapshot (minuti) | Quasi nulla, dipende dalla policy fsync |
| Dimensione file | Compatto (binario, compresso) | Più grande (log di comandi), ma si riscrive/comprime periodicamente |
| Velocità di restart | Veloce (carica una fotografia) | Più lento (riapplica il log) |
| Impatto in scrittura | Basso (fork periodico) | Più alto se fsync always |
save 900 1 # snapshot se >=1 chiave cambiata in 900s
save 300 10 # o >=10 chiavi cambiate in 300s
save 60 10000 # o >=10000 chiavi cambiate in 60s
Redis fa fork() del processo per scrivere lo snapshot senza bloccare le operazioni normali (copy-on-write): efficiente, ma su dataset enormi il fork stesso può causare un breve picco di latenza.
appendonly yes
appendfsync everysec # default: compromesso ragionevole
-- appendfsync always : zero perdita dati, molto piu' lento
-- appendfsync no : lascia decidere al SO, veloce ma rischioso
Il file AOF cresce continuamente; Redis lo riscrive periodicamente in forma compatta (BGREWRITEAOF) senza perdere lo storico delle chiavi attuali.
save "") e vivere felice: dipende da quanto ti costa "ricalda" la cache da zero.
Redis vive tutto in RAM: prima o poi la RAM finisce, e devi decidere cosa succede quando è piena.
| Policy | Comportamento |
|---|---|
noeviction | Rifiuta nuove scritture con errore (default). Sicuro ma può rompere l'app se non gestito |
allkeys-lru | Butta via le chiavi usate meno di recente, tra tutte. Tipico per cache pura |
volatile-lru | Come sopra, ma solo tra le chiavi con un TTL impostato |
allkeys-lfu | Butta via le chiavi usate meno frequentemente (non solo meno di recente) |
volatile-ttl | Butta via prima le chiavi con TTL più vicino alla scadenza |
noeviction è quasi sempre la scelta giusta: meglio un errore esplicito all'applicazione che perdere silenziosamente dati che pensavi persistenti. allkeys-* ha senso solo se ogni chiave è davvero rigenerabile da un'altra fonte.
redis-cli INFO memory
redis-cli MEMORY USAGE nomechiave
redis-cli --bigkeys # scansiona e trova le chiavi piu' grandi per tipo
Due modi diversi di far comunicare servizi tramite Redis, con garanzie molto diverse tra loro.
-- Un client in ascolto:
SUBSCRIBE notifiche
-- Un altro client pubblica:
PUBLISH notifiche "nuovo ordine #42"
Semplice ed efficace, ma senza persistenza: se nessun client è in ascolto in quel momento, il messaggio è perso per sempre. Va bene per notifiche "best effort" (invalidazione cache tra più server), non per nulla che deve essere garantito.
XADD ordini '*' cliente_id 42 totale 150
XREAD COUNT 10 STREAMS ordini 0
-- Consumer group: piu' worker si dividono il carico
XGROUP CREATE ordini worker_grp '$'
XREADGROUP GROUP worker_grp worker1 COUNT 1 STREAMS ordini '>'
XACK ordini worker_grp <id-messaggio>
A differenza del Pub/Sub, gli Stream persistono i messaggi (soggetti comunque a RDB/AOF) e permettono a un consumer di riconnettersi e riprendere da dove aveva lasciato: molto più vicino a una vera coda di messaggi (concettualmente simile a Kafka, in scala più piccola).
Le ricette pratiche che coprono il 90% di quello per cui la gente installa Redis.
valore = redis.GET("prodotto:42")
if valore is None:
valore = database.query("SELECT * FROM prodotti WHERE id=42")
redis.SET("prodotto:42", valore, EX=300) # cache per 5 minuti
return valore
L'applicazione controlla prima la cache; solo se manca ("cache miss") interroga il database "vero" e ripopola Redis. Semplice, ma richiede di pensare a come invalidare la chiave quando il dato cambia (es. DEL prodotto:42 dopo un UPDATE).
MULTI
INCR rate:utente42:2026-07-06T14:32
EXPIRE rate:utente42:2026-07-06T14:32 60
EXEC
-- se il risultato di INCR > 100, rifiuta la richiesta
SET lock:job42 "worker-1" NX EX 30
-- NX = solo se non esiste gia': solo un worker "vince"
-- EX 30 = si autolibera se il worker crasha senza rilasciarlo
DEL lock:job42 -- rilascio esplicito a lavoro finito
SET NX EX funziona bene per la maggior parte dei casi, ma non è a prova di ogni scenario patologico (failover del nodo Redis proprio mentre il lock è attivo, clock drift). L'algoritmo Redlock proposto da Redis per i casi multi-nodo è stato oggetto di un noto dibattito tecnico pubblico tra lo stesso autore di Redis e altri esperti di sistemi distribuiti sulla sua reale robustezza in scenari di clock drift e GC pause. Se un lock che fallisce causa danni seri (non solo un piccolo doppio-processamento), valuta uno strumento pensato apposta per il consenso distribuito (etcd, ZooKeeper) invece di forzare Redis a fare quel lavoro.
Un solo nodo Redis è anche un solo punto di guasto (vedi la guida Alta Affidabilità per il concetto generale). Ecco come Redis lo risolve.
# Sul nodo replica, a runtime:
REPLICAOF 10.0.0.10 6379
# Oppure in redis.conf, permanente:
replicaof 10.0.0.10 6379
masterauth segreta
# Tornare master (es. dopo un failover manuale)
REPLICAOF NO ONE
La replica è asincrona: il master non aspetta conferma dal replica prima di rispondere al client, quindi in caso di crash improvviso del master puoi perdere le ultimissime scritture non ancora propagate. È un compromesso deliberato per non sacrificare la latenza, coerente con l'uso tipico di Redis (cache, non registro bancario).
Sentinel è un processo separato (tipicamente 3 o più istanze, per avere quorum come nella guida Alta Affidabilità) che monitora master e replica, e se il master smette di rispondere, promuove automaticamente un replica a nuovo master e riconfigura gli altri replica di conseguenza.
sentinel monitor mymaster 10.0.0.10 6379 2 # 2 = quorum minimo per dichiarare il master giu'
sentinel down-after-milliseconds mymaster 5000
sentinel failover-timeout mymaster 60000
sentinel auth-pass mymaster segreta
redis-cli -p 26379 SENTINEL get-master-addr-by-name mymaster
Sentinel risolve la disponibilità. Redis Cluster risolve un problema diverso: i dati non entrano più nella RAM di una sola macchina.
Redis Cluster divide lo spazio delle chiavi in 16384 hash slot fissi. Ogni chiave viene assegnata a uno slot tramite CRC16(chiave) % 16384, e ogni nodo del cluster è responsabile di un sottoinsieme di slot. Un client che scrive una chiave viene automaticamente indirizzato (o reindirizzato, con un MOVED) al nodo giusto.
redis-cli --cluster create \
10.0.0.1:6379 10.0.0.2:6379 10.0.0.3:6379 \
10.0.0.4:6379 10.0.0.5:6379 10.0.0.6:6379 \
--cluster-replicas 1
redis-cli -c -p 6379 cluster info
redis-cli -c -p 6379 cluster nodes
MGET su chiavi sparse su slot diversi falliscono in Redis Cluster. La soluzione: le hash tag — racchiudi la parte "da hashare" tra graffe, es. ordini:{cliente42}:lista e ordini:{cliente42}:totale finiscono sempre sullo stesso slot perché Redis hasha solo la parte tra {}.
Redis storicamente si fidava della rete su cui girava. Su Internet aperto, questa fiducia è costata caro a migliaia di istanze.
authorized_keys della macchina host tramite CONFIG SET dir + SAVE, o cifravano i dati per riscatto. La lezione non è "Redis è insicuro": è che un datastore pensato per reti fidate va sempre messo dietro un firewall, mai esposto direttamente.
bind 127.0.0.1 10.0.0.5 # MAI 0.0.0.0 su una rete non fidata
protected-mode yes
requirepass una-password-lunga-e-vera
rename-command FLUSHALL "" # disabilita comandi distruttivi se non servono
rename-command CONFIG ""
ACL SETUSER app_readonly on >segreta \
~cache:* +get +exists -@all
ACL LIST
ACL WHOAMI
Prima di Redis 6 esisteva solo requirepass (tutto o niente). Le ACL permettono utenti dedicati con permessi ristretti a comandi e chiavi specifiche — lo stesso principio del minimo privilegio visto per i database relazionali.
requirepass: senza cifratura, la password stessa viaggia in chiaro sulla rete.
Redis è velocissimo finché non trovi il comando, la chiave enorme, o lo script Lua che lo rallenta per tutti.
CONFIG SET slowlog-log-slower-than 10000 -- microsecondi (10ms)
SLOWLOG GET 10
SLOWLOG RESET
redis-cli MONITOR
Mostra ogni comando eseguito, in tempo reale. Utilissimo per il debug puntuale, ma non lasciarlo mai acceso in produzione a lungo: l'overhead di stampare ogni comando rallenta il server sotto carico reale.
redis-cli INFO stats # hit/miss della cache, connessioni, comandi al secondo
redis-cli INFO memory # uso RAM, frammentazione
redis-cli INFO replication # stato master/replica, lag
redis-cli --latency # latenza media percepita in tempo reale
keyspace_hit_rate basso (in INFO stats: keyspace_hits vs keyspace_misses) su una cache spesso significa TTL troppo corti, o chiavi che cambiano così spesso da rendere la cache quasi inutile — vale la pena guardarlo prima di aggiungere più RAM pensando che risolva da solo.
SCAN invece di KEYS, sempre, in produzionemaxmemory e la eviction policy scelta consapevolmenteredis-cli --bigkeys periodico per scovare chiavi anomaleMONITOR lasciato acceso su un'istanza di produzionemaxmemory non impostato): rischio OOM del sistemaTutto quello che serve, su una pagina. Bookmark questa sezione e dimentica il resto.
SET k v EX 60
GET k
DEL k
EXISTS k
TTL k
SCAN 0 MATCH pattern:* COUNT 100
INCR contatore
LPUSH lista v1 v2 / LRANGE lista 0 -1
HSET h campo v / HGETALL h
SADD s v1 v2 / SMEMBERS s
ZADD z 10 v1 / ZRANGE z 0 -1 WITHSCORES
REPLICAOF host porta
INFO replication
redis-cli -p 26379 SENTINEL get-master-addr-by-name mymaster
redis-cli --cluster create ...
INFO stats
SLOWLOG GET 10
redis-cli --bigkeys
redis-cli --latency
MEMORY USAGE chiave
{} usata per forzare lo stesso slotSCAN invece di KEYS, metti sempre un TTL su quello che può scadere, non esporre mai Redis direttamente su Internet, e decidi consapevolmente se ti serve davvero persistere i dati o se è solo cache ricostruibile. Il resto — Cluster, Streams, Lua scripting — lo aggiungi quando il problema concreto arriva, non prima.