Scan cursor [MATCH pattern] [MATCH pattern] [COUNT count] [TYPE type]

Disponível desde 2.8.0.

Complexidade de tempo: O(1) para cada chamada. O(N) para uma iteração completa, incluindo chamadas de comando suficientes para que o cursor volte a 0. N é o número de elementos dentro da coleção.

O comando SCAN e os comandos estreitamente relacionados SSCAN, HSCAN e ZSCAN são usados a fim de iterar incrementalmente sobre uma coleção de elementos.

  • SCAN itera o conjunto de chaves da base de dados Redis atualmente selecionada.
  • SSCAN itera elementos de Sets types.
  • HSCAN itera campos de Hash types e seus valores associados.
  • ZSCAN itera elementos de Sets Sorted Set types e suas pontuações associadas.

Desde que estes comandos permitam a iteração incremental, retornando apenas um pequeno número de elementos por chamada, eles podem ser usados em produção sem o lado negativo de comandos como KEYS ou SMEMBERS que podem bloquear o servidor por um longo tempo (até mesmo vários segundos) quando chamados contra grandes coleções de chaves ou elementos.

No entanto, enquanto comandos de bloqueio como SMEMBERS são capazes de fornecer todos os elementos que fazem parte de um Set em um dado momento, a família de comandos SCAN oferece apenas garantias limitadas sobre os elementos retornados, uma vez que a coleção que nós iteramos incrementalmente pode mudar durante o processo de iteração.

Nota que SCAN, SSCAN, HSCAN e ZSCAN funcionam de forma muito semelhante, por isso esta documentação cobre todos os quatro comandos. No entanto uma diferença óbvia é que no caso do SSCAN, HSCAN e ZSCAN o primeiro argumento é o nome da chave que contém o valor Set, Hash ou Sorted Set. O comando SCAN não precisa de nenhum argumento de nome de chave pois itera chaves no banco de dados atual, então o objeto iterado é o próprio banco de dados.

*SCAN uso básico

SCAN é um iterador baseado no cursor. Isto significa que a cada chamada do comando, o servidor retorna um cursor atualizado que o usuário precisa usar como argumento do cursor na próxima chamada.

Uma iteração começa quando o cursor é definido como 0, e termina quando o cursor retornado pelo servidor é 0. O seguinte é um exemplo de iteração 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" 

No exemplo acima, a primeira chamada usa zero como cursor, para iniciar a iteração. A segunda chamada usa o cursor retornado pela chamada anterior como o primeiro elemento da resposta, ou seja, 17.

Como você pode ver o valor de retorno SCAN é um array de dois valores: o primeiro valor é o novo cursor para usar na próxima chamada, o segundo valor é um array de elementos.

Desde que na segunda chamada o cursor retornado é 0, o servidor sinalizou para o chamador que a iteração terminou, e a coleção foi completamente explorada. Iniciando uma iteração com um valor de cursor 0, e chamando SCAN até que o cursor retornado seja 0 novamente é chamada uma iteração completa.

*As garantias de varredura

O comando SCAN, e os outros comandos da família SCAN, são capazes de fornecer ao usuário um conjunto de garantias associadas às iterações completas.

  • Uma iteração completa sempre recupera todos os elementos que estavam presentes na coleção desde o início até o final de uma iteração completa. Isto significa que se um dado elemento está dentro da coleção quando uma iteração é iniciada, e ainda está lá quando uma iteração termina, então em algum momento o SCAN o devolve ao usuário.
  • Uma iteração completa nunca retorna nenhum elemento que NÃO estava presente na coleção desde o início até o final de uma iteração completa. Então se um elemento foi removido antes do início de uma iteração, e nunca é adicionado de volta à coleção por todo o tempo que uma iteração dura, SCAN garante que esse elemento nunca será retornado.

