Usare Redis in Laravel

Redis è un datastore in-memory estremamente veloce, spesso usato come cache, broker per code (queue), store per sessioni e per operazioni atomiche (lock, contatori, rate limiting). Laravel lo integra in modo nativo: puoi usarlo tramite i driver di Cache, Queue, Session e tramite il client Redis per operazioni dirette.

Prerequisiti e scenari d’uso

  • Cache: ridurre query e calcoli ripetuti, memorizzare risposte e risultati.
  • Queue: eseguire job asincroni (email, export, elaborazioni) con un broker veloce.
  • Sessioni: archiviare sessioni su un backend condiviso in ambienti multi-istanza.
  • Lock distribuiti: coordinare processi concorrenti ed evitare race condition.
  • Contatori e rate limiting: incrementi atomici, limiti di frequenza e metriche.
  • Pub/Sub: messaggistica in tempo reale o notifiche interne (con i giusti caveat).

Installazione di Redis

In sviluppo, il modo più semplice è usare Docker. In produzione, spesso Redis viene gestito come servizio dedicato (VM o managed service). Ecco un esempio rapido con Docker:

docker run --name redis -p 6379:6379 -d redis:7-alpine
# (opzionale) vedere i log
docker logs -f redis

Se usi Docker Compose insieme a Laravel:

services:
  app:
    build: .
    depends_on:
      - redis
  redis:
    image: redis:7-alpine
    ports:
      - "6379:6379"

Client Redis in Laravel: Predis o PhpRedis

Laravel può usare:

  • PhpRedis (estensione PHP): in genere più performante e consigliata in produzione.
  • Predis (libreria PHP): semplice da installare via Composer quando non puoi usare estensioni.

Opzione A: PhpRedis (consigliata)

Installa l’estensione (il comando dipende dal sistema). Esempio tipico su Linux:

# esempio generico; i pacchetti cambiano tra distro
sudo apt-get update
sudo apt-get install -y php-redis

In .env imposta il client:

REDIS_CLIENT=phpredis

Opzione B: Predis

Installa via Composer e imposta il client:

composer require predis/predis
REDIS_CLIENT=predis

Configurazione in Laravel

Laravel legge la configurazione da config/database.php nella sezione redis. In genere è sufficiente impostare le variabili in .env:

REDIS_HOST=127.0.0.1
REDIS_PASSWORD=null
REDIS_PORT=6379
REDIS_DB=0
REDIS_CACHE_DB=1

Se Redis richiede autenticazione:

REDIS_PASSWORD=unaPasswordMoltoForte

Se usi Docker Compose e il servizio si chiama redis, imposta:

REDIS_HOST=redis

Prefissi e separazione dei database

Laravel usa spesso database Redis diversi per isolare i dati: uno per le operazioni generiche e uno per la cache. Puoi anche usare un prefix per evitare collisioni tra applicazioni che condividono lo stesso Redis.

REDIS_PREFIX=miaapp_database_
CACHE_PREFIX=miaapp_cache_

Nota: i prefissi reali dipendono dalla configurazione (cache e redis). In molte installazioni Laravel applica un prefisso automatico basato su APP_NAME se non lo imposti.

Usare Redis come cache

Imposta Redis come driver della cache:

CACHE_DRIVER=redis

Esempi tipici con la facade Cache:

<?php

use Illuminate\Support\Facades\Cache;

// memorizza per 10 minuti
Cache::put('users.count', 123, now()->addMinutes(10));

// recupera con default
$count = Cache::get('users.count', 0);

// remember: calcola e salva solo se mancante
$top = Cache::remember('users.top', now()->addMinutes(5), function () {
    return \App\Models\User::query()
        ->orderByDesc('score')
        ->take(10)
        ->get();
});

// invalidazione
Cache::forget('users.count');

Tag (attenzione alla compatibilità)

I tag della cache sono supportati da Redis, ma la semantica cambia a seconda del driver; inoltre possono aumentare la complessità e la memoria usata. Se li usi, mantieni i tag piccoli e coerenti.

<?php

use Illuminate\Support\Facades\Cache;

Cache::tags(['users', 'leaderboard'])->put('top10', $top, now()->addMinutes(5));

$top10 = Cache::tags(['users', 'leaderboard'])->get('top10');

Cache::tags(['users'])->flush();

Cache di query e N+1

Redis non sostituisce la corretta progettazione del database. Prima riduci N+1 con eager loading e indici; poi usa cache per i risultati più richiesti e con invalidazione chiara (ad esempio su eventi di modello).

Usare Redis per le sessioni

In ambienti con più istanze (load balancer) salvare le sessioni su Redis evita “logout” casuali e problemi di sticky sessions. Imposta:

SESSION_DRIVER=redis
SESSION_CONNECTION=default

Per alcune applicazioni è utile definire un DB Redis dedicato alle sessioni (o un prefisso dedicato), specialmente se anche queue e cache condividono Redis.

Usare Redis per le code (Queue)

Redis è uno dei backend più comuni per la queue di Laravel. Imposta:

QUEUE_CONNECTION=redis

Configura code multiple e priorità in config/queue.php (di solito non serve modificare se usi il default). Esegui un worker:

php artisan queue:work
# oppure per ambienti di produzione con supervisore
php artisan queue:work --sleep=1 --tries=3 --timeout=120

Esempio di Job

<?php

namespace App\Jobs;

use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Foundation\Bus\Dispatchable;
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Queue\SerializesModels;

class SendReportEmail implements ShouldQueue
{
    use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;

    public function __construct(public int $userId) {}

    public function handle(): void
    {
        // invia email, genera report, ecc.
    }
}

Dispatch:

<?php

use App\Jobs\SendReportEmail;

SendReportEmail::dispatch($user->id)->onQueue('emails');

Laravel Horizon

Se la tua queue usa Redis e vuoi un pannello di controllo con metriche e gestione di processi/queue, Horizon è spesso la scelta migliore. È particolarmente utile quando hai molte code e worker da coordinare.

Operazioni Redis dirette con la facade Redis

Oltre ai driver (Cache/Queue/Session), puoi usare Redis come datastore key-value e strutture dati (liste, set, hash). Laravel espone la facade Redis.

<?php

use Illuminate\Support\Facades\Redis;

// stringhe
Redis::set('status', 'ok');
$status = Redis::get('status');

// TTL (secondi)
Redis::setex('otp:123', 60, '987654');

// incrementi atomici
$views = Redis::incr('page:home:views');

// hash
Redis::hset('user:1', 'name', 'Ada');
Redis::hset('user:1', 'email', 'ada@example.com');
$user = Redis::hgetall('user:1');

Connessioni e database

Puoi specificare una connessione configurata in config/database.php:

<?php

use Illuminate\Support\Facades\Redis;

$redis = Redis::connection('cache'); // ad es. un DB/prefix dedicato
$redis->set('key', 'value');

Pipelining per prestazioni

Quando devi fare molte operazioni, riduci i round-trip usando pipeline:

<?php

use Illuminate\Support\Facades\Redis;

$results = Redis::pipeline(function ($pipe) {
    for ($i = 1; $i <= 100; $i++) {
        $pipe->incr("counter:{$i}");
    }
});

// $results contiene gli output dei comandi

Transazioni e atomicità

Per operazioni che devono essere atomiche su più comandi, puoi usare transazioni (MULTI/EXEC). In alternativa, per logiche più complesse e sicure, valuta Lua script.

<?php

use Illuminate\Support\Facades\Redis;

$result = Redis::transaction(function ($tx) {
    $tx->incr('cart:1:items');
    $tx->expire('cart:1:items', 3600);
});

Lua script

Lua permette di eseguire logiche atomiche direttamente in Redis. Esempio: incrementa un contatore solo se non supera una soglia.

<?php

use Illuminate\Support\Facades\Redis;

$lua = <<<'LUA'
local current = redis.call('GET', KEYS[1])
if not current then current = 0 else current = tonumber(current) end

local next = current + tonumber(ARGV[1])
if next > tonumber(ARGV[2]) then
  return -1
end

redis.call('SET', KEYS[1], next)
redis.call('EXPIRE', KEYS[1], tonumber(ARGV[3]))
return next
LUA;

$value = Redis::eval($lua, 1, 'quota:user:1', 1, 100, 3600);

if ($value === -1) {
    // quota superata
}

Lock distribuiti con Cache::lock

Quando più processi possono eseguire la stessa operazione (es. generare un report, ricalcolare una cache), un lock evita concorrenza indesiderata. Laravel fornisce un’API semplice basata su Redis.

<?php

use Illuminate\Support\Facades\Cache;

$lock = Cache::lock('reports:daily', 60);

if ($lock->get()) {
    try {
        // sezione critica
    } finally {
        $lock->release();
    }
} else {
    // qualcun altro sta già lavorando
}

Se vuoi attendere il lock fino a un certo tempo:

<?php

use Illuminate\Support\Facades\Cache;

Cache::lock('reports:daily', 60)->block(10, function () {
    // acquisito entro 10 secondi
});

Rate limiting con Redis

Laravel usa spesso cache store per il rate limiting. Con Redis ottieni incrementi atomici e TTL affidabili. Esempio: definire un limiter personalizzato in App\Providers\RouteServiceProvider (o dove configuri i limitatori).

<?php

use Illuminate\Cache\RateLimiting\Limit;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\RateLimiter;

RateLimiter::for('api', function (Request $request) {
    $key = $request->user()?->id ?: $request->ip();
    return Limit::perMinute(60)->by($key);
});

Applicazione alle rotte:

<?php

use Illuminate\Support\Facades\Route;

Route::middleware(['throttle:api'])->group(function () {
    // rotte API
});

Pub/Sub e notifiche interne

Redis Pub/Sub è utile per comunicazioni effimere (messaggi non persistiti). Per eventi critici, preferisci una queue o un sistema di messaggistica persistente. Un esempio minimale:

<?php

use Illuminate\Support\Facades\Redis;

// Publisher
Redis::publish('events', json_encode(['type' => 'order.created', 'id' => 123]));

// Subscriber (script dedicato, non dentro una request web)
Redis::subscribe(['events'], function ($message) {
    // gestisci messaggio
});

Pattern utili: cache-aside, write-through e invalidazione

  • Cache-aside (consigliato): l’app legge dalla cache, se manca legge dal DB e scrive in cache (es. Cache::remember).
  • Write-through: l’app scrive su cache e DB insieme; richiede maggiore disciplina e gestione errori.
  • Invalidazione: definisci regole chiare (su eventi di dominio o observer dei modelli) per cancellare chiavi correlate.

Esempio semplice di invalidazione su evento modello:

<?php

namespace App\Observers;

use App\Models\User;
use Illuminate\Support\Facades\Cache;

class UserObserver
{
    public function saved(User $user): void
    {
        Cache::forget("user:{$user->id}");
    }
}

Testing: come gestire Redis nei test

Nei test conviene:

  • Usare un database Redis separato (es. REDIS_DB=15) e svuotarlo tra i test.
  • Mockare Cache/Redis quando stai testando logica pura e non integrazione.
  • Per test d’integrazione, eseguire Redis in container nel CI.

Esempio: svuotare Redis a inizio suite (approccio semplice):

<?php

use Illuminate\Support\Facades\Redis;

beforeEach(function () {
    Redis::flushdb();
});

Osservabilità e manutenzione

  • Monitoraggio: controlla memoria, evictions, hit ratio, latenza, connessioni.
  • Politiche di eviction: se Redis ha limite di memoria, scegli una policy adatta (es. LRU/LFU) in base al tipo di carico.
  • Dimensionamento: tieni separati i carichi (cache vs queue) se crescono molto, anche con istanze Redis diverse.
  • Persistenza: per cache pura spesso non serve persistere; per altri usi valuta RDB/AOF in base a requisiti e costi.

Sicurezza

  • Non esporre Redis su internet: usa rete privata/VPC e firewall.
  • Imposta password e, se disponibile, TLS (soprattutto su servizi managed o reti non fidate).
  • Usa prefissi e DB separati per ridurre rischi di collisione e impatti tra componenti.
  • Evita di salvare dati sensibili in chiaro; per sessioni valuta anche cifratura applicativa se necessario.

Checklist rapida

  1. Installa Redis (Docker/servizio) e rendilo raggiungibile dall’app.
  2. Scegli il client: phpredis se possibile, altrimenti predis.
  3. Configura .env: host, porta, password, DB dedicati.
  4. Imposta i driver: CACHE_DRIVER=redis, QUEUE_CONNECTION=redis, SESSION_DRIVER=redis se serve.
  5. Usa Cache::remember e invalidazione chiara; usa Cache::lock per sezioni critiche.
  6. Ottimizza round-trip con pipeline e usa Lua per logiche atomiche complesse.
  7. Monitora metriche e imposta limiti/eviction policy coerenti con il tuo carico.

Con queste basi puoi integrare Redis in Laravel in modo affidabile e scalabile: parti dalla cache, poi sposta su Redis queue e sessioni quando l’architettura lo richiede, e usa operazioni dirette (lock, contatori, hash) quando servono atomicità e performance.

Torna su