Redis prevede, tra le sue caratteristiche, anche le transazioni, meccanismo con il quale un gruppo di operazioni viene trattato come un unico blocco funzionale, imponendo che i comandi che ne fanno parte siano eseguiti:
- sequenzialmente, senza intromissioni dall'esterno;
- atomicamente: andranno in porto tutti o verranno annullati in gruppo.
I comandi che servono ad utilizzare le transazioni in Redis sono quattro: MULTI, EXEC, DISCARD e WATCH.
Eseguire una transazione
Una transazione prende il via all'invocazione di MULTI
. Questa funzione restituisce sempre il messaggio OK e da quel momento la transazione è dichiarata aperta. Questi i passi che ne regolano il funzionamento:
- inviamo il comando
MULTI
. Redis risponde OK; - digitiamo i comandi che devono far parte della sessione di lavoro. Redis risponde sempre QUEUED a significare che il comando non viene eseguito subito ma messo in coda;
- conclusione della transazione: si può scegliere se eseguire tutti i comandi con il comando
EXEC
o annullare la transazione conDISCARD
.
Vediamo un esempio al lavoro nella console:
> MULTI
OK
> LPUSH numeri 12
QUEUED
> LPUSH numeri 8
QUEUED
> LPUSH numeri 10
QUEUED
> LPUSH numeri 21
QUEUED
> EXEC
1) (integer) 1
2) (integer) 2
3) (integer) 3
4) (integer) 4
Le operazioni svolte mostrano una transazione portata a termine con successo seguendo i passi poco fa elencati: abbiamo usato MULTI
, svolto le operazioni di inserimento in testa a una lista, edd eseguito il tutto col comando EXEC
per la finalizzazione delle operazioni. Osserviamo il modo in cui Redis si è comportato: ogni comando di sessione ha ricevuto QUEUED come risposta, ma nessun altro output; eseguendo EXEC
, si è avviata l'esecuzione e si sono ottenuti tutti gli output relativi.
Dopo il salvataggio dei dati potremo vedere la lista così completata:
> LRANGE numeri 0 -1
1) "21"
2) "10"
3) "8"
4) "12"
Se al posto di EXEC
avessimo invocato DISCARD
, la transazione sarebbe stata abbandonata e non avremmo ottenuto alcun output. Nel seguente esempio, avviamo una transazione nella quale inseriamo una serie di stringhe in una lista. Eseguendo il comando DISCARD
, termina la transazione e, come si vede usando il comando GET
, nessuno dei dati risulta salvato:
> MULTI
OK
> LPUSH nomi Andrea
QUEUED
> LPUSH nomi Valerio
QUEUED
> LPUSH nomi Nadia
QUEUED
> LPUSH nomi Alessia
QUEUED
> DISCARD
OK
> GET nomi
(nil)
Errori nelle transazioni
Il comportamento di Redis di fronte ad errori nelle transazioni è differente a seconda che l'errore si sia verificato durante l'accodamento o in fase di esecuzione (a seguito, quindi, di un accodamento corretto).
Facciamo un esempio di errore in accodamento. Se, durante una transazione, inseriamo un comando sintatticamente errato l'accodamento non verrà permesso e verrà lanciato un comando di DISCARD
automatico:
> MULTI
OK
v LPUSH nomi Silvio
QUEUED
> LPUSH nomi Alessia
QUEUED
> LPUSH nomi
(error) ERR wrong number of arguments for 'lpush' command
> LPUSH nomi Irene
QUEUED
> EXEC
(error) EXECABORT Transaction discarded because of previous errors.
Si è inserito un comando errato in quanto privo di un argomento necessario. Come si vede, l'errore viene segnalato subito ma si può procedere con l'accodamento. Al momento di effettuare l'EXEC
, però, si viene avvisati che la transazione ha subìto un DISCARD
tanto che, ad una successiva verifica, la lista apparirà completamente vuota:
> LRANGE nomi 0 -1
(empty list or set)
I casi più comuni di errore in accodamento sono ricondubili alla sintassi dei comandi: invocazione di
funzioni non esistenti, argomenti errati o mancanti e via dicendo. Il comportamento appena visto vige in Redis dalla versione 2.6.5. In quelle precedenti, non veniva annullata la transazione, ma si eseguiva tutto ciò che era stato accodato correttamente. Sicuramente il comportamento attuale garantisce una maggiore coerenza delle transazioni e ne tutela l'atomicità.
Per completezza, vediamo cosa succede in caso di errore in fase di esecuzione:
> MULTI
OK
> SET valore 24
QUEUED
> SET altro_valore 25
QUEUED
> LPOP valore
QUEUED
> SET altro_valore_ancora 100
QUEUED
> EXEC
1) OK
2) OK
3) (error) WRONGTYPE Operation against a key holding the wrong kind of value
4) OK
In questo caso, l'accodamento è stato eseguito senza problemi in quanto l'uso del comando LPOP
era sintatticamente corretto: ciò che non andava bene era l'oggetto al quale veniva applicato (una stringa anzichè una lista). Al momento di eseguire il codice, ci accorgiamo che la transazione non viene annullata ma tutti i comandi corretti vengono eseguiti indipendentemente dal fatto che siano stati inoltrati prima o dopo quello errato. Sarà il client (il programma che interagisce con Redis) a dover decidere come gestire la comunicazione di errore.
È sufficiente inserire alcuni comandi come i seguenti per avere la certezza che le operazioni ordinate siano andate tutte in porto:
> GET valore
"24"
> GET altro_valore
"25"
> GET altro_valore_ancora
"100"
Comando WATCH e i lock
Il comando WATCH permette di applicare un
lock ad una o più chiavi, in modo che se il loro valore venisse modificato durante la transazione, questa verrebbe annullata. Affinché ciò avvenga è sufficiente invocare il comando WATCH
seguito dalle chiavi cui si applica il lock.
> WATCH valore
OK
> MULTI
OK
> SET valore2 34
QUEUED
> SET valore3 77
QUEUED
> EXEC
Se, prima di chiudere la transazione, un'altra connessione modifica la chiave valore la transazione non sarà completata e verrà restituito nil
per indicarne il fallimento. Possiamo svolgere una prova aprendo due sessioni diverse di un client Redis (ad esempio, due connessioni SSH verso il server). Nella prima costruiamo la transazione, con la seconda violiamo il lock prima dell'inoltro del comando EXEC
. Vediamole al lavoro.
Prima sessione: apre il lock con WATCH
e inizia la transazione:
> WATCH valore
OK
> MULTI
OK
> SET valore 100
QUEUED
Seconda sessione: modifica valore, alla cui chiave è stato applicato il lock:
> SET valore 200
Prima sessione: chiude la transazione:
> EXEC
(nil)
Piuttosto che rispondere con l'output di tutte le operazioni, EXEC
restituisce nil che certifica il fallimento della transazione con il suo annullamento. Alla chiave valore corrisponderà 200 e la modifica applicata in transazione sarà andata persa. Qualora svolgessimo l'esperimento senza impartire il comando WATCH
, prevarrebbe l'assegnazione imposta all'interno della transazione e, come esito di EXEC
, otterremmo l'output OK.