No entanto, como SCAN tem muito pouco estado associado (apenas o cursor) ele tem os seguintes inconvenientes:

  • Um dado elemento pode ser retornado várias vezes. Cabe à aplicação lidar com o caso de elementos duplicados, por exemplo, usando apenas os elementos retornados para realizar operações que são seguras quando re-aplicados várias vezes.
  • Elementos que não estavam constantemente presentes na coleção durante uma iteração completa, podem ser retornados ou não: ele é indefinido.

*Número de elementos retornados em cada chamada SCAN

As funções da família SCAN não garantem que o número de elementos retornados por chamada esteja em um determinado intervalo. Os comandos também podem retornar zero elementos, e o cliente não deve considerar a iteração completa desde que o cursor retornado não seja zero.

No entanto o número de elementos retornados é razoável, ou seja, em termos práticos SCAN pode retornar um número máximo de elementos na ordem de algumas dezenas de elementos ao iterar uma grande coleção, ou pode retornar todos os elementos da coleção em uma única chamada quando a coleção iterada é pequena o suficiente para ser representada internamente como uma estrutura de dados codificada (isto acontece para pequenos conjuntos, hashes e conjuntos ordenados).

No entanto, existe uma forma de ajustar a ordem de grandeza do número de elementos retornados por chamada usando a opção COUNT.

*A opção COUNT

Embora SCAN não ofereça garantias sobre o número de elementos retornados em cada iteração, é possível ajustar empiricamente o comportamento de SCAN usando a opção COUNT. Basicamente com COUNT o usuário especificou a quantidade de trabalho que deve ser feito em cada chamada para recuperar elementos da coleção. Isto é apenas uma dica para a implementação, no entanto geralmente é o que se poderia esperar da implementação.

  • O valor padrão de COUNT é 10.
  • Ao iterar o espaço chave, ou um Set, Hash ou Set Sorted que seja grande o suficiente para ser representado por uma tabela hash, assumindo que nenhuma opção MATCH é usada, o servidor normalmente retornará a contagem ou um pouco mais do que contar elementos por chamada. Por favor verifique o porque SCAN pode retornar todos os elementos de uma vez mais tarde neste documento.
  • Quando iterando Sets codificados como intsets (pequenos conjuntos compostos de apenas inteiros), ou Hashes e Sets ordenados codificados como ziplists (pequenos hashes e conjuntos compostos de pequenos valores individuais), normalmente todos os elementos são retornados na primeira chamada SCAN independentemente do valor do COUNT.

Importante: não há necessidade de usar o mesmo valor de COUNT para cada iteração. O chamador é livre para alterar a contagem de uma iteração para a outra conforme necessário, desde que o cursor passado na próxima chamada seja o obtido na chamada anterior ao comando.

*A opção MATCH

Só é possível iterar elementos que correspondam a um dado padrão estilo glob, de forma similar ao comportamento do comando KEYS que toma um padrão como único argumento.

Para fazer isso, basta anexar o MATCH <pattern> argumentos no final do comando SCAN (funciona com todos os comandos da família SCAN).

Este é um exemplo de iteração 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 notar que o filtro MATCH é aplicado depois que os elementos são recuperados da coleção, imediatamente antes de retornar os dados para o cliente. Isto significa que se o padrão combinar muito poucos elementos dentro da coleção, o SCAN provavelmente não retornará nenhum elemento na maioria das iterações. Um exemplo é mostrado abaixo:

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 você pode ver a maioria das chamadas retornou zero elementos, mas a última chamada onde um COUNT de 1000 foi usado para forçar o comando a fazer mais varredura para aquela iteração.

*A opção TYPE

As da versão 6.0 você pode usar esta opção para pedir ao SCAN para retornar somente objetos que correspondem a um dado type, permitindo a iteração através da base de dados procurando por chaves de um tipo específico. A opção TYPE só está disponível no SCAN da base de dados inteira, não no HSCAN ou ZSCAN etc.

