Disponible desde la versión 2.8.0.
Complejidad temporal: O(1) para cada llamada. O(N) para una iteración completa, incluyendo suficientes llamadas al comando para que el cursor vuelva a 0. N es el número de elementos dentro de la colección.
El comando SCAN y los comandos estrechamente relacionados SSCAN, HSCAN y ZSCAN se utilizan para iterar incrementalmente sobre una colección de elementos.
- SCAN itera el conjunto de claves en la base de datos Redis actualmente seleccionada.
- SSCAN itera elementos de tipo Sets.
- HSCAN itera campos de tipo Hash y sus valores asociados.
- ZSCAN itera elementos de tipo Sorted Set y sus puntuaciones asociadas.
- *Uso básico de SCAN
- *Garantías de exploración
- *Número de elementos devueltos en cada llamada de SCAN
- *La opción COUNT
- *La opción MATCH
- *La opción TYPE
- *Iteraciones paralelas múltiples
- *Terminar iteraciones en la mitad
- *Llamando a SCAN con un cursor corrupto
- *Garantía de terminación
- *¿Por qué SCAN puede devolver todos los elementos de un tipo de dato agregado en una sola llamada?
- *Return value
- *History
- *Additional examples
Dado que estos comandos permiten una iteración incremental, devolviendo sólo un pequeño número de elementos por llamada, pueden ser utilizados en producción sin el inconveniente de comandos como KEYS o SMEMBERS que pueden bloquear el servidor durante mucho tiempo (incluso varios segundos) cuando se llaman contra grandes colecciones de claves o elementos.
Sin embargo, mientras que los comandos de bloqueo como SMEMBERS son capaces de proporcionar todos los elementos que forman parte de un Set en un momento dado, la familia de comandos SCAN sólo ofrece garantías limitadas sobre los elementos devueltos ya que la colección que iteramos incrementalmente puede cambiar durante el proceso de iteración.
Nótese que SCAN, SSCAN, HSCAN y ZSCAN funcionan de forma muy similar, por lo que esta documentación cubre los cuatro comandos. Sin embargo, una diferencia obvia es que en el caso de SSCAN, HSCAN y ZSCAN el primer argumento es el nombre de la clave que contiene el valor de Set, Hash o Sorted Set. El comando SCAN no necesita ningún argumento de nombre de clave ya que itera las claves de la base de datos actual, por lo que el objeto iterado es la propia base de datos.
*Uso básico de SCAN
SCAN es un iterador basado en un cursor. Esto significa que en cada llamada del comando, el servidor devuelve un cursor actualizado que el usuario debe utilizar como argumento del cursor en la siguiente llamada.
Una iteración comienza cuando el cursor se pone a 0, y termina cuando el cursor devuelto por el servidor es 0. El siguiente es un ejemplo de iteración de 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"
En el ejemplo anterior, la primera llamada utiliza cero como cursor, para iniciar la iteración. La segunda llamada utiliza el cursor devuelto por la llamada anterior como primer elemento de la respuesta, es decir, 17.
Como se puede ver el valor de retorno de SCAN es un array de dos valores: el primer valor es el nuevo cursor a utilizar en la siguiente llamada, el segundo valor es un array de elementos.
Dado que en la segunda llamada el cursor devuelto es 0, el servidor indicó a la persona que llamó que la iteración terminó, y la colección fue explorada completamente. Comenzar una iteración con un valor de cursor 0, y llamar a SCAN hasta que el cursor devuelto sea 0 de nuevo se llama una iteración completa.
*Garantías de exploración
El comando SCAN, y los otros comandos de la familia SCAN, son capaces de proporcionar al usuario un conjunto de garantías asociadas a las iteraciones completas.
- Una iteración completa siempre recupera todos los elementos que estaban presentes en la colección desde el inicio hasta el final de una iteración completa. Esto significa que si un elemento dado está dentro de la colección cuando se inicia una iteración, y todavía está allí cuando una iteración termina, entonces en algún momento SCAN lo devolvió al usuario.
- Una iteración completa nunca devuelve ningún elemento que NO estaba presente en la colección desde el inicio hasta el final de una iteración completa. Por lo tanto, si un elemento fue eliminado antes del inicio de una iteración, y nunca se añade de nuevo a la colección durante todo el tiempo que dura una iteración, SCAN asegura que este elemento nunca será devuelto.
- Un elemento dado puede ser devuelto varias veces. Depende de la aplicación manejar el caso de los elementos duplicados, por ejemplo sólo utilizando los elementos devueltos para realizar operaciones que sean seguras cuando se vuelvan a aplicar múltiples veces.
- Los elementos que no estaban constantemente presentes en la colección durante una iteración completa, pueden ser devueltos o no: es indefinido.
- El valor de COUNT por defecto es 10.
- Cuando se itera el espacio de claves, o un Conjunto, Hash o Conjunto ordenado que es lo suficientemente grande como para ser representado por una tabla hash, suponiendo que no se utiliza la opción MATCH, el servidor normalmente devolverá el recuento o un poco más de elementos de recuento por llamada. Consulte la sección por qué SCAN puede devolver todos los elementos a la vez más adelante en este documento.
- Cuando se iteran Conjuntos codificados como intsets (conjuntos pequeños compuestos sólo por enteros), o Hashes y Conjuntos ordenados codificados como ziplists (pequeños hashes y conjuntos compuestos por pequeños valores individuales), normalmente se devuelven todos los elementos en la primera llamada a SCAN independientemente del valor COUNT.
Sin embargo, debido a que SCAN tiene muy poco estado asociado (sólo el cursor) tiene los siguientes inconvenientes:
*Número de elementos devueltos en cada llamada de SCAN
Las funciones de la familia SCAN no garantizan que el número de elementos devueltos por llamada estén en un rango determinado. También se permite que los comandos devuelvan cero elementos, y el cliente no debe considerar la iteración completa mientras el cursor devuelto no sea cero.
Sin embargo, el número de elementos devueltos es razonable, es decir, en términos prácticos SCAN puede devolver un número máximo de elementos del orden de unas decenas de elementos cuando se itera una colección grande, o puede devolver todos los elementos de la colección en una sola llamada cuando la colección iterada es lo suficientemente pequeña como para ser representada internamente como una estructura de datos codificada (esto sucede para conjuntos pequeños, hashes y conjuntos ordenados).
Sin embargo, existe una forma de que el usuario ajuste el orden de magnitud del número de elementos devueltos por llamada utilizando la opción COUNT.
*La opción COUNT
Aunque SCAN no proporciona garantías sobre el número de elementos devueltos en cada iteración, es posible ajustar empíricamente el comportamiento de SCAN utilizando la opción COUNT. Básicamente, con COUNT el usuario especifica la cantidad de trabajo que debe realizarse en cada llamada para recuperar elementos de la colección. Esto es sólo una pista para la implementación, sin embargo, en términos generales esto es lo que se podría esperar la mayoría de las veces de la implementación.
Importante: no es necesario utilizar el mismo valor COUNT para cada iteración. El invocador es libre de cambiar el recuento de una iteración a otra según sea necesario, siempre que el cursor pasado en la siguiente llamada sea el obtenido en la anterior llamada al comando.
*La opción MATCH
Es posible iterar sólo los elementos que coincidan con un patrón de estilo glob dado, de forma similar al comportamiento del comando KEYS que toma un patrón como único argumento.
Para ello, basta con añadir los argumentos MATCH <pattern>
al final del comando SCAN (funciona con todos los comandos de la familia SCAN).
Este es un ejemplo de iteración utilizando 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>
Es importante tener en cuenta que el filtro MATCH se aplica después de recuperar los elementos de la colección, justo antes de devolver los datos al cliente. Esto significa que si el patrón coincide con muy pocos elementos dentro de la colección, es probable que SCAN no devuelva ningún elemento en la mayoría de las iteraciones. A continuación se muestra un ejemplo:
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>
Como se puede ver la mayoría de las llamadas devolvieron cero elementos, pero la última llamada en la que se utilizó un COUNT de 1000 para forzar al comando a realizar más escaneos para esa iteración.
*La opción TYPE
A partir de la versión 6.0 se puede utilizar esta opción para pedir a SCAN que sólo devuelva objetos que coincidan con un determinado type
, permitiendo iterar por la base de datos buscando claves de un tipo específico. La opción TYPE sólo está disponible en el SCAN de toda la base de datos, no en HSCAN o ZSCAN, etc.
El argumento type
es el mismo nombre de cadena que devuelve el comando TYPE. Tenga en cuenta una peculiaridad en la que algunos tipos de Redis, como GeoHashes, HyperLogLogs, Bitmaps y Bitfields, pueden ser implementados internamente utilizando otros tipos de Redis, como una cadena o un zset, por lo que no pueden ser distinguidos de otras claves de ese mismo tipo por SCAN. Por ejemplo, un ZSET y 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"
Es importante tener en cuenta que el filtro TYPE también se aplica después de recuperar los elementos de la base de datos, por lo que la opción no reduce la cantidad de trabajo que el servidor tiene que hacer para completar una iteración completa, y para los tipos raros puede no recibir ningún elemento en muchas iteraciones.
*Iteraciones paralelas múltiples
Es posible que un número infinito de clientes iteren la misma colección al mismo tiempo, ya que el estado completo del iterador está en el cursor, que se obtiene y devuelve al cliente en cada llamada. En el lado del servidor no se toma ningún estado.
*Terminar iteraciones en la mitad
Dado que no hay estado en el lado del servidor, pero el estado completo es capturado por el cursor, el llamador es libre de terminar una iteración a mitad de camino sin señalar esto al servidor de ninguna manera. Un número infinito de iteraciones pueden ser iniciadas y nunca terminadas sin ningún problema.
*Llamando a SCAN con un cursor corrupto
Llamando a SCAN con un cursor roto, negativo, fuera de rango, o de otra manera inválido, resultará en un comportamiento indefinido pero nunca en un crash. Lo que será indefinido es que las garantías sobre los elementos devueltos ya no pueden ser aseguradas por la implementación de SCAN.
Los únicos cursores válidos a utilizar son:
- El valor del cursor de 0 al iniciar una iteración.
- El cursor devuelto por la llamada anterior a SCAN para continuar la iteración.
*Garantía de terminación
El algoritmo SCAN está garantizado para terminar sólo si el tamaño de la colección iterada permanece acotado a un tamaño máximo dado, de lo contrario iterar una colección que siempre crece puede resultar en que SCAN nunca termine una iteración completa.
Esto es fácil de ver intuitivamente: si la colección crece cada vez hay más trabajo que hacer para visitar todos los elementos posibles, y la capacidad de terminar la iteración depende del número de llamadas a SCAN y de su valor de la opción COUNT en comparación con la velocidad a la que crece la colección.
*¿Por qué SCAN puede devolver todos los elementos de un tipo de dato agregado en una sola llamada?
En la documentación de la opción COUNT
se indica que en ocasiones esta familia de comandos puede devolver todos los elementos de un Set, Hash o Sorted Set de una sola vez en una sola llamada, independientemente del valor de la opción COUNT
. La razón por la que esto ocurre es que el iterador basado en el cursor puede implementarse, y es útil, sólo cuando el tipo de datos agregados que estamos escaneando se representa como una tabla hash. Sin embargo, Redis utiliza una optimización de memoria en la que los tipos de datos agregados pequeños, hasta que alcanzan una cantidad determinada de elementos o un tamaño máximo determinado de elementos individuales, se representan utilizando una codificación compacta de asignación única. Cuando este es el caso, SCAN no tiene ningún cursor significativo para devolver, y debe iterar toda la estructura de datos a la vez, por lo que el único comportamiento cuerdo que tiene es devolver todo en una llamada.
Sin embargo, una vez que las estructuras de datos son más grandes y se promueven para utilizar tablas hash reales, la familia de comandos SCAN recurrirá al comportamiento normal. Obsérvese que, dado que este comportamiento especial de devolver todos los elementos es válido sólo para agregados pequeños, no tiene efectos en la complejidad o latencia del comando. Sin embargo, los límites exactos para convertirse en tablas hash reales son configurables por el usuario, por lo que el número máximo de elementos que puede ver devueltos en una sola llamada depende de lo grande que pueda ser un tipo de datos agregados y seguir utilizando la representación empaquetada.
También tenga en cuenta que este comportamiento es específico de SSCAN, HSCAN y 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"