Quando più worker PHP possono eseguire contemporaneamente un’attività sulla stessa risorsa, un lock può ridurre il lavoro duplicato. È comune valutare questa soluzione per generare un report, elaborare un ordine o aggiornare una risorsa condivisa. Ma usare Redis non rende di per sé coerente un’operazione: il processo può essere sospeso, il lock può scadere e un altro worker può iniziare prima che il primo abbia terminato.
La decisione importante non riguarda solo come creare una chiave, ma quale proprietà deve essere protetta e quale sistema può farla rispettare. In molti casi, un vincolo univoco o una transazione sul database offrono una garanzia più diretta. I lock distribuiti con Redis in PHP sono utili quando coordinano processi che non possono essere gestiti con una singola operazione atomica sul database, purché siano progettati come protezione temporanea e non come fonte definitiva di verità.
Cosa risolve un lock distribuito e cosa non garantisce

Un lock distribuito consente a più processi, eventualmente su server diversi, di contendersi un’esclusione temporanea. Se un worker acquisisce il lock, gli altri dovrebbero astenersi dall’entrare nella sezione critica finché il lock è valido. È utile, per esempio, per impedire a due processi di avviare contemporaneamente un’attività costosa o per coordinare un lavoro che coinvolge servizi esterni.
Il lock non garantisce di per sé che esista una sola scrittura, che il processo proprietario sia ancora attivo o che Redis e il database mantengano uno stato coerente. È una concessione temporanea, simile a un lease. Se il worker si ferma per un periodo superiore alla durata del lock, Redis può consentire a un altro processo di acquisirlo anche se il primo continua a lavorare quando riprende.
Per questo, un’applicazione non dovrebbe affidarsi esclusivamente alla chiave Redis per proteggere una regola di business. L’operazione finale deve disporre di difese proprie: validazioni, vincoli di integrità, controllo delle versioni o idempotenza, a seconda del caso.
Quando usare Redis e quando scegliere il database
Prima di aggiungere un lock, individua la condizione da preservare. Se si tratta di impedire che un’entità abbia due record con lo stesso identificatore, un vincolo univoco sul database esprime e fa rispettare questa regola nel punto in cui i dati vengono scritti. Non dipende dal fatto che tutti i worker rispettino un protocollo esterno.
Se più modifiche devono essere confermate insieme, può bastare una transazione. Per evitare di sovrascrivere un aggiornamento concorrente, può essere adatto il controllo ottimistico: leggere una versione, aggiornare solo se non è cambiata e gestire esplicitamente il conflitto. Per operazioni su righe o insiemi di dati, può essere più appropriato il lock pessimistico del database stesso, anche se ambito e durata devono essere limitati.
Redis può essere una buona scelta quando l’esclusione coordina il lavoro tra processi e non esiste una singola operazione sul database in grado di risolvere il problema, oppure quando è necessario evitare che molti worker avviino contemporaneamente un’attività ripetuta. Valuta anche se il costo di attesa, nuovi tentativi e gestione di Redis sia giustificato. Se il problema si risolve con meno componenti tramite una chiave univoca, in genere è preferibile questa soluzione.
Acquisizione atomica, proprietario univoco e scadenza
L’acquisizione deve essere una singola operazione atomica: creare la chiave solo se non esiste e assegnarle una scadenza nella stessa operazione. In Redis, il pattern abituale usa SET con le opzioni equivalenti a “solo se non esiste” e un TTL. Separare la creazione della chiave dall’impostazione della scadenza introduce una race condition: il processo potrebbe fallire tra le due operazioni e lasciare un lock senza scadenza.
Il valore della chiave deve essere un token univoco generato per quel tentativo, non un valore costante condiviso da tutti i worker. Il token consente di identificare il proprietario e impedisce a un processo di eliminare per errore il lock acquisito successivamente da un altro. Il client PHP deve considerare acquisito il lock solo se Redis conferma il successo dell’operazione condizionale.
// Schema concettuale; adatta la chiamata alla libreria Redis utilizzata.
$token = generaTokenUnivoco();
$acquisito = redisImpostaSeNonEsisteConScadenza($chiave, $token, $durata);
if (!$acquisito) {
// Non entrare nella sezione critica.
}
La scadenza deve superare la durata prevista del lavoro, ma non essere così lunga da lasciare altri processi in attesa inutilmente in caso di arresto anomalo. Non esiste un valore universale: misura la durata effettiva, considera le variazioni e definisci cosa fare con i lavori che si avvicinano al limite. Anche il rinnovo di una concessione richiede di verificare che il token sia ancora quello del proprietario; un rinnovo alla cieca potrebbe prolungare il lock di un altro worker.
Rilascio sicuro e rischi di scadenza
Non eliminare la chiave con una semplice cancellazione al termine del lavoro. Se il lock originale è scaduto e un altro processo ne ha acquisito uno nuovo, il primo worker potrebbe eliminare la chiave del secondo. Il rilascio deve confrontare il valore memorizzato con il token del proprietario ed eliminare la chiave solo se coincidono, come parte di un’operazione atomica. Si può implementare tramite uno script eseguito in Redis o una primitiva equivalente che confronti ed elimini senza intercalare altre operazioni.
Anche con acquisizione e rilascio corretti, una lunga sospensione del processo può causare scritture concorrenti. Il worker A acquisisce il lock, viene sospeso e il suo lease scade; B lo acquisisce e scrive; A riprende e scrive a sua volta. Peggio ancora, una partizione di rete o un errore di connessione possono impedire al client di sapere se l’operazione di acquisizione è stata eseguita.
Se una scrittura tardiva può causare danni, la risorsa protetta deve rifiutarla in modo indipendente. Una strategia consiste nell’usare un numero di fencing o una versione crescente e richiedere al sistema che riceve la scrittura di scartare i valori obsoleti. Questo contatore deve garantire le proprietà di ordinamento e durabilità richieste dal caso; non si deve presumere automaticamente che un contatore Redis costituisca una garanzia senza considerare riavvii o failover. Un’altra opzione è verificare una versione nel database all’interno della stessa operazione di scrittura.
Sezione critica, contesa ed errori di acquisizione
Mantieni nel lock solo il lavoro che richiede davvero l’esclusione. Evita di includervi chiamate lente a servizi esterni, attese dell’utente o elaborazioni indipendenti. Più dura la sezione critica, più aumentano la contesa e la possibilità di superare il tempo di scadenza. Se il lavoro può essere suddiviso, separa il breve coordinamento dalle attività che non dipendono dallo stato condiviso.
Se non si riesce ad acquisire il lock, è meglio non riprovare all’infinito né tenere occupati i worker PHP con lunghe attese. Definisci una policy in base all’importanza del lavoro: rinunciare e registrare il conflitto, riprovare con un’attesa limitata oppure inviare l’attività a una coda per elaborarla in seguito. I nuovi tentativi devono essere sicuri e idempotenti, e il sistema deve distinguere una normale contesa da un errore di Redis.
In caso di connessione incerta, non dare per scontato né che il lock sia stato acquisito né che non lo sia stato. Progetta il flusso in modo che possa recuperare senza eseguire due volte un’azione irreversibile: per esempio, usa chiavi di idempotenza nell’operazione di business e controllane il risultato prima di ripeterla.
Test e segnali operativi
Verifica il comportamento con più worker e controlla le intercalazioni, non solo il caso ideale. Includi scenari in cui il processo termina prima del rilascio, la scadenza si verifica durante il lavoro, Redis non risponde e un worker obsoleto tenta di rilasciare il lock dopo che un altro lo ha acquisito. Verifica che il token impedisca di eliminare una chiave altrui e che le scritture tardive vengano rifiutate quando l’applicazione richiede questa garanzia.
In produzione, monitora le acquisizioni fallite, la durata delle sezioni critiche, le scadenze, i rinnovi, gli errori di connessione e i nuovi tentativi. Un aumento della contesa può indicare che la granularità del lock è troppo ampia o che al suo interno viene svolto più lavoro del necessario. Scadenze frequenti durante le normali attività indicano che occorre rivedere il tempo di concessione o la progettazione del processo, non semplicemente aumentare automaticamente il TTL.
Checklist per decidere

- Quale regola di business viene protetta e può essere espressa con un vincolo o una transazione sul database?
- L’acquisizione è atomica, condizionale e accompagnata da una scadenza?
- Ogni tentativo ha un token univoco e il rilascio verifica il proprietario prima di eliminare la chiave?
- Cosa succede se il processo viene sospeso per un periodo superiore al lease o Redis perde la connessione?
- Il sistema che riceve la scrittura può rifiutare versioni obsolete o duplicati?
- Esiste una policy limitata per attesa, nuovi tentativi e ripristino, oltre a metriche e test di concorrenza?
Redis offre coordinamento temporaneo, non una garanzia universale di coerenza. Usalo quando il problema richiede davvero di coordinare processi distribuiti e mantieni la protezione definitiva nel sistema che scrive i dati. Se la regola può essere imposta più semplicemente nel database, inizia da lì.