O argumento type é o mesmo nome de string que o comando TYPE retorna. Note uma peculiaridade onde alguns tipos de Redis, tais como GeoHashes, HyperLogLogs, Bitmaps e Bitfields, podem ser implementados internamente usando outros tipos de Redis, tais como uma string ou zset, por isso não podem ser distinguidos de outras chaves desse mesmo tipo pelo SCAN. Por exemplo, um ZSET e 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 notar que o filtro TYPE também é aplicado depois dos elementos serem recuperados da base de dados, por isso a opção não reduz a quantidade de trabalho que o servidor tem que fazer para completar uma iteração completa, e para tipos raros você pode não receber elementos em muitas iterações.

*Iterações paralelas múltiplas

É possível para um número infinito de clientes iterar a mesma coleção ao mesmo tempo, já que o estado completo do iterador está no cursor, que é obtido e devolvido ao cliente a cada chamada. Do lado do servidor não é tomado nenhum estado.

*Terminating iterations in the middle

Desde que não existe nenhum lado do servidor de estados, mas o estado completo é capturado pelo cursor, o chamador é livre para terminar uma iteração a meio caminho sem sinalizar isto para o servidor de qualquer forma. Um número infinito de iterações pode ser iniciado e nunca terminado sem nenhum problema.

*Chamar SCAN com um cursor corrompido

Chamar SCAN com um cursor quebrado, negativo, fora do alcance, ou inválido de outra forma, resultará em comportamento indefinido, mas nunca em um travamento. O que será indefinido é que as garantias sobre os elementos retornados não podem mais ser asseguradas pela implementação de SCAN.

Os únicos cursores válidos para usar são:

  • O valor do cursor de 0 ao iniciar uma iteração.
  • O cursor retornado pela chamada anterior a SCAN para continuar a iteração.

*Garantia de terminação

O algoritmo SCAN é garantido para terminar somente se o tamanho da coleção iterada permanecer limitado a um determinado tamanho máximo, caso contrário iterar uma coleção que sempre cresce pode resultar em SCAN para nunca terminar uma iteração completa.

Isto é fácil de ver intuitivamente: se a coleção cresce, há cada vez mais trabalho a fazer para visitar todos os elementos possíveis, e a capacidade de terminar a iteração depende do número de chamadas para SCAN e seu valor de opção COUNT comparado com a taxa na qual a coleção cresce.

*Por que SCAN pode retornar todos os elementos de um tipo de dado agregado em uma única chamada?

Na documentação da opção COUNT, afirmamos que às vezes esta família de comandos pode retornar todos os elementos de um Conjunto, Hash ou Conjunto Ordenado de uma só vez em uma única chamada, independentemente do valor da opção COUNT. A razão porque isto acontece é que o iterador baseado no cursor pode ser implementado, e é útil, somente quando o tipo de dados agregados que estamos escaneando é representado como uma tabela hash. No entanto, Redis usa uma otimização de memória onde pequenos tipos de dados agregados, até atingirem uma determinada quantidade de itens ou um determinado tamanho máximo de elementos individuais, são representados usando uma codificação compacta de alocação única embalada. Quando este é o caso, SCAN não tem nenhum cursor significativo para retornar, e deve iterar toda a estrutura de dados de uma vez, então o único comportamento são é retornar tudo em uma chamada.

No entanto, uma vez que as estruturas de dados são maiores e são promovidas a usar tabelas de hash reais, a família de comandos SCAN irá recorrer ao comportamento normal. Note que como este comportamento especial de retornar todos os elementos é verdadeiro apenas para pequenos agregados, ele não tem efeitos sobre a complexidade ou latência do comando. Entretanto os limites exatos para ser convertido em tabelas de hash reais são configuráveis pelo usuário, então o número máximo de elementos que você pode ver retornados em uma única chamada depende de quão grande um tipo de dado agregado pode ser e ainda assim usar a representação empacotada.

Notem também que este comportamento é específico do 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" 

Deixe uma resposta

O seu endereço de email não será publicado. Campos obrigatórios marcados com *