Memcached è un sistema di caching in-memory distribuito, pensato per ridurre latenza e carico su database e servizi esterni tramite l’uso di una cache veloce e volatile. Laravel lo supporta nativamente come driver di cache (e, in molti casi, anche per sessioni e lock), rendendolo una scelta solida quando serve semplicità operativa e prestazioni elevate.
Quando scegliere Memcached
- Cache di dati caldi: risultati di query, payload API, configurazioni calcolate, feature flag, ecc.
- Carichi con molte letture: Memcached rende molto rapide le letture rispetto a database o file system.
- Scaling orizzontale semplice: puoi aggiungere nodi Memcached e usare client-side hashing per distribuire le chiavi.
- Cache “best effort”: se perdi il contenuto (riavvio nodo), l’app può rigenerarlo.
In generale, Memcached è ottimo per caching semplice e a bassa latenza. Se ti servono strutture dati avanzate, persistenza o funzionalità più ricche (stream, pub/sub, script), Redis può essere più adatto.
Prerequisiti
1) Estensione PHP memcached (non memcache)
Laravel usa tipicamente l’estensione memcached (basata su libmemcached). Verifica che sia installata e abilitata:
php -m | grep -i memcached
Se non compare, dovrai installarla via package manager (dipende dal sistema operativo/distribuzione) o tramite PECL. Se usi Docker, è comune installare le dipendenze e compilare l’estensione nel Dockerfile.
2) Server Memcached
Puoi avviare Memcached localmente o via container. Esempio con Docker:
docker run --name memcached -p 11211:11211 -d memcached:1.6
Per ambienti di produzione, valuta più nodi e rete privata. Memcached di default non offre cifratura e autenticazione robusta in tutti gli scenari: è fondamentale limitarne l’accesso a una rete fidata.
Configurare Memcached in Laravel
Laravel centralizza la configurazione cache nel file config/cache.php. Il driver si imposta tramite variabili d’ambiente, tipicamente in .env.
1) Impostare il driver
CACHE_DRIVER=memcached
Per Laravel più recenti potresti trovare CACHE_STORE al posto di CACHE_DRIVER a seconda del progetto. Usa la chiave prevista dalla tua versione, ma il concetto non cambia: selezionare lo store memcached.
2) Definire i server Memcached
In config/cache.php è presente una sezione dedicata:
<?php
return [
// ...
'stores' => [
'memcached' => [
'driver' => 'memcached',
'persistent_id' => env('MEMCACHED_PERSISTENT_ID'),
'sasl' => [
env('MEMCACHED_USERNAME'),
env('MEMCACHED_PASSWORD'),
],
'options' => [
// Opzioni del client Memcached (facoltative)
// \Memcached::OPT_CONNECT_TIMEOUT => 200,
// \Memcached::OPT_RETRY_TIMEOUT => 2,
// \Memcached::OPT_NO_BLOCK => true,
// \Memcached::OPT_TCP_NODELAY => true,
],
'servers' => [
[
'host' => env('MEMCACHED_HOST', '127.0.0.1'),
'port' => env('MEMCACHED_PORT', 11211),
'weight' => 100,
],
],
],
],
// ...
];
Nel file .env potresti avere:
MEMCACHED_HOST=127.0.0.1
MEMCACHED_PORT=11211
MEMCACHED_PERSISTENT_ID=laravel_cache
MEMCACHED_USERNAME=
MEMCACHED_PASSWORD=
Nota su SASL: l’autenticazione SASL dipende dal supporto del tuo setup. In molti deployment si preferisce proteggere Memcached con rete privata e firewall piuttosto che esporlo pubblicamente.
3) Più nodi Memcached
Per distribuire il carico e aumentare la capacità, aggiungi più server nella lista:
'servers' => [
['host' => '10.0.1.10', 'port' => 11211, 'weight' => 100],
['host' => '10.0.1.11', 'port' => 11211, 'weight' => 100],
['host' => '10.0.1.12', 'port' => 11211, 'weight' => 100],
],
La distribuzione delle chiavi tra nodi avviene lato client (hashing). Se cambi il numero di nodi, una parte delle chiavi “migrerà” (cache miss temporanei). In contesti critici, pianifica l’operazione e monitora i miss rate.
Usare la cache in Laravel con Memcached
Una volta configurato lo store, Laravel espone un’API uniforme tramite la facade Cache.
Operazioni base
use Illuminate\Support\Facades\Cache;
// Scrivere in cache per 10 minuti
Cache::put('users:count', 123, now()->addMinutes(10));
// Leggere dalla cache (null se non presente)
$count = Cache::get('users:count');
// Leggere con default
$count = Cache::get('users:count', 0);
// Verificare esistenza
if (Cache::has('users:count')) {
// ...
}
// Rimuovere
Cache::forget('users:count');
Cache “remember”
Il pattern più comune è calcolare un valore e memorizzarlo solo se assente:
$users = Cache::remember('users:active', now()->addMinutes(5), function () {
return \App\Models\User::query()
->where('active', true)
->orderByDesc('last_seen_at')
->take(50)
->get();
});
Se ti serve distinguere cache per tenant/lingua/utente, includi tali informazioni nella chiave (con attenzione alla cardinalità, per evitare cache troppo frammentata).
TTL e considerazioni pratiche
- TTL troppo lungo: rischi dati stantii; definisci invalidazioni o TTL ragionevoli.
- TTL troppo corto: aumentano ricalcoli e carico su DB (cache churn).
- Cache stampede: molte richieste simultanee su chiave scaduta possono causare picchi. Mitiga con lock o strategie di “stale-while-revalidate”.
Lock (evitare cache stampede)
Laravel offre lock atomici via Cache::lock() (supporto dipende dallo store). Un esempio tipico: rigenerare un valore solo da un processo alla volta.
use Illuminate\Support\Facades\Cache;
$value = Cache::get('report:daily');
if ($value === null) {
$lock = Cache::lock('lock:report:daily', 10); // 10 secondi
try {
if ($lock->get()) {
$value = Cache::remember('report:daily', now()->addMinutes(30), function () {
return app(\App\Services\ReportService::class)->buildDaily();
});
} else {
// Un altro processo sta rigenerando: fallback
$value = Cache::get('report:daily');
}
} finally {
optional($lock)->release();
}
}
Se il lock non è supportato dal tuo store o dalla tua versione, puoi migrare i lock su Redis o database mantenendo Memcached per la cache di lettura.
Cache tags
Le tag consentono di raggruppare chiavi e invalidarle in modo selettivo. Esempio: invalidare tutte le cache relative a un “catalogo” quando cambiano i prodotti.
use Illuminate\Support\Facades\Cache;
Cache::tags(['catalog', 'products'])->put('products:list', $list, now()->addMinutes(10));
$list = Cache::tags(['catalog', 'products'])->remember('products:list', now()->addMinutes(10), function () {
return \App\Models\Product::query()->where('active', 1)->get();
});
// Invalidazione mirata
Cache::tags(['catalog', 'products'])->flush();
Se nel tuo progetto l’uso delle tag è centrale, verifica che il driver effettivamente le supporti nel tuo stack (versione di Laravel + estensione PHP + configurazione). In caso di incompatibilità, Redis è spesso la scelta più “prevedibile” per questa funzionalità.
Usare Memcached per le sessioni
Memcached può essere usato anche per memorizzare sessioni, utile quando hai più istanze dell’app dietro un load balancer e vuoi sessioni condivise.
SESSION_DRIVER=memcached
Assicurati che config/session.php punti allo stesso cluster e valuta attentamente TTL e dimensione delle sessioni. Memcached ha limiti sulla dimensione dei singoli item: sessioni troppo grandi possono fallire in modo silenzioso o generare comportamenti inattesi.
Ottimizzare configurazione e prestazioni
Opzioni utili del client
Le opzioni vanno impostate in config/cache.php nella sezione options dello store Memcached. Esempi (scegli in base al contesto):
'options' => [
\Memcached::OPT_CONNECT_TIMEOUT => 200,
\Memcached::OPT_RETRY_TIMEOUT => 2,
\Memcached::OPT_NO_BLOCK => true,
\Memcached::OPT_TCP_NODELAY => true,
\Memcached::OPT_LIBKETAMA_COMPATIBLE => true, // hashing consistente compatibile (se utile)
],
- Timeout aggressivi aiutano a “fallire veloce” se un nodo non risponde.
- No-block + TCP_NODELAY può ridurre latenza in alcuni scenari.
- Hashing consistente riduce l’impatto quando cambiano i nodi, ma va valutato rispetto al client e alla tua strategia di scaling.
Chiavi: naming, versioning e invalidazione
- Prefissi: usa prefissi chiari (
users:,catalog:) per mantenere ordine. - Versioning: quando cambia la forma dei dati, cambia versione chiave (es.
products:list:v2). - Evita cardinalità esplosiva: caching per ogni combinazione di filtri può generare troppe chiavi.
Serializzazione e dimensione degli oggetti
Memcached memorizza blob serializzati. Oggetti Eloquent molto grandi, collezioni enormi o payload JSON voluminosi possono saturare memoria e peggiorare hit ratio. Preferisci:
- liste “snelle” (solo campi necessari);
- paginazione o limit;
- cache di ID e fetch dal DB solo quando serve;
- compressione solo se sai che aiuta (valuta costo CPU).
Osservabilità: metriche e strumenti
Per capire se la cache sta aiutando davvero, monitora:
- Hit rate (hit/miss);
- Evictions (quando Memcached espelle item per mancanza di memoria);
- Memory usage e items;
- Latency lato client.
Puoi interrogare lo stato con comandi come stats (via telnet o strumenti dedicati). Esempio rapido:
echo "stats" | nc 127.0.0.1 11211
In Laravel, affianca anche strumenti applicativi come logging mirato e tracing (ad esempio per individuare chiavi ad alta churn o endpoint con cache stampede).
Problemi comuni e troubleshooting
Errore: “No available servers”
- Verifica host/porta e che Memcached sia avviato.
- Controlla firewall e sicurezza di rete (security group, ACL, Docker network).
- Verifica che l’estensione PHP
memcachedsia caricata dal PHP usato da FPM/CLI.
Cache che “sparisce”
- Memcached è volatile: riavvii e deploy possono svuotare la cache.
- Evictions per memoria insufficiente: aumenta
-m(memoria) o ottimizza cosa metti in cache.
Sessioni instabili
- Sessioni troppo grandi possono superare i limiti di item size.
- TTL sessione incoerente con
SESSION_LIFETIME. - Load balancer: assicurati che tutti i nodi puntino allo stesso cluster Memcached.
Sicurezza essenziale
- Non esporre Memcached su Internet: mettilo in rete privata o bind su localhost se locale.
- Firewall e allowlist: consenti accesso solo dalle istanze applicative.
- Credenziali: se usi SASL, gestisci segreti tramite variabili d’ambiente e secret manager.
Esempio completo: caching di una rotta con invalidazione
Supponiamo un endpoint che restituisce il catalogo e va invalidato quando cambia un prodotto:
// routes/web.php oppure routes/api.php
use Illuminate\Support\Facades\Cache;
use App\Models\Product;
Route::get('/catalog', function () {
return Cache::tags(['catalog'])->remember('catalog:v1', now()->addMinutes(10), function () {
return Product::query()
->where('active', 1)
->orderBy('name')
->get(['id', 'name', 'price']);
});
});
Quando un prodotto cambia, invalidi la tag:
// App\Observers\ProductObserver.php
use Illuminate\Support\Facades\Cache;
use App\Models\Product;
class ProductObserver
{
public function saved(Product $product): void
{
Cache::tags(['catalog'])->flush();
}
public function deleted(Product $product): void
{
Cache::tags(['catalog'])->flush();
}
}
Ricorda di registrare l’observer (es. in AppServiceProvider):
use App\Models\Product;
use App\Observers\ProductObserver;
public function boot(): void
{
Product::observe(ProductObserver::class);
}
Checklist finale
- Estensione PHP
memcachedinstallata e attiva (FPM e CLI). - Memcached raggiungibile dalle istanze Laravel (rete e firewall).
CACHE_DRIVER(o store equivalente) impostato amemcached.- Server configurati in
config/cache.phpcon eventuali opzioni. - Chiavi ben progettate (prefissi, versioning) e invalidazione/TTL coerenti.
- Monitoraggio di hit rate, evictions, memoria e latenza.
Con questi passaggi, Memcached diventa un componente semplice ma potentissimo per migliorare reattività e scalabilità di un’app Laravel, mantenendo il codice pulito grazie all’API unificata del framework.