Disponibile dalla 2.8.0.
Complessità temporale: O(1) per ogni chiamata. O(N) per un’iterazione completa, incluse abbastanza chiamate di comando perché il cursore ritorni a 0. N è il numero di elementi all’interno della collezione.
Il comando SCAN e i comandi strettamente correlati SSCAN, HSCAN e ZSCAN sono usati per iterare in modo incrementale su una collezione di elementi.
- SCAN itera l’insieme di chiavi nel database Redis attualmente selezionato.
- SSCAN itera gli elementi di tipo Sets.
- HSCAN itera i campi di tipo Hash e i loro valori associati.
- ZSCAN itera gli elementi di tipo Sorted Set e i loro punteggi associati.
Siccome questi comandi permettono un’iterazione incrementale, restituendo solo un piccolo numero di elementi per chiamata, possono essere usati in produzione senza l’inconveniente di comandi come KEYS o SMEMBERS che possono bloccare il server per molto tempo (anche diversi secondi) quando vengono chiamati per grandi collezioni di chiavi o elementi.
Tuttavia, mentre i comandi bloccanti come SMEMBERS sono in grado di fornire tutti gli elementi che fanno parte di un Set in un dato momento, la famiglia di comandi SCAN offre solo garanzie limitate sugli elementi restituiti, poiché la collezione che iteriamo incrementalmente può cambiare durante il processo di iterazione.
Nota che SCAN, SSCAN, HSCAN e ZSCAN lavorano tutti in modo molto simile, quindi questa documentazione copre tutti e quattro i comandi. Tuttavia una differenza evidente è che nel caso di SSCAN, HSCAN e ZSCAN il primo argomento è il nome della chiave che contiene il valore di Set, Hash o Sorted Set. Il comando SCAN non ha bisogno di alcun argomento per il nome della chiave poiché itera le chiavi nel database corrente, quindi l’oggetto iterato è il database stesso.
- *Uso base di SCAN
- *Garanzie di SCAN
- *Numero di elementi restituiti ad ogni chiamata SCAN
- *L’opzione COUNT
- *L’opzione MATCH
- *L’opzione TYPE
- *Iterazioni multiple parallele
- *Terminare le iterazioni a metà
- *Chiamare SCAN con un cursore corrotto
- *Garanzia di terminazione
- *Perché SCAN può restituire tutti gli elementi di un tipo di dati aggregati in una sola chiamata?
- *Return value
- *History
- *Additional examples
*Uso base di SCAN
SCAN è un iteratore basato sul cursore. Questo significa che ad ogni chiamata del comando, il server restituisce un cursore aggiornato che l’utente deve usare come argomento del cursore nella chiamata successiva.
Un’iterazione inizia quando il cursore è impostato a 0, e termina quando il cursore restituito dal server è 0. Il seguente è un esempio di iterazione SCAN:
redis 127.0.0.1:6379> scan 0 1) "17" 2) 1) "key:12" 2) "key:8" 3) "key:4" 4) "key:14" 5) "key:16" 6) "key:17" 7) "key:15" 8) "key:10" 9) "key:3" 10) "key:7" 11) "key:1" redis 127.0.0.1:6379> scan 17 1) "0" 2) 1) "key:5" 2) "key:18" 3) "key:0" 4) "key:2" 5) "key:19" 6) "key:13" 7) "key:6" 8) "key:9" 9) "key:11"
Nell’esempio sopra, la prima chiamata usa zero come cursore, per iniziare l’iterazione. La seconda chiamata usa il cursore restituito dalla chiamata precedente come primo elemento della risposta, cioè 17.
Come potete vedere il valore di ritorno di SCAN è un array di due valori: il primo valore è il nuovo cursore da usare nella chiamata successiva, il secondo valore è un array di elementi.
Siccome nella seconda chiamata il cursore restituito è 0, il server ha segnalato al chiamante che l’iterazione è finita, e la collezione è stata completamente esplorata. Iniziare un’iterazione con un valore del cursore pari a 0, e chiamare SCAN fino a quando il cursore restituito è di nuovo 0 è chiamato un’iterazione completa.
*Garanzie di SCAN
Il comando SCAN, e gli altri comandi della famiglia SCAN, sono in grado di fornire all’utente un insieme di garanzie associate alle iterazioni complete.
- Un’iterazione completa recupera sempre tutti gli elementi che erano presenti nella collezione dall’inizio alla fine di un’iterazione completa. Questo significa che se un dato elemento è dentro l’insieme quando un’iterazione è iniziata, ed è ancora lì quando un’iterazione termina, allora ad un certo punto SCAN lo ha restituito all’utente.
- Un’iterazione completa non restituisce mai nessun elemento che NON era presente nell’insieme dall’inizio alla fine di un’iterazione completa. Così, se un elemento è stato rimosso prima dell’inizio di un’iterazione, e non viene mai aggiunto di nuovo all’insieme per tutto il tempo che dura un’iterazione, SCAN assicura che questo elemento non sarà mai restituito.
Tuttavia, poiché SCAN ha pochissimo stato associato (solo il cursore) ha i seguenti svantaggi:
- Un dato elemento può essere restituito più volte. Sta all’applicazione gestire il caso di elementi duplicati, per esempio utilizzando solo gli elementi restituiti per eseguire operazioni che sono sicure se riapplicate più volte.
- Gli elementi che non erano costantemente presenti nell’insieme durante un’iterazione completa, possono essere restituiti o meno: è indefinito.
*Numero di elementi restituiti ad ogni chiamata SCAN
Le funzioni della famiglia SCAN non garantiscono che il numero di elementi restituiti per chiamata siano in un dato intervallo. I comandi possono anche restituire zero elementi, e il cliente non dovrebbe considerare l’iterazione completa finché il cursore restituito non è zero.
Comunque il numero di elementi restituiti è ragionevole, cioè, in termini pratici SCAN può restituire un numero massimo di elementi dell’ordine di qualche decina di elementi quando si itera una grande collezione, o può restituire tutti gli elementi della collezione in una sola chiamata quando la collezione iterata è abbastanza piccola da essere rappresentata internamente come una struttura dati codificata (questo accade per piccoli insiemi, hash e insiemi ordinati).
C’è comunque un modo per l’utente di regolare l’ordine di grandezza del numero di elementi restituiti per chiamata usando l’opzione COUNT.
*L’opzione COUNT
Mentre SCAN non fornisce garanzie sul numero di elementi restituiti ad ogni iterazione, è possibile regolare empiricamente il comportamento di SCAN usando l’opzione COUNT. Fondamentalmente con COUNT l’utente specifica la quantità di lavoro che dovrebbe essere fatto ad ogni chiamata per recuperare elementi dalla collezione. Questo è solo un suggerimento per l’implementazione, tuttavia in generale questo è ciò che ci si può aspettare la maggior parte delle volte dall’implementazione.
- Il valore predefinito di COUNT è 10.
- Quando si itera lo spazio delle chiavi, o un Set, Hash o Sorted Set che è abbastanza grande da essere rappresentato da una tabella hash, assumendo che non venga usata l’opzione MATCH, il server di solito restituirà il conteggio o un po’ più di elementi per chiamata. Si prega di controllare la sezione Perché SCAN può restituire tutti gli elementi in una volta sola più avanti in questo documento.
- Quando si iterano insiemi codificati come intset (piccoli insiemi composti solo da interi), o hash e insiemi ordinati codificati come ziplist (piccoli hash e insiemi composti da piccoli valori individuali), di solito tutti gli elementi vengono restituiti nella prima chiamata SCAN indipendentemente dal valore COUNT.
Importante: non è necessario usare lo stesso valore COUNT per ogni iterazione. Il chiamante è libero di cambiare il conteggio da un’iterazione all’altra come richiesto, purché il cursore passato nella chiamata successiva sia quello ottenuto nella chiamata precedente al comando.
*L’opzione MATCH
È possibile iterare solo gli elementi che corrispondono a un dato pattern di tipo glob, in modo simile al comportamento del comando KEYS che prende un pattern come unico argomento.
Per farlo, basta aggiungere gli argomenti MATCH <pattern>
alla fine del comando SCAN (funziona con tutti i comandi della famiglia SCAN).
Questo è un esempio di iterazione usando MATCH:
redis 127.0.0.1:6379> sadd myset 1 2 3 foo foobar feelsgood (integer) 6 redis 127.0.0.1:6379> sscan myset 0 match f* 1) "0" 2) 1) "foo" 2) "feelsgood" 3) "foobar" redis 127.0.0.1:6379>
È importante notare che il filtro MATCH viene applicato dopo che gli elementi vengono recuperati dalla collezione, appena prima di restituire i dati al client. Questo significa che se il pattern corrisponde a pochissimi elementi all’interno dell’insieme, SCAN probabilmente non restituirà alcun elemento nella maggior parte delle iterazioni. Un esempio è mostrato qui sotto:
redis 127.0.0.1:6379> scan 0 MATCH *11* 1) "288" 2) 1) "key:911" redis 127.0.0.1:6379> scan 288 MATCH *11* 1) "224" 2) (empty list or set) redis 127.0.0.1:6379> scan 224 MATCH *11* 1) "80" 2) (empty list or set) redis 127.0.0.1:6379> scan 80 MATCH *11* 1) "176" 2) (empty list or set) redis 127.0.0.1:6379> scan 176 MATCH *11* COUNT 1000 1) "0" 2) 1) "key:611" 2) "key:711" 3) "key:118" 4) "key:117" 5) "key:311" 6) "key:112" 7) "key:111" 8) "key:110" 9) "key:113" 10) "key:211" 11) "key:411" 12) "key:115" 13) "key:116" 14) "key:114" 15) "key:119" 16) "key:811" 17) "key:511" 18) "key:11" redis 127.0.0.1:6379>
Come potete vedere la maggior parte delle chiamate ha restituito zero elementi, ma l’ultima chiamata in cui è stato usato un COUNT di 1000 per forzare il comando a fare più scansioni per quella iterazione.
*L’opzione TYPE
A partire dalla versione 6.0 puoi usare questa opzione per chiedere a SCAN di restituire solo gli oggetti che corrispondono a un dato type
, permettendoti di iterare il database alla ricerca di chiavi di un tipo specifico. L’opzione TYPE è disponibile solo sulla SCAN dell’intero database, non su HSCAN o ZSCAN ecc.
L’argomento type
è lo stesso nome di stringa che il comando TYPE restituisce. Notate una stranezza per cui alcuni tipi di Redis, come GeoHashes, HyperLogLogs, Bitmap e Bitfields, possono essere implementati internamente usando altri tipi di Redis, come una stringa o zset, quindi non possono essere distinti da altre chiavi dello stesso tipo da SCAN. Per esempio, uno ZSET e un GEOHASH:
redis 127.0.0.1:6379> GEOADD geokey 0 0 value (integer) 1 redis 127.0.0.1:6379> ZADD zkey 1000 value (integer) 1 redis 127.0.0.1:6379> TYPE geokey zset redis 127.0.0.1:6379> TYPE zkey zset redis 127.0.0.1:6379> SCAN 0 TYPE zset 1) "0" 2) 1) "geokey" 2) "zkey"
È importante notare che il filtro TYPE viene applicato anche dopo che gli elementi sono stati recuperati dal database, quindi l’opzione non riduce la quantità di lavoro che il server deve fare per completare un’iterazione completa, e per tipi rari si potrebbe non ricevere elementi in molte iterazioni.
*Iterazioni multiple parallele
È possibile per un numero infinito di client iterare la stessa collezione allo stesso tempo, poiché lo stato completo dell’iteratore è nel cursore, che viene ottenuto e restituito al client ad ogni chiamata. Lato server non viene preso alcuno stato.
*Terminare le iterazioni a metà
Poiché non c’è stato lato server, ma lo stato completo è catturato dal cursore, il chiamante è libero di terminare un’iterazione a metà strada senza segnalare questo al server in alcun modo. Un numero infinito di iterazioni può essere iniziato e mai terminato senza alcun problema.
*Chiamare SCAN con un cursore corrotto
Chiamare SCAN con un cursore rotto, negativo, fuori portata, o comunque non valido, risulterà in un comportamento non definito ma mai in un crash. Ciò che sarà indefinito è che le garanzie sugli elementi restituiti non possono più essere assicurate dall’implementazione di SCAN.
Gli unici cursori validi da usare sono:
- Il valore del cursore 0 quando si inizia un’iterazione.
- Il cursore restituito dalla precedente chiamata a SCAN per continuare l’iterazione.
*Garanzia di terminazione
L’algoritmo SCAN è garantito per terminare solo se la dimensione della collezione iterata rimane limitata ad una data dimensione massima, altrimenti l’iterazione di una collezione che cresce sempre può portare SCAN a non terminare mai una iterazione completa.
Questo è facile da vedere intuitivamente: se la collezione cresce c’è sempre più lavoro da fare per visitare tutti i possibili elementi, e la capacità di terminare l’iterazione dipende dal numero di chiamate a SCAN e dal valore della sua opzione COUNT rispetto al tasso di crescita della collezione.
*Perché SCAN può restituire tutti gli elementi di un tipo di dati aggregati in una sola chiamata?
Nella documentazione delle opzioni COUNT
, si afferma che talvolta questa famiglia di comandi può restituire tutti gli elementi di un Set, Hash o Sorted Set in una sola chiamata, indipendentemente dal valore dell’opzione COUNT
. Il motivo per cui questo accade è che l’iteratore basato sul cursore può essere implementato, ed è utile, solo quando il tipo di dati aggregati che stiamo analizzando è rappresentato come una tabella hash. Tuttavia Redis utilizza un’ottimizzazione della memoria in cui i piccoli tipi di dati aggregati, fino a quando non raggiungono una data quantità di elementi o una data dimensione massima di elementi singoli, sono rappresentati utilizzando una codifica compatta a singola allocazione. Quando questo è il caso, SCAN non ha un cursore significativo da restituire, e deve iterare l’intera struttura di dati in una volta sola, quindi l’unico comportamento sano che ha è quello di restituire tutto in una chiamata.
Tuttavia una volta che le strutture di dati sono più grandi e vengono promosse all’uso di vere tabelle hash, la famiglia di comandi SCAN ricorrerà al comportamento normale. Si noti che poiché questo comportamento speciale di restituire tutti gli elementi è vero solo per piccoli aggregati, non ha effetti sulla complessità o latenza del comando. Tuttavia i limiti esatti per essere convertiti in tabelle hash reali sono configurabili dall’utente, quindi il numero massimo di elementi che potete vedere restituiti in una singola chiamata dipende da quanto grande possa essere un tipo di dati aggregato e utilizzare ancora la rappresentazione impacchettata.
Nota anche che questo comportamento è specifico di SSCAN, HSCAN e ZSCAN. SCAN itself never shows this behavior because the key space is always represented by hash tables.
*Return value
SCAN, SSCAN, HSCAN and ZSCAN return a two elements multi-bulk reply, where the first element is a string representing an unsigned 64 bit number (the cursor), and the second element is a multi-bulk with an array of elements.
- SCAN array of elements is a list of keys.
- SSCAN array of elements is a list of Set members.
- HSCAN array of elements contain two elements, a field and a value, for every returned element of the Hash.
- ZSCAN array of elements contain two elements, a member and its associated score, for every returned element of the sorted set.
*History
-
>= 6.0
: Supports the TYPE subcommand.
*Additional examples
Iteration of a Hash value.
redis 127.0.0.1:6379> hmset hash name Jack age 33 OK redis 127.0.0.1:6379> hscan hash 0 1) "0" 2) 1) "name" 2) "Jack" 3) "age" 4) "33